To this sentence you wrote in reply to Jim (@_jims) - I’ve already said that TimeMachine is no Foreground Application. It’s a Background App based on Processes bundled into the OS.
Greetings from Germany
Tobias
To this sentence you wrote in reply to Jim (@_jims) - I’ve already said that TimeMachine is no Foreground Application. It’s a Background App based on Processes bundled into the OS.
Greetings from Germany
Tobias
Hello @azorpheunt
I read the Article you’ve linked to …. I am by far no Developer even though I’m learning how to develop my own tools. But I think I now understand that Apple has changed a lot how the OS is handling Applications and Processes.
Maybe Mousedown Software had to cut down the functionality of their software depending upon these changes to keep EventScripts in the AppStore.
This would be a good reason why under Sequoia is no chance to catch the TimeMachine Process with EventScripts.
It’s maybe now up to Apple to allow the particular API‘s.
But it could also be a bug in the App itself which causes the Trigger to fail. I am still on Monterey and have to stick with it for a while until I can fully upgrade to Ventura. Based on this I will have no chance to test it.
Anyway - based on all that it makes totally sense to use a LaunchAgent to grab the TimeMachine Process and trigger Macros this way.
Greetings from Germany
Tobias
Correct
Okay, @azorpheunt now that I've had more time and read the thread more carefully, I see I led you down the wrong path. Sorry!
I also see that you have a solution and it appears you've also contributed the solution to the Amethyst site!
Curious, do you know if the window tiling manager yabai has the same issue? I do know that it can uniquely control Mission Control Desktop Spaces. For example, you can configure yabai to ignore certain spaces.
Hello @_jims,
Thank you very much for your response. I learnt so much from this thread and with interactions with the wonderful folks who contributed to it!
The very latest is you mentioning yabai
which was one of the candidates I was considering when I was first hunting around for an auto-window tiling manager. If I remember correctly, between yabai
and Amethyst
, I went with the latter because it was more 'out-of-the-box' what I needed and I didn't want to (at the time) invest time in learning/configuring yabai
.
More recently, I came across a frequent usecase which led me to use yabai. A bit of a tangent this is, but I wanted to auto-focus a specific Application when the mouse would hover over it. Amethyst has this functionality, but it is more of a hammer in the sense that it is enabled for ALL windows and applications or none at all:
However, I only wanted this focus-window-on-mouse-hover for a SINGLE application (Chrome in this case). So I was able to do this thanks to yabai
:
#!/bin/bash
export PATH="$PATH:/opt/homebrew/bin"
export PATH="/opt/homebrew/opt/bc/bin:$PATH"
# Store last focused window to prevent unnecessary actions
COOLDOWN_FILE="/tmp/chrome_focus_cooldown"
DEBUG_LOG="/tmp/chrome_hover_focus_debug.log"
# Clear debug log when starting
echo "--- Starting Chrome Hover Focus $(date) ---" >"$DEBUG_LOG"
log_debug() {
local message="[$(date +%H:%M:%S.%N)] $1"
echo "$message" | tee -a "$DEBUG_LOG"
}
# How often to check (in seconds)
CHECK_INTERVAL=0.1
while true; do
log_debug "Script iteration started"
log_debug "PATH: $PATH"
# Check cooldown
log_debug "Checking cooldown..."
if [ -f "$COOLDOWN_FILE" ]; then
CURRENT_TIME=$(date +%s.%N)
COOLDOWN_UNTIL=$(cat "$COOLDOWN_FILE")
log_debug "Cooldown file exists. Current time: $CURRENT_TIME, Cooldown until: $COOLDOWN_UNTIL"
# Use bc for floating point comparison
if (( $(echo "$CURRENT_TIME < $COOLDOWN_UNTIL" | bc -l) )); then
log_debug "Still in cooldown period, skipping this iteration"
sleep $CHECK_INTERVAL
continue
else
log_debug "Cooldown period expired"
fi
else
log_debug "No cooldown file found"
fi
# Get all window data in a single call for efficiency
log_debug "Querying window under mouse..."
window_data=$(yabai -m query --windows --window mouse)
log_debug "Window data: $window_data"
window_id=$(echo "$window_data" | jq '.id')
log_debug "Window ID: $window_id"
# Check for valid window and Chrome
if [ -n "$window_id" ] && [ "$window_id" != "null" ]; then
app_name=$(echo "$window_data" | jq -r '.app')
log_debug "App name: $app_name"
if [ "$app_name" = "Google Chrome" ] || [ "$app_name" = "Chrome" ]; then
log_debug "Chrome window detected"
# Check if the window already has focus
has_focus=$(echo "$window_data" | jq '.["has-focus"]')
log_debug "Window has focus: $has_focus"
if [ "$has_focus" = "true" ]; then
log_debug "Chrome window already has focus, skipping"
else
# Focus the window
log_debug "Focusing Chrome window: $window_id"
focus_result=$(/opt/homebrew/bin/yabai -m window --focus $window_id 2>&1)
log_debug "Focus result: $focus_result"
# Set cooldown period with a very short duration
CURRENT_TIME=$(date +%s.%N)
# Add 0.005 seconds to current time for cooldown
COOLDOWN_UNTIL=$(echo "$CURRENT_TIME + 0.005" | bc)
echo "$COOLDOWN_UNTIL" > "$COOLDOWN_FILE"
log_debug "Set cooldown until: $COOLDOWN_UNTIL (0.005s cooldown)"
fi
else
log_debug "Not a Chrome window, ignoring"
fi
else
log_debug "No valid window under mouse or could not get window data"
fi
log_debug "Script iteration completed"
# Sleep for short interval
sleep $CHECK_INTERVAL
done
And that's the story of how I first came to appreciate yabai
Back to your query, first of all THANK YOU!! Here is a script I created to monitor the list of windows continuously and highlight the current windows with the word ACTIVE
:
#!/usr/bin/env bash
# yabai-window-monitor.sh - Script to monitor yabai windows with enhanced formatting
# Save this script to a file (e.g., ~/bin/yabai-window-monitor.sh)
# Make it executable: chmod +x ~/bin/yabai-window-monitor.sh
# Run with: watch -n 1 ~/bin/yabai-window-monitor.sh
# For color mode (if supported): COLOR=1 watch -n 1 ~/bin/yabai-window-monitor.sh
# Check if color is enabled via environment variable
if [[ "$COLOR" == "1" ]]; then
# Define color variables
RESET="\033[0m"
CYAN="\033[36m"
BOLD_CYAN="\033[1;36m"
BOLD_GREEN="\033[1;32m"
BOLD_YELLOW="\033[1;33m"
BOLD_WHITE="\033[1;37m"
GRAY="\033[0;90m"
else
# No colors
RESET=""
CYAN=""
BOLD_CYAN=""
BOLD_GREEN=""
BOLD_YELLOW=""
BOLD_WHITE=""
GRAY=""
fi
# Get yabai window information
WINDOWS=$(yabai -m query --windows)
# Print header
echo -e "${BOLD_CYAN}=== YABAI WINDOW MONITOR ===${RESET}"
echo -e "${BOLD_YELLOW}ID${RESET} | ${BOLD_YELLOW}APPLICATION${RESET} | ${BOLD_YELLOW}TITLE${RESET}"
echo "----------------------------------------"
# Create a temporary jq script file
JQ_SCRIPT=$(cat <<EOF
.[] | {id, app, title, focus: ."has-focus", space, display} |
if .focus == true then
"$BOLD_GREEN" + (.id|tostring) + "$RESET" + " | " +
"$BOLD_GREEN" + .app + "$RESET" + " | " +
"$BOLD_GREEN" + .title + "$RESET" + " " +
"$BOLD_WHITE" + "[ACTIVE - Space: " + (.space|tostring) + ", Display: " + (.display|tostring) + "]" + "$RESET"
else
(.id|tostring) + " | " + .app + " | " + .title + " " +
"$GRAY" + "[Space: " + (.space|tostring) + ", Display: " + (.display|tostring) + "]" + "$RESET"
end
EOF
)
# Process and display window information
echo "$WINDOWS" | jq -r "$JQ_SCRIPT"
# Show current date and time
echo -e "\n${CYAN}Last updated: $(date '+%Y-%m-%d %H:%M:%S')${RESET}"
=== YABAI WINDOW MONITOR ===
ID | APPLICATION | TITLE
----------------------------------------
21741 | Firefox Developer Edition | Unable to detect Application Launch or Quit for Time Machine app - Questions & Suggestions - Keyboard Maestro Discourse [ACTIVE - Space: 1, Display: 1]
21739 | Mail | Inbox – redacted – Filter by: Unread (9 messages) [Space: 1, Display: 1]
6172 | IntelliJ IDEA | redacted [~/work/redacted] – [Space: 1, Display: 1]
Last updated: 2025-03-27 14:15:20
=== YABAI WINDOW MONITOR ===
ID | APPLICATION | TITLE
----------------------------------------
22284 | Finder | Timeline Controls [Space: 1, Display: 1]
21321 | Finder | /Users/az [ACTIVE - Space: 1, Display: 1]
21741 | Firefox Developer Edition | Unable to detect Application Launch or Quit for Time Machine app - Questions & Suggestions - Keyboard Maestro Discourse [Space: 1, Display: 1]
Last updated: 2025-03-27 14:18:04
This is so curious, it is almost like there is a Finder
application embedded inside the TimeMachine app because that is what is shown as ACTIVE
. However, even more useful is the fact that there is a Timeline Controls
window which ONLY exists when the Time Machine app is running !!!
So this could potentially be used to achieve my original goal of detection whether Time Machine App is running !
@_jims , here is my attempt to use the Timeline Controls
window title to detect if TimeMachine app is running:
The crux of the following script is this:
is_time_machine_running() {
if yabai -m query --windows | jq -r '.[] | select(.title | contains("Timeline Controls"))' | grep -q "Finder"; then
return 0 # True, Time Machine is running
else
return 1 # False, Time Machine is not running
fi
}
#!/bin/bash
# time-machine-amethyst-manager.sh - Monitors Time Machine and controls Amethyst
# Save this to a file and make it executable with: chmod +x time-machine-amethyst-manager.sh
# Run in the background with: nohup ./time-machine-amethyst-manager.sh &
# Log file
LOG_FILE="$HOME/Library/Logs/time-machine-amethyst-manager.log"
# Function to log messages
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# Function to check if Time Machine is running
is_time_machine_running() {
if yabai -m query --windows | jq -r '.[] | select(.title | contains("Timeline Controls"))' | grep -q "Finder"; then
return 0 # True, Time Machine is running
else
return 1 # False, Time Machine is not running
fi
}
# Function to check if Amethyst is running
is_amethyst_running() {
pgrep -x "Amethyst" > /dev/null
return $?
}
# Function to quit Amethyst
quit_amethyst() {
if is_amethyst_running; then
log_message "Quitting Amethyst because Time Machine was launched"
osascript -e 'tell application "Amethyst" to quit'
else
log_message "Amethyst is already not running"
fi
}
# Function to launch Amethyst
launch_amethyst() {
if ! is_amethyst_running; then
log_message "Launching Amethyst because Time Machine has quit"
open -a Amethyst
else
log_message "Amethyst is already running"
fi
}
# Initial state
TIME_MACHINE_WAS_RUNNING=false
if is_time_machine_running; then
TIME_MACHINE_WAS_RUNNING=true
log_message "Time Machine is running at script start, quitting Amethyst"
quit_amethyst
else
log_message "Time Machine is not running at script start"
fi
log_message "Starting monitoring loop"
# Main loop
while true; do
# Check current state
if is_time_machine_running; then
# Time Machine is running now
if [ "$TIME_MACHINE_WAS_RUNNING" = false ]; then
# Time Machine just started
log_message "Time Machine launched"
quit_amethyst
TIME_MACHINE_WAS_RUNNING=true
fi
else
# Time Machine is not running now
if [ "$TIME_MACHINE_WAS_RUNNING" = true ]; then
# Time Machine just quit
log_message "Time Machine quit"
launch_amethyst
TIME_MACHINE_WAS_RUNNING=false
fi
fi
# Wait before checking again (x seconds)
# sleep 0.001
done
This worked in the sense that it does successfully detect whether TimeMachine App is running or not and accordingly quits or starts Amethyst respectively. However, unfortunately, even after playing around with the sleep
time (near the end of the script), I couldn't get it to work fast enough to quit Amethyst before the damage is done.
I tried using yabai signals as well, hoping that they would be faster than polling to no avail (besides the other problem that my yabai signals script had a bug that it was only detecting the start of TimeMachine app, not the end).
So Yabai was far better than EventScript and even KM, and it will probably be my goto where the timing of application launch detection is not critical.
This was amazing
In this one single thread, I have learnt a lot more about EventScripts, thanks to @Nr.5-need_input , discovered a possible OCR approach thanks to @Airy and have gotten yet another glimpse at the power of Yabai
thanks to you!
This almost seems too obvious to ask, but since I sometimes miss the obvious, I'll go ahead and ask...
Have you considered launching Time Machine with a macro that first quits Amethyst?
I have considered this approach as seen in this reply (#3) above (because a KM macro would essentially be doing the same thing):
But I was stumped with why KM couldn't detect Time Machine and thought it was something wrong that I was doing. That led me down into this rabbit hole of the special nature of a Time Machine app window which evades detection by KM as well as EventScripts.
@_jims Thank you!!! I have a working KM Macro now!! And I was only able to find a solution because of your suggestion to see how yabai
handles this!!
Here it is:
TimeMachine App Monitor Macro (v11.0.3)
TimeMachine App Monitor.kmmacros (5.9 KB)
And this macro very reliably. I tested it quite a few times and it never failed to function as expected once!!
Hi, @azorpheunt. I'm glad you've found a solution that works to your satisfaction.
When all else fails, the focused window trigger can often come in handy. Before Keyboard Maestro included the active space changes trigger, I used it with Desktop Spaces • Macros to Improve Navigation and Window Management. For that macro set, the focused window trigger worked reliably is most cases, but not always. (It was not a bug with the trigger, it was with the macro logic.) Plus, it's probably just me, but I didn't particularly care for all of the clutter this trigger added to the Engine.log. So for my particular case, the active space changes trigger was a welcome addition.