Detect Audio Output on Mac? - bash

Is there a way to detect if audio is being output from the Mac system? Whether it's via headphone jack, usb-c, or bluetooth, is there a process which runs if and only if audio is playing?
I've made a unix script which plays a 15hz sine waveform, the idea is to execute the script every 580 seconds if and only if no audio is playing. If audio is playing, the script won't execute.
I have really good speakers but the drawback is they enter a 'standby' power saver mode every 10 minutes (600 seconds). When I'm at work I don't really care about whether it runs in the background or not* but when I'm at home I tend to miss notifications when speakers enter standby, so the aim of the script is to play the 3 second waveform every 580 seconds if and only if no audio is playing.

I'm not sure if this is super related to what you want but maybe try out Soundflower ? It's super useful for re-routing audio in mac.

You do not necessarily need to use AppleScript to accomplish the goal, although you certainly can if you want.
I'd opt to use a Launch Agent, as in the following example XML plist code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.my.play.if.no.audio.is.playing</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>[[ $(pmset -g | grep ' sleep') =~ coreaudiod ]] || '/path/to/shell_script'</string>
</array>
<key>RunAtLoad</key>
<false/>
<key>StartInterval</key>
<integer>580</integer>
</dict>
</plist>
Change '/path/to/shell_script' in the XML plist code to the actual fully qualified pathname of your shell script.
Then save is as, e.g., com.my.play.if.no.audio.is.playing.plist in ~/Library/LaunchAgents/ and load it from Terminal using, e.g.:
cd ~/Library/LaunchAgents/
launchctl load com.my.play.if.no.audio.is.playing.plist
Then every 580 seconds your shell script will execute if there is no audio playing.
Notes:
As coded, it assumes the executable bit is set on your shell script.
If you are going to use Launch Agents and Launch Daemons, I highly recommend you read the manual pages for launchctl, launchd.plist and launchd.
You can read the manual page for command in Terminal by typing man command, then press enter, or for easier reading, just type command and then right-click on it and select: Open man Page
To stop your Launch Agent, in Terminal:
cd ~/Library/LaunchAgents/
launchctl unload com.my.play.if.no.audio.is.playing.plist
You could also instead, include the test condition in your shell script, e.g.:
#!/bin/bash
if [[ ! $(pmset -g | grep ' sleep') =~ coreaudiod ]]; then
# Code to generate your sine waveform goes here.
fi
Then, the command used in the example XML plist code would be:
<key>ProgramArguments</key>
<array>
<string>/path/to/shell_script</string>
</array>
If you really want to use AppleScript, and assuming a Stay Open application, then:
Example AppleScript code:
on run
-- # Add any AppleScript code you what run when the
-- # Stay Open AppleScript application is opened.
end run
on idle
do shell script ¬
"[[ $(pmset -g | grep ' sleep') =~ coreaudiod ]] || '/path/to/shell_script'"
return 580
end idle
on quit
continue quit
end quit
Notes:
When saving the example AppleScript code in Script Editor , set File Format: [Application] and [√] Stay open after run handler.
If your add the test condition to your shell script, as shown further above, then your do shell script command would be:
do shell script "'/path/to/shell_script'"
I tend to stay away from using this type of AppleScript application, as I find that over time they can/may become resource intensive in that they continue consume more RAM. YMMV.

Related

Notifying when using high CPU. Via AppleScript or Automator?

