Pennyworth Posted June 14, 2020 Share Posted June 14, 2020 I'm a heavy user of Bjango's iStat Menus, using it to check my system stats frequently. I also happen to be a heavy Alfred user, making my mouse trips up to the menu bar for iStat Menus feel really infuriating. I'd ideally like to open iStat Menus menu bar items using Alfred keywords. For example, if I type in "cpu" in Alfred, it will open the CPU stats from the menu bar, "ram" for "RAM", etc. I've searched everywhere for a workflow like this but can't find anything. I've conducted some research, mostly around how to get a readout of what menu bar items are called by the SystemUIServer, so I can call them with a script, but even that has been fruitless. I also stumbled upon Menu Bar Activate, which lets users activate the menu bar with a hotkey. However, this does not allow activating the right-side of the menu bar, with all the menu bar 'applets'. I haven't found a way to "reverse engineer" this workflow to access those 'applets'. Any help would be greatly appreciated! Link to comment
deanishe Posted June 14, 2020 Share Posted June 14, 2020 (edited) If you've got your heart set on toggling iStat Menu's menu programmatically (rather than finding a workflow that can show the info you're interested in), you need to talk to Bjango. You can simulate clicking on menu bar extras the same way as you can simulate clicking on menu bars. Something like this AppleScript should work: tell application "System Events" to tell process "iStat Menus Status" tell menu bar item 2 of menu bar 1 to click end tell (Where menu bar item 2 will have a different number depending on which widget you want to click). But it doesn't work with iStat Menus. Edited June 14, 2020 by deanishe Link to comment
Pennyworth Posted June 15, 2020 Author Share Posted June 15, 2020 Thank you @deanishe for your help! Your sample script is a helpful start. However, when I run it, it merely selects one of the iStat Menus menu bar items and releases. In other words, it doesn't "open" the menu bar item. I would think that modifying 'click' to 'open' in Line 2 of your script would accomplish this, but there apparently is no 'open' command in AppleScript. Is there a way to 'open' the menu bar item instead of merely clicking/selecting it? Another approach I tried is to write a 'delay' line to give me time to press the down arrow key to 'open' the menu bar item, but this doesn't work, because the delay happens sequentially after the click event. Link to comment
deanishe Posted June 15, 2020 Share Posted June 15, 2020 Read my whole post: I said it doesn’t work with iStat Menus. It should. And it works with other menu bar extras, but it doesn’t work with iStat Menus. Link to comment
Pennyworth Posted June 15, 2020 Author Share Posted June 15, 2020 4 minutes ago, deanishe said: Read my whole post: I said it doesn’t work with iStat Menus. It should. And it works with other menu bar extras, but it doesn’t work with iStat Menus. Ah, that's what you meant by 'it doesn't work.' Understood. I'll email Bjango to see if they can help. Alternatively, do you recommend any Alfred workflows for revealing the system stats that iStat Menus exposes? I'm mainly interested in: battery health/cycles/condition RAM 'pressure'/swap/% utilization per process CPU % utilization per process GPU Status (integrated vs dedicated) temps for CPU/GPU/ports fan speed CPU/GPU/system wattage Link to comment
deanishe Posted June 15, 2020 Share Posted June 15, 2020 (edited) Here’s an alternative JXA script that does work with iStat Menus. Put it in a Run Script action with Language = "/usr/bin/osascript (JS)" You may need to change the matchiStatsItem function at the top, depending on which iStats menu extra you want to click on. /* Click iStat Menus menu bar extra ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Moves mouse cursor to menu bar extra, clicks, then moves mouse back to its previous position. This is a workaround because iStat Menus doesn't respond to AppleScript's simulated clicks. Change the matchiStatsItem function to match the properties of the menu bar extra you want to click on. */ ObjC.import('CoreGraphics') // Function that matches the menu item we're looking for let matchiStatsItem = props => { return props.name.match(/CPU/) } function mouseEvent(type, x, y) { let ev = $.CGEventCreateMouseEvent(null, type, {x:x, y:y}, $.kCGMouseButtonLeft) $.CGEventPost($.kCGHIDEventTap, ev) } function click(x, y) { mouseEvent($.kCGEventLeftMouseDown, x, y) mouseEvent($.kCGEventLeftMouseUp, x, y) } function move(x, y) { mouseEvent($.kCGEventMouseMoved, x, y) } function getMenuProperties(match) { let se = Application('System Events') let istat = se.processes.whose({name: 'iStat Menus Status'})[0] for (let i = 0; i < istat.menuBars.length; i++) { let menubar = istat.menuBars[i] for (let j = 0; j < menubar.menuBarItems.length; j++) { let item = menubar.menuBarItems[j] let props = item.properties() console.log(`item=${props.name}`) if (match(props)) { return props } } } } function showError(title, message) { let app = Application.currentApplication() app.includeStandardAdditions = true app.displayDialog(message, { buttons: ['OK'], defaultButton: 'OK', withTitle: title, withIcon: 'stop', givingUpAfter: 2, }) } function run(argv) { // get current mouse position, so we can restore it later const currentPos = $.CGEventGetLocation($.CGEventCreate(null)) let props = getMenuProperties(matchiStatsItem) if (!props) { showError('iStat Menu Clicker', 'No matching iStat Menu item') throw 'menu item not found' } let pos = props.position click(pos[0]+5, pos[1]+5) move(currentPos.x, currentPos.y) } Edited June 15, 2020 by deanishe Pennyworth 1 Link to comment
deanishe Posted June 15, 2020 Share Posted June 15, 2020 @Pennyworth So, it works then? Thanks for the beer money 🍻. I wouldn't have known it was from you, though, if I weren't a moderator and couldn't seen your email address. Link to comment
Pennyworth Posted June 15, 2020 Author Share Posted June 15, 2020 1 hour ago, deanishe said: Here’s an alternative JXA script that does work with iStat Menus. Thank you so much! This is amazing! I don't know the magic behind much of this, but it works perfectly! Just bought you a beer via PayPal! 🍻 I've gone ahead and replaced /CPU/ with /Memory/ and /Battery/ for the other menu bar items and those work great, but still can't figure out what iStat calls their menu bar items for 'Sensors' and 'Time'. Wonder how I could do that? Alternatively, can the line let matchiStatsItem = props => { return props.name.match(/dateandtime/) } be modified so that it's a "fuzzy match"? deanishe 1 Link to comment
deanishe Posted June 15, 2020 Share Posted June 15, 2020 (edited) 13 minutes ago, Pennyworth said: can't figure out what iStat calls their menu bar items for 'Sensors' and 'Time' I used the Accessibility Inspector to see what iStats widgets are called. If you paste this AppleScript into Script Editor and run it, it will show you the name of every iStats widget: set _names to "" tell application "System Events" tell process "iStat Menus Status" repeat with _bar in every menu bar repeat with _item in _bar's every menu bar item log (name of _item) as text if _names = "" then set _names to (name of _item) as text else set _names to _names & "\n" & (name of _item) as text end if end repeat end repeat end tell display dialog _names end tell You’ll have to change the regular expression to match the title, with some items being easier than others. If you can’t figure some out, post the titles shown, and I’ll try to help. For time, you might need something like \d\d:\d\d, for example. Edited June 15, 2020 by deanishe Pennyworth 1 Link to comment
Pennyworth Posted June 15, 2020 Author Share Posted June 15, 2020 Thank you again for this helpful follow-up script! I really appreciate your help. I ran your script and it showed that my 'Sensor' menu bar item actually goes by "CPU PECI {temp}" (PECI being the "Platform Environment Control Interface", which is the thermal management controller that reports temperatures). I simply swapped out your line with "CPU PECI" and it fuzzy-matched! I did the same for date/time, simply using ":" and it fuzzy-matched as well! (Otherwise, your script reports the menu bar name as a dynamically updated name of whatever time it is in the moment it's queried.) Link to comment
deanishe Posted June 15, 2020 Share Posted June 15, 2020 Just now, Pennyworth said: Otherwise, your script reports the menu bar name as a dynamically updated name of whatever time it is in the moment it's queried. Yes, it will. That's why I used a regular expression to match the name. The widget titles are (presumably) intended to be useful to blind people, not scripts looking for a specific widget. Link to comment
Pennyworth Posted June 15, 2020 Author Share Posted June 15, 2020 11 minutes ago, deanishe said: The widget titles are (presumably) intended to be useful to blind people, not scripts looking for a specific widget. That makes perfect sense. Anyway, thank you for all your help. You may as well add this workflow to your list of workflows! I'm sure there's a good overlap between Alfred user and iStat Menus users. Becoming so accustomed to Alfred over the years, I felt like a dunce still using my mouse to check on stats from the menu bar. Link to comment
deanishe Posted June 15, 2020 Share Posted June 15, 2020 4 minutes ago, Pennyworth said: You may as well add this workflow to your list of workflows! Oh, no. Too much work, tbh. The majority of the effort in building a workflow is making it user-friendly and flexible. If anyone's interested, the script that does the hard bit is here, and they can build their own workflow around it. 7 minutes ago, Pennyworth said: Anyway, thank you for all your help And thanks for the beer! I'm drinking it now 🍻 Pennyworth 1 Link to comment
Pennyworth Posted November 9, 2021 Author Share Posted November 9, 2021 Hi there! I need to start tracking disk usage stats now, and I'm having trouble adapting this workflow to also incorporate that. I thought it would be as simple as using the "menu bar name finder" script that was provided on this thread, and simply using a unique word for the Disk widget ("Data" or "used" or "free") in the line "let matchiStatsItem = props => { return props.name.match(/{}/) }", but none of those terms work. I tried experimenting to see what would get it to work. By disabling my other workflow keywords for the other widgets (CPU, RAM, Sensor, etc), this new Disk keyword started working again, but none of the others did! And then when I flipped it back around, the Disk keyword stopped working! So I'm not exactly sure what's causing this, but something is amiss. The "menu bar name finder" script also reveals "missing value" entries, so not sure if that's indicative of a greater problem here? I just don't know. Here's the full output from that script: CPU 10% Memory Pressure 63%. Memory Used 79% M1 SOC Die Average 101° Battery 100%, Charged, MagicTrackpad 83%, AirPods 90% missing value missing value missing value missing value Data 99% used. 8.9 GB free missing value Link to comment
Pennyworth Posted November 12, 2021 Author Share Posted November 12, 2021 This issue now resolved itself upon restarting my computer. Every iStat Menus menu bar item is now accessible via Alfred workflow keyword. Link to comment
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now