Detecting the app switch current application is one behind

I'm trying to set up a macro to send the current application to a Raspberry Pi Pico over USB each time the frontmost application switches.

The following code works perfectly run in Script Editor or in a Python or Bash script via osascript:

tell application "System Events" to displayed name of (processes where frontmost is true)

However, when I run it in Keyboard Maestro, it always sends the app that is being switched from rather than the one switched to.

Stranger than this, when I check what is being sent in Keyboard Maestro, it seems to be correct, but that isn't what is received at the other end.

This is how it is set up:


Although I've also tried it using both AppleScript and bash scripts in Keyboard Maestro to find the current app with the same result.

When I enable it and switch to, say, Safari then VS Code then back to Keyboard Maestro, the "speak text" will say "Safari, Code, Keyboard Maestro", but the Pi will say it is receiving "Keyboard Maestro, Safari, Code".

Yet if I use a constantly running python script, it receives the current app name correctly.

Any ideas about what's going on here? I'd rather not be polling System Events every second via a python script, but that's the only thing working for me at the moment.

Thanks.

Try using a local, not a global, variable by renaming to Local_active_app and using echo "$KMVAR_Local_active_app" in your shell script. And inputting the variable to the shell script action isn't necessary -- change "With input from" to "Nothing".

That'll force the use of the current instance value for your variable, avoiding any timing/caching issues from the global updating -- plus it's good practice to use a local unless you need the persistence or cross-macro availability a global provides.

Thanks for that, but it's still the same. I made the changes, switched from Keyboard Maestro to Safari and the "speak text" said "Safari" but the Pi said it received "Keyboard Maestro".

Weird.

What happens if you try this instead?

App Swich Test.kmmacros (3.0 KB)

That pops up with the correct app.

There's something about sending it to the USB port that puts it one place behind. If I just do echo "Hello" > /dev/cu.usbmodem1413303 in the command line without the Keyboard Maestro macro turned on, the Pico receives "Hello" immediately, but if I turn the macro on and do the same, it is one behind again.

Something is buffering these messages, but only when Keyboard Maestro is involved. Without KM listening, messages sent directly to the USB port are received immediately.

Not really a solution, but could you flush the buffer with a simple echo > /dev/cu.usbmodem1413303 in a shell script action immediately before the "proper" one?

Yes -- grasping at straws here!

As another workaround option, write the current app variable to a file in /tmp, then cat that file and send the output to the Pi. I don't see any way that could return the wrong value.

-rob.

Thanks for grasping, but that really confuses it!

It writes the correct app name to the file, but the Pi is still reporting the previous name. Something is getting stuck somewhere, and it only happens through Keyboard Maestro, not when I run any of these options at the command line or through a python or AppleScript script.

I don't see how that's even possible—very weird. What does the Pi get if you write "Hello world" to the file, then send that? Does it still get "Keyboard Maestro?"

As another thought, what happens if you assign a hot key to your macro, quit the KM Editor, then launch the macro via hot key? (Either your version, or my version that writes the data to a file first.)

-rob.

Which leaves the usual -- if it works in Terminal but doesn't work in a KM shell action, it's probably a difference in environments. printenv in Terminal, same in a KM shell script, compare the two to see if you can find out what it is.

This is making less and less sense to me,

The main while TRUE loop on the Pi is running this code:

   if usb_cdc.data.in_waiting > 0:
        print(f"Waiting data: {usb_cdc.data.in_waiting} bytes")
        line = usb_cdc.data.readline()
        print(f"Raw data: {line}")
        line = line.strip().decode("utf-8")
        print(f"Received {line} from host")

If I use the python script on the Mac to poll every second for the current app and send it if it changes, the Pico reports the following from those print statements:

code.py output:
Waiting data: 5 bytes
Raw data: b'Warp\n'
Received Warp from host
Waiting data: 7 bytes
Raw data: b'Safari\n'
Received Safari from host
Waiting data: 10 bytes
Raw data: b'Mu Editor\n'
Received Mu Editor from host

That's obviously when I switch from Warp to Safari to Mu Editor.

Doing the same using the KM macro, I get the following:

code.py output:
Waiting data: 5 bytes
Raw data: b'Warp\n'
Received Warp from host
Waiting data: 7 bytes
Raw data: b'Safari\n'
Received Safari from host
Waiting data: 10 bytes

With the first, it reports the waiting data (the number of characters including the line ending \n), then the raw data, then the decoded string, as you would expect.

With the KM macro, it prints the previous raw data, then the previous formatted name, but then the number of bytes of the new data.

So from the python script, it prints new data size, new raw data, new formatted string in that order, from KM it prints old raw data, old formatted string, new data size in that order. That 10 bytes at the end of the second log is correct for "Mu Editor\n" but it doesn't report that until the next change. I don't know how that is possible but that's what's happening!

Almost like the \n isn't being sent until the beginning of the next submission and the Pi is blocking on the readline...

Out of interest, what happens if you include an explicit linefeed in your variable:

%Application%1%%LineFeed%

...then use echo -n in your shell script:

#!/bin/bash
echo -n "$KMVAR_Local_active_app" > /dev/cu.usbmodem1413303

No, afraid not. Like you say, it seems to receive the data, print out the number of bytes then get stuck at the readline until the next batch of data is sent, but only on the data sent by KM, not when sent from a python script with pyserial, AppleScript or from a zsh console. readline should read all the data up to the next linefeed (this is CircuitPython on the Pi) or, if there isn't one, to the end of the data stream—which should be the same for this particular data.