I'd like to automate that whenever a process is using more than 50% CPU
it sends a notification to my Notification Center
I'm using terminal-notifier for sending trough notifications
but I'm a bit stuck on what the best method is for creating this automation.
Should I use Automator.app or create a custom AppleScript and if so,
how do I make it to always be on?
If this is for interactive use, let me suggest a pragmatic alternative:
Run Activity Monitor.
Control-click its dock icon.
Select Dock Icon > Show CPU Usage - or, for a floating window, Monitors > Show CPU Usage.
You'll get a per-core display of current CPU usage - clicking on it will show the full Activity Monitor window, where you can sort by CPU usage.
If you do need an automated solution, I suggest:
writing a bash script that uses top to find the highest-CPU-percentage task and invokes terminal-notifier, if above the threshold.
scheduling that script as a launchd task for periodic invocation.
Automator and AppleScript are probably too heavy for such - presumably frequent - background activity.
Even running top itself uses quite a bit of CPU.
Here's a simple bash script that roughly does what you want:
#!/usr/bin/env bash
read pct name < <(top -l 2 -n 1 -F -o cpu -stats cpu,command | tail -1)
if (( ${pct%.*} >= 50 )); then
/Applications/terminal-notifier.app/Contents/MacOS/terminal-notifier \
-message "Process > 50%: $name ($pct%)"
fi
Note that this takes at least 2 seconds to run, because 2 samples (1 second apart) must be collected to calculate CPU-usage percentages, so consider that when determining how frequently to invoke the command.
Update - see below for step-by-step implementation instructions.
References:
As for scheduling the script to have launchd run it on login: see https://stackoverflow.com/a/22872222/45375
The general format of launchd *.plist files is described at https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html or man launchd.plist; StartInterval is the key for specifying invocations every N seconds.
Step-by-step instructions for implementing the automated solution:
Create the bash script:
Create plain-text file ~/watchcpu (i.e., file watchcpu in your home folder), paste the above bash script into it, and save it.
Create the per-user launch agent for invocation at login and periodic invocation thereafter:
Create plain-text file ~/Library/LaunchAgents/WatchCPU.plist, paste the following XML document into it, and save it:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<false/>
<key>Label</key>
<string>WatchCPU</string>
<key>ProgramArguments</key>
<array>
<string>bash</string>
<string>-c</string>
<string>. ~/watchcpu</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>15</integer>
</dict>
</plist>
Load the per-user launch agent to activate it:
Run the following command in Terminal (only needed once; from then on, the file will auto-load on every login):
launchctl load ~/Library/LaunchAgents/WatchCPU.plist
Note:
You're free to choose your own filenames and a different location for your bash script, but the launch agent .plist file MUST reside in ~/Library/LaunchAgents in order to be loaded automatically at login.
The interval (key StartInterval) is chosen at 15 seconds; again, you're free to change that, but note that choosing more frequent invocations doesn't make much sense, because launchd (the service that invokes launch agents) throttles agents whose execution time is too close to the invocation interval; I'm unclear on the details, but in the solution at hand an interval of 10 seconds results in frequent throttling notices in system.log (check via Console.app).
You can easily pull the CPU usage to use either with a script or in an Automator workflow. Here's a script that you can schedule to run on a schedule and will notify if usage is over 50%:
set theDelay to 3 -- number of seconds to sample the CPU
set CPUusage to do shell script "top -F -l " & theDelay & " -n 1 -stats cpu | grep 'CPU usage:' | tail -1 | cut -d. -f1"
set idlePercent to word -2 of CPUusage as number
if idlePercent < 50 then display notification ("CPU usage is at " & (100 - idlePercent) & "%.") with title "CPU Usage"
See the comments to follow the editing of the do shell script command to allow for getting the integer naturally from the shell command to work better with non-English systems.

Run a script only at shutdown (not log off or restart) on Mac OS X

