Unable to detect Application Launch or Quit for Time Machine app

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

Tobias

Hello @azorpheunt :wave:

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

Tobias

1 Like

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:

**Script To Auto Focus Chrome on Mouse Hover using 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 :slight_smile:


Yabai Window Monitor Script

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:

Yabai Continous Window Monitor Script
#!/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}"

Output 1 - Time Machine App not running

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

Output 2 - Time machine App IS running

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

1 Like

@_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
}
Script
#!/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 :slight_smile:

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!

:pray:

1 Like

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.

1 Like

@_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!!

:clap: :clap: :clap:

2 Likes

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.