Ooooh! How about from an AppleScript action in a KM macro? And what about a direct "Write to file..." action, avoiding the (pared down ENV) shell?

I've tried an execute AppleScript action, and I've tried executing the action in a shell script action using osascript -e but the result is the same.

Which points even more to a difference in your "personal" and KM's shell environments. printenv in Terminal, "Execute Shell Script printenv" in KM, compare the two.

This is KM:

KMINFO_ThisMacroUUID=
SHELL=/bin/zsh
KMINFO_PasteByNameText=
TMPDIR=/var/folders/6x/9mv1_twd3psb81yvtsd_1svh0000gn/T/
KMINFO_PromptWithListModifiers=
KMINFO_TriggerTime=1720447862.68400383
LC_ALL=en_US.UTF-8
KMINFO_MacroUUID=
KMINFO_TriggerValue=
USER=djchadderton
COMMAND_MODE=unix2003
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.h42KoHVvbp/Listeners
__CF_USER_TEXT_ENCODING=0x1F5:0:2
PATH=/usr/local/sbin:/Users/djchadderton/.rbenv/shims:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Users/djchadderton/.fig/bin:/Users/djchadderton/.local/bin
_=/usr/bin/printenv
LaunchInstanceID=FA4258F4-0F88-47C0-8D0B-A8EF4686145E
__CFBundleIdentifier=com.stairways.keyboardmaestro.engine
KMINFO_Trigger=Trying
PWD=/
LANG=en_US.UTF-8
KMINFO_PromptWithListText=
KMINFO_MacroGroupUUID=
XPC_FLAGS=0x0
KMINFO_TriggerBase=Trying
XPC_SERVICE_NAME=0
HOME=/Users/djchadderton
SHLVL=2
KMINFO_ThisMacroGroupName=Trying
KMINFO_ActionResult=OK
LOGNAME=djchadderton
KMINFO_LastWindowID=
KMINSTANCE=60D552F7-A453-46B8-B585-0553AB0572E8:1B2F4BBF-48C1-435B-8584-DDA42CB34A84
KMINFO_MacroName=Trying
KMINFO_MacroGroupName=Trying
KMINFO_ThisMacroGroupUUID=
KMINFO_ThisMacroName=Trying
KMINFO_SelectMenuByNameText=
SECURITYSESSIONID=186a4

And in the shell:

COLORTERM=truecolor
COMMAND_MODE=unix2003
HOME=/Users/djchadderton
LANG=en_GB.UTF-8
LOGNAME=djchadderton
LaunchInstanceID=FA4258F4-0F88-47C0-8D0B-A8EF4686145E
PATH=/usr/local/opt/openssl@1.1/bin:/Users/djchadderton/.nvm/versions/node/v20.8.1/bin:/usr/local/sbin:/Users/djchadderton/.rbenv/shims:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin
PWD=/Users/djchadderton
SECURITYSESSIONID=186a4
SHELL=/bin/zsh
SHLVL=3
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.h42KoHVvbp/Listeners
SSH_SOCKET_DIR=~/.ssh
TERM=xterm-256color
TERM_PROGRAM=WarpTerminal
TERM_PROGRAM_VERSION=v0.2024.06.25.08.02.stable_01
TMPDIR=/var/folders/6x/9mv1_twd3psb81yvtsd_1svh0000gn/T/
USER=djchadderton
WARP_COMBINED_PROMPT_COMMAND_GRID=0
WARP_HONOR_PS1=0
WARP_IS_LOCAL_SHELL_SESSION=1
WARP_USE_SSH_WRAPPER=1
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
__CFBundleIdentifier=dev.warp.Warp-Stable
__CF_USER_TEXT_ENCODING=0x1F5:0:2
OLDPWD=/Users/djchadderton
ZSH=/Users/djchadderton/.oh-my-zsh
P9K_SSH=0
_P9K_SSH_TTY=/dev/ttys001
PAGER=less
LESS=-R
LSCOLORS=Gxfxcxdxbxegedabagacad
RBENV_SHELL=zsh
NVM_DIR=/Users/djchadderton/.nvm
NVM_CD_FLAGS=-q
NVM_BIN=/Users/djchadderton/.nvm/versions/node/v20.8.1/bin
NVM_INC=/Users/djchadderton/.nvm/versions/node/v20.8.1/include/node
CONDA_CHANGEPS1=false
P9K_TTY=old
_P9K_TTY=/dev/ttys001
_=/usr/bin/printenv

SHLVL is different, LANG is US in KM and GB in my shell though that shouldn't make a difference (and I can't see a way to change this in KM). I can't see anything else that looks significant.

SHLVL isn't important here -- it's "how many levels deep" you are in the shell. Start a bash shell from within a bash shell and the new shell's SHLVL will be one more than the old one's:

luggage:Desktop nigel$ echo $SHLVL
1
luggage:Desktop nigel$ bash
Uptime: 15:53  up 109 days, 20:50, 9 users, load averages: 2.36 2.58 2.78

bash-3.2$ echo $SHLVL
2

While language shouldn't make a difference, there's a chance that the other locale settings might. You could try setting everything to en_GB by putting the following under the shebang line but before anything else:

LC_ALL=en_GB.UTF-8
LANG=en_GB.UTF-8

They'll only apply to that instance of the shell, so you won't be breaking other things.

Still no I'm afraid.

And a further odd thing is that when I turn off the KM macro and start up the Python script on the Mac again, the Pico spits out all the data that was missing, as though it's catching up.

Thanks for your persistence, but I think I'm about to give up on this!

1 Like