Jump to content

Open iStat Menus menu bar items with Alfred keyword


Recommended Posts

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

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 by deanishe
Link to comment

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
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

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 by deanishe
Link to comment
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"?

Link to comment
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 by deanishe
Link to comment

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
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
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
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 🍻

Link to comment
  • 1 year later...

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...