Jump to content
Pennyworth

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!

Share this post


Link to post
Posted (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 by deanishe

Share this post


Link to post

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.

Share this post


Link to post

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.

 

Share this post


Link to post
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

Share this post


Link to post
Posted (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 by deanishe

Share this post


Link to post
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"?

Share this post


Link to post
Posted (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 by deanishe

Share this post


Link to post

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

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
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 🍻

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...