I want to trigger two macros based on when /Applications/Time Machine.app launches or quits.
However, Keyboard Maestro does not seem to detect the launch or quit of Time Machine app.
Usecase
I use an application called Amethyst which automatically tiles windows. However, Time Machine becomes unsuable when Amethyst is running, so I was hoping to quit Amethyst when Time Machine app lauches and start it again when Time Machine quits. There is a related issue here where the creator of the app mentions that they are unable to detect Time Machine app window.
Things I have checked
I have looked through the forum for any related advice but only found this post which led me to confirm that the macro group itself is not the problem as seen below.
The Macro Group which contains these two macros has no conditional activation. Snapshot of the macro group below:
The Macros do fire if I use some other application (such as iTerm) as the trigger. In this test, if I activate iTerm, first one macro fires and then if I quite iTerm the other macro fires, as expected.
Macros
Launch Amethyst when TimeMachine Quits Macro (v11.0.3)
If the KM trigger doesn't trigger on it, I may know the reason, (some apps have multiple processes, and KM is probably triggering on a different one) but the solution would be to use polling on the correct process name.
Thank you very much. I did think of polling, but I can't seem to activate any other Window (like Activity Monitor) or shell (iTerm etc) to be able to see the process list when Time Machine App is running. I guess I could work around this, but SSH'ing into this machine from another and then using ps or such to see the list.
I was hoping to avoid polling as Amethyst should be quit before launching Time Machine.
The next workaround would be to create a script that does this (i.e. something like killall Amethyst && open "/Applications/Time Machine.app") however, I thought KM would be able to handle this elegantly. Have never come across an application that KM is not able to detect at all.
Well your solution doesn't actually quit Amethyst before Time Machine launches. Your solution quits Amethyst slightly AFTER Time Machine Launches. And polling would achieve the same result. So I don't see a big problem there.
I found a way to do that, and oddly, there appears to be NO DIFFERENCE that I could see in the process list when Time Machine is running vs when it's not running. There are two processes that I could see related to Time Machine, which run all the time, and there's no difference when you launch the app itself.
So this raises a more fundamental question. If Time Machine is always running, what do you mean by: "I want to trigger two macros based on when /Applications/Time Machine.app launches or quits." That statement seems to me to have no meaning.
EDIT: I have an idea that might work. But it will take me an hour, because I need to get something to eat. My idea involves polling and might impart a 1% degradation on your CPU usage, so I'm not sure if you will accept that.
EDIT: Yes, my idea is working. Wow. But I'm still going to make some food while I ponder the reliability of my solution. (My solution might have false positive and false negatives, but I'm not sure yet.)
EventScripts by Mousedown (the Link is shared above) has a Trigger for Background Applications like TimeMachine and many many others … this way EventScripts is the best way to go.
I use it by my self to prepare my Mac for TimeMachine and many other Background Application‘s ….
This script creates a log file on ~/Desktop/EventScripts_Log.txt and keeps appending to it for every execution.
-- EventScripts Logging Utility
-- Configuration flags
property showDialog : false -- Controls whether to show dialog (default: false)
property enableLogging : true -- Controls whether to log to file (default: true)
property logFileName : "EventScripts_Log.txt" -- Name of the log file
-- Main handler - gets called when event triggers
on run eventArgs
-- Format message from event data
set logMessage to formatMessage(eventArgs)
-- Show dialog if enabled
if showDialog then
display dialog logMessage buttons {"OK"} default button "OK" with title "Event Triggered"
end if
-- Log to file if enabled
if enableLogging then
logToFile(eventArgs, logMessage)
end if
end run
-- Function to format message for display and logging
on formatMessage(eventArgs)
-- Start with basic message
set logMessage to "EventScripts Parameters:
"
-- Try to get trigger
try
set triggerValue to trigger of eventArgs
set logMessage to logMessage & "trigger: " & triggerValue & "
"
end try
-- Try to get applicationName
try
set appName to applicationName of eventArgs
set logMessage to logMessage & "applicationName: " & appName & "
"
end try
-- Try to get bundleCode
try
set bundleCode to bundleCode of eventArgs
set logMessage to logMessage & "bundleCode: " & bundleCode & "
"
end try
-- Try to get applicationPath
try
set appPath to applicationPath of eventArgs
set logMessage to logMessage & "applicationPath: " & appPath & "
"
end try
-- Try to get processIdentifier
try
set processID to processIdentifier of eventArgs
set logMessage to logMessage & "processIdentifier: " & processID & "
"
end try
-- Try to get scriptPath
try
set scriptPath to scriptPath of eventArgs
set logMessage to logMessage & "scriptPath: " & scriptPath & "
"
end try
-- Try to get tagName
try
set tagName to tagName of eventArgs
set logMessage to logMessage & "tagName: " & tagName & "
"
end try
return logMessage
end formatMessage
-- Function to create timestamp
on getTimestamp()
set currentDate to current date
set dateString to (year of currentDate as string) & "-" & (month of currentDate as integer as string) & "-" & (day of currentDate as string) & " " & (time string of currentDate)
return dateString
end getTimestamp
-- Function to log to file
on logToFile(eventArgs, logMessage)
-- Get timestamp for log entry
set dateString to getTimestamp()
-- Get event type
set eventType to "Unknown"
try
set eventType to trigger of eventArgs
end try
-- Get app name if available
set appName to "Unknown"
try
set appName to applicationName of eventArgs
on error
-- App name not available for this event type
end try
-- Create full log entry
set completeLogEntry to dateString & " - Event: " & eventType & " - " & appName & "
" & logMessage & "
-------------------
"
-- Get desktop path for log file
tell application "Finder"
set desktopPath to desktop as string
end tell
set theFile to desktopPath & logFileName
-- Write to file with error handling
try
appendToFile(theFile, completeLogEntry)
-- Show notification on successful write
display notification "Log updated for event: " & eventType with title "EventScripts Log" subtitle "Saved to desktop"
on error errMsg
-- Show error even if dialogs are disabled (important for debugging)
display dialog "Error writing log: " & errMsg buttons {"OK"} default button "OK" with icon caution
end try
end logToFile
-- Function to safely append to a file
on appendToFile(filePath, textToAppend)
set fileExists to false
-- Check if file exists
tell application "System Events"
if exists file filePath then
set fileExists to true
end if
end tell
if fileExists then
-- File exists, read contents first
set fileRef to open for access filePath
set currentContent to read fileRef
close access fileRef
-- Now write everything back with the new content appended
set fileRef to open for access filePath with write permission
set newContent to currentContent & textToAppend
write newContent to fileRef starting at 1
close access fileRef
else
-- Create new file
set fileRef to open for access filePath with write permission
write textToAppend to fileRef
close access fileRef
end if
end appendToFile
Results
tl;dr: No joy
Sample content of this log file with my testing showing that each of the event types are detected:
This log file is not updated and the script is not fired for any of the events (including my most hopeful candidate 'Background App Launch') when I launch Time Machine app.
This was a very interesting exploration, even if it didn't work this time around. Thank you.
That's amazing and thanks for pointing that out. I came up with a KM-only solution that involves polling. I just finished a meal and I'll try to test it before I present it here.
Thank you very much @Airy and @Nr.5-need_input , I have a solution that works fine. I had to resort to monitoring the runningboard log entries after discovering how macos Sequoia launches apps thanks to this link.
#!/bin/bash
# Time Machine Launch and Quit Detector
# This script monitors for Time Machine launches and quits using macOS logs
echo "Starting Time Machine launch/quit detector..."
echo "Press Ctrl+C to exit"
# Track the last events to prevent duplicate notifications
LAST_LAUNCH_DETECTED=0
LAST_QUIT_DETECTED=0
COOLDOWN_SECONDS=10
# Set app state tracker
APP_RUNNING=false
# We'll control Amethyst directly with AppleScript
# No script paths needed
# Function to handle launch events
handle_launch() {
CURRENT_TIME=$(date +%s)
# Only trigger if we haven't recently detected a launch
if (( CURRENT_TIME - LAST_LAUNCH_DETECTED > COOLDOWN_SECONDS )); then
LAST_LAUNCH_DETECTED=$CURRENT_TIME
APP_RUNNING=true
# Get current date/time for logging
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
echo "==============================================="
echo "Time Machine launch detected at $TIMESTAMP"
echo "==============================================="
# Quit Amethyst
osascript -e 'tell application "Amethyst" to quit'
echo "Amethyst has been quit"
# Display a notification
osascript -e 'display notification "Time Machine launched and Amethyst quit" with title "Time Machine Monitor"'
echo "Launch handled, waiting for events..."
fi
}
# Function to handle quit events
handle_quit() {
CURRENT_TIME=$(date +%s)
# Only trigger if app was running and we haven't recently detected a quit
if $APP_RUNNING && (( CURRENT_TIME - LAST_QUIT_DETECTED > COOLDOWN_SECONDS )); then
LAST_QUIT_DETECTED=$CURRENT_TIME
APP_RUNNING=false
# Get current date/time for logging
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
echo "==============================================="
echo "Time Machine quit detected at $TIMESTAMP"
echo "==============================================="
# Launch Amethyst
osascript -e 'tell application "Amethyst" to activate'
echo "Amethyst has been launched"
# Display a notification
osascript -e 'display notification "Time Machine quit and Amethyst launched" with title "Time Machine Monitor"'
echo "Quit handled, waiting for events..."
fi
}
# Use the full path to log to avoid shell builtin conflicts
/usr/bin/log stream --predicate '(subsystem == "com.apple.runningboard") AND (eventMessage CONTAINS "com.apple.backup.launcher")' | while read -r line; do
# Check for launch events
if echo "$line" | grep -q "Launch request"; then
handle_launch
fi
# Check for quit/termination events
if echo "$line" | grep -E "process (exited|terminated)|assertion invalidated|no longer tracked|Process .* exited|Removing"; then
handle_quit
fi
done
Hi, @azorpheunt. I don't have time at the moment to carefully follow this thread, but you can use AppleScript or shell commands to return the state of Time Machine. For more information, refer to: Time Machine Assistant
Basically I'm polling with OCR to see if the bottom quarter of the screen contains the Time Machine buttons, and nothing else. Believe it or not, it seems to work.
P.S. I probably should have added semaphore to block duplicate copies from running at the same time.
I did come across this thread, however my impression was that this is more to do with Time Machine Backups being performed rather than the Time Machine application launching/quitting.
I’ve carefully checked after everything you’ve said that you’ve done …
Based on what I’ve seen you did a fabulous job workin out getting the desired information out of the triggers - but if you don’t use the Information to trigger your Macros then you’re right and don’t get any value out of it.
Your shell script and LaunchAgent approach might be a good idea though but it is nothing else then the same EventScripts is doing, despite the fact you’ll have to write a lot of LaunchAgents, when you‘re polling using shell scripts on every Application.
Just had a look at the script, and I believe that it detects whether Time Machine Backups is currently in progress.
To confirm, I executed the applescript contained in the macro. At this point the /Applications/Time Machine.app was not running. The script reported something similar to your snapshot showing that the Time Machine is not running:
However, based on what I've seen (and posted above) EventScripts is unable (just like Keyboard Maestro) to detect the launch (even 'Background') of Time Machine app. I welcome you to use the applescript I posted above and check this yourself.
I think there is something special about the 'Time Machine.app' in that it is not a normal macOS app. I am not a macOS developer so am not certain about this, but the inability of EventScripts and KM to detect Time Machine launch/quit seems to add support to this conjecture.
I think EventScripts (and my goto application for such use case KM) does work for 'normal' apps, but not for this specific usecase (Time Machine.app) which needs special handling.