Is there any way to run a script only at shutdown?
I mean, only when the computer is really shutting down to off state. This script should not run when doing just a log off or restart.
Few days ago I published on github a configuration/script able to be executed at boot/shutdown.
Basically on Mac OS X you could/should use a System wide and per-user daemon/agent configuration file (plist) in conjunction with a bash script file. This is a sample of the plist file you could use:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>boot.shutdown.script.name</string>
<key>ProgramArguments</key>
<array>
<string>SCRIPT_PATH/boot-shutdown.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>LOG_PATH/boot-shutdown.log</string>
<key>StandardErrorPath</key>
<string>LOG_PATH/boot-shutdown.err</string>
</dict>
</plist>
You can place this file into /Library/LaunchDaemons. There are many directories where the plist file could be placed, it depends from what you need, the rights of the process and so on.
~/Library/LaunchAgents Per-user agents provided by the user.
/Library/LaunchAgents Per-user agents provided by the administrator.
/Library/LaunchDaemons System wide daemons provided by the administrator.
/System/Library/LaunchAgents Mac OS X Per-user agents.
/System/Library/LaunchDaemons Mac OS X System wide daemons.
This script boot-shutdown.sh will be loaded and executed at every boot/shutdown.
#!/bin/bash
function shutdown()
{
# INSERT HERE THE COMMAND YOU WANT EXECUTE AT SHUTDOWN OR SERVICE UNLOAD
exit 0
}
function startup()
{
# INSERT HERE THE COMMAND YOU WANT EXECUTE AT STARTUP OR SERVICE LOAD
tail -f /dev/null &
wait $!
}
trap shutdown SIGTERM
trap shutdown SIGKILL
startup;
Then call launchctl command which load and unload daemons/agents.
sudo launchctl load -w /Library/LaunchDaemons/boot-shutdown-script.plist
It looks like the most straightforward way would be to write a small C++ application that would run as a daemon with launchctl, catch the shutdown notification but ignore the reboot notification (see below) and then call whatever is given to it as arguments, e.g. a shell script. It does not look like Apple provides libraries to catch those notifications in any other language.
From the "Kernel Programming" manual https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/KernelProgramming/KernelProgramming.pdf from Apple, page 150:
"Although OS X does not have traditional BSD-style shutdown hooks, the I/O Kit provides equivalent functionality in recent versions. Since the I/O Kit provides this functionality, you must call it from C++ code."
"To register for notification, you call registerSleepWakeInterest (described in IOKit/RootDomain.h) and register for sleep notification. If the system is about to be shut down, your handler is called with the message type kIOMessageSystemWillPowerOff. If the system is about to reboot, your handler gets the message type kIOMessageSystemWillRestart. If the system is about to reboot, your handler gets the message type kIOMessageSystemWillSleep."
As you can see there is a different message for reboot, so you can handle the shutdown case exclusively.
Here is a way that does work (I just tested it) but it is quite technical and not for inexperienced people... I put a wrapper around /sbin/shutdown. This will work even if you shutdown your Mac from the Apple menu in the GUI.
Basically, you need to su to root, like this, and rename the existing, Apple-supplied shutdown binary to shutdown.orig.
su -
cd /sbin
mv shutdown shutdown.orig
Then you create a bash script called shutdown that does what you want first, then execs the original Apple-supplied shutdown binary.
#!/bin/bash
Do something you want done before shutdown
exec /sbin/shutdown.orig "$#"
There are three things to watch out for...
1. Make all the permissions the same on shutdown as shutdown.orig
2. Parse the parameters to the originl shutdown and see if `-r` is one of them as this means it is a `restart` shutdown. You will also have to pass through the other parameters that Apple calls the script with - if any.
3. Apple may feel at liberty to overwrite your lovely, shiny, new `shutdown` script when updating OSX, so maybe abstract out the bulk of your personal shutdown script into another place so that you can easily re-insert a single-line call to it if/when Apple overwrites it at some point.
Be careful! And make a backup first!
https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CustomLogin.html#//apple_ref/doc/uid/10000172i-SW10-SW1
I tested it on my MacOs 12.5.1,It does work!
cat LogoutHook.sh
#!/bin/bash
export BLUEUTIL_ALLOW_ROOT=1
function shutdown()
{
echo `date` >> /Users/blanc/.LaunchShell/shutdown_date.log
echo `whoami` >> /Users/blanc/.LaunchShell/shutdown_date.log
/usr/local/bin/blueutil -p 0
sleep 1
#exit 0
}
shutdown;
sudo defaults write com.apple.loginwindow LogoutHook /Users/blanc/.LaunchShell/LogoutHook.sh
In case someone comes to this answer like me googling for similar but somewhat different problem.
Due to CopyQ crash bug that I found https://github.com/hluk/CopyQ/issues/1301
I had to write a cron script which will reload if it crashes.
But I did not want to reload copyq, if copyq exits during shutdown or restart
I ended up using
if ps aux | grep "Finder.app" | grep -v grep
then
echo "I am still running"
else
echo "I am in shutdown"
fi
To detect if system is in shutdown, (you can use Chrome.app or any other always running app depending on your need)
not exactly the solution, but helped me solve a similar problem that I was having

OS X LaunchAgent should launch an application

i am looking for a way to periodically re-launch an application in OS X. since this application has a GUI i chose the LaunchAgent method to do it. the LaunchAgent will call a shell script, which will quit the application (if running) and then open it again.
so far it works, with the only exception that once my called shell script has ended, the launched GUI application is also quit.
i tried opening the application with trailing "&" and using nohup, but no luck.
this is my LaunchAgent (ignore my stupid StartCalendarInterval settings, they are for debugging only):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.mycompany.myapp_relauncher</string>
<key>ProgramArguments</key>
<array>
<string>/Users/xyz/Desktop/myapp_relauncher.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Minute</key>
<integer>21</integer>
<key>Minute</key>
<integer>25</integer>
</dict>
</dict>
</plist>
and this is the called script /Users/xyz/Desktop/myapp_relauncher.sh
#!/bin/bash
function log { echo "myapp: "`date "+%Y%m%d-%H%M%S"`" $1" ; logger -t "myapp" "$1" ; }
# stopping the app if running (code ommitted here)
log "starting application now..."
nohup /Applications/myapp.app/Contents/MacOS/myapp &
log "sleeping a few seconds now..."
sleep 10 # the launched applications stays open
log "done sleeping, quitting."
# script ends, launched application ends too
Add <key>AbandonProcessGroup</key><true/> to your LaunchAgent .plist; without that, when the agent (the script) exits launchd "helpfully" cleans up all leftover subprocesses (technically, anything with the same process group ID), like for example the application.
You want to use disown here. This works without closing the application after the script finishes:
#!/bin/bash
/Applications/Adium.app/Contents/MacOS/Adium &
disown
This is because using & just puts the job into the background. Even though it's in the background it is still a child process of the current script, which is killed when its parent process, the script, finishes. By using disown that relationship is removed and it runs on its own.
You might also just use the open command:
killall myapp; open -jga myapp
open -jg usually opens an application hidden and without raising any windows. If for example TextEdit is open but has no open windows, -jg creates and raises a new default window though. To avoid it, you can test if the application is already open:
<key>ProgramArguments</key>
<array>
<string>bash</string>
<string>-c</string>
<string>pgrep -x TextEdit||open -jga TextEdit</string>
</array>

How can I skip logging my executable prints to the Mac Console

I'm creating a Mac Launch Agent for my app. The Launch agent works fine. But the executable bin file prints some console messages,when the exe is started in terminal and all those messages are logged to Mac Console. How can i skip logging those messages to the Mac Console ..?
I have tried adding a shell script as a Lauch Agent, which starts the exe, so that executable won't log messages to the console. But the script doesn't starts the bin.
This is my Launch Agent plist file.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myapp</string>
<key>Program</key>
<string>./bin/MyBin</string>
<key>WorkingDirectory</key>
<string>/Applications/MyApp/</string>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
You can override the default treatment of stdout and stderr for launchd jobs by adding the StandardOutPath and StandardErrorPath keys to your .plist file. To discard all output, add this:
<key>StandardOutPath</key>
<string>/dev/null</string>
<key>StandardErrorPath</key>
<string>/dev/null</string>
You could also redirect one or both to a log file.
The easiest way is as Gordon Davisson said above. However when i need to start more than one executable, then its possible by shell script.
This was my Script :
#!/bin/bash
cd /Applications/MyApp/
dates=`date`
echo "Exe Started at ::: ${dates}" > proc.log
# Starting Exe
./bin/MyBin 1>/dev/null 2>/dev/null & echo $! > proc.pid
I was having trouble with this script. I can see the proc.log filling up, but no sign of my App. Surprisingly when i removed the '&' from the script ,launch agent started working fine.
./bin/MyBin 1>/dev/null 2>/dev/null & echo $! > proc.pid
Changed to
./bin/MyBin 1>/dev/null 2>/dev/null
I m not sure why Mac is making the background process as a difference in launchd. The Script was & is working fine as it is, when i manually run it.
The output can only be redirected before the process begins. In this case if you don't want to change all your code then the executable for your launch agent will need to be a script. The script can then redirect as needed, e.g.
#!/bin/sh
# redirect to a log file, /dev/null, or wherever it should go;
# this is just an example log file destination
`/usr/bin/dirname $0`/run_real_agent > /tmp/my_agent.$$.log 2>&1

Does "launchctl unload" on OSX pass "ProgramArguments" to the target application?

I am attempting to set up a .plist file for use with launchctl on OSX. When run from the command line (not using launchctl), our application is executed as:
/path/to/ourapp
... and to terminate our application, we type:
/path/to/ourapp -k
... which causes the new instance of ourapp to properly kill the previous instance that is running.
Now I set up a .plist file to control execution of the application via launchctl, as follows:
// start.plist
<dict>
<key>Disabled</key>
<false/>
<key>Label</key>
<string>ourapp</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/ourapp</string>
</array>
<key>OnDemand</key>
<false/>
<key>UserName</key>
<string>daniel347x</string>
<key>SHAuthorizationRight</key>
<string>system.preferences</string>
</dict>
... and I execute it at the command line, as follows:
launchctl load /path/to/start.plist
This works successfully to launch the application.
Unfortunately, when creating a stop.plist, as follows (the only difference is the addition of the -k argument):
// stop.plist
<dict>
<key>Disabled</key>
<false/>
<key>Label</key>
<string>ourapp</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/ourapp</string>
<string>-k</string> // <-- only difference is adding this argument
</array>
<key>OnDemand</key>
<false/>
<key>UserName</key>
<string>daniel347x</string>
<key>SHAuthorizationRight</key>
<string>system.preferences</string>
</dict>
... and executing via
launchctl unload /path/to/stop.plist
... the application does NOT terminate ... therefore, it seems that when using launchctl, the application is not being executed equivalently to
/path/to/ourapp -k
Can someone tell me what launchctl unload is really doing - i.e. is it calling the application from the command line with the given arguments, or not - and what I can do to get my stop.plist to work when using launchctl unload, given whatever it is doing?
High level
No, "launchctl unload" on OSX does not pass "ProgramArguments" to the target application. It removes the plist from the ones that launchd is running.
Short answer
I'm not sure you are using launchctl and launchd the way it was designed, but try:
launchctl load /path/to/stop.plist
Longer answer
The launchctl command is used to control which plists launchd runs at any given time. When you issue:
launchctl load /path/to/start.plist
the plist is loaded into launchd and your "ourapp" is trigged. A critical point is that unless "ourapp" unloads the start.plist, it will still be running in launchd. You can check this by viewing the output from:
launchctl list
The reason your call to unload the stop.plist didn't have any effect is that it was never loaded into launchd to begin with. To be able to stop it, you must first start it with:
launchctl load /path/to/stop.plist
The trick with this is that once you do this, both your start.plist and stop.plist will be running. Obviously, I haven't tested your app/setup, but this could cause weird behavior. Also, I'm not sure what running the "load" again on your start.plist would do since it would already be running.
Form what you've described here, it looks like launchd and launchctl might not be the best approach. If you want to stick with it, I think you would need to add system calls into your "ourapp" so that when it gets issued the kill command it also unloads both the start and the stop plist. Or, write a proxy script that unloads the plists and then calls your "ourapp -k". That way, the next time you use "launchctl load /path/to/start.plist" you won't be trying to start something that's already running.
One other note. You might have just been using the "ourapp" as place holders, but it's worth pointing out that you probably want to use unique Label names. If nothing else, it'll help you keep track of them with "launchctl list". I don't know if launchd itself would have an issue with duplicate names, but it seems safest to avoid the possibility.

Resources