Launchd as a replacement for at

A need for a simple timer/alarm script to keep me from overbrewing my tea led to a better understanding of the Mac’s launchd system.

I drink tea in the morning at work. My routine is to go to the kitchen, start the tea brewing, go back to my office to look at email or something, then return to the kitchen when the tea is done, three or four minutes later. Sometimes—OK, lots of times—I forget the tea and it steeps for half an hour or more. Bitter! I needed a timer to remind me to go back to the kitchen when the tea was done.

You may ask why I don’t just bring the tea with me to my office and let it steep there. Two reasons. First, my office wastebasket doesn’t have a plastic liner, so throwing a wet teabag in it makes a mess. Second, I like to put a couple of ice cubes in the tea after it’s brewed so I can start drinking right away. The ice cubes, of course, are in the kitchen.

There are several timer programs available for the Mac, both commercial and free, but I didn’t want to be running yet another program for just a couple of cups of tea. Also, I had particular ideas of how I wanted to start the timer (a keystroke combination from within any program) and how I wanted to be alerted (both audio and visual). These requirements meant I’d be writing my own little script.

The script itself was short and simple.

delay 180
tell application "Play Sound" to play "HD:System:Library:Sounds:Glass.aiff"
tell application "FastScripts" to display message "Tea is done!" dismissing after delay 0

The script is called Tea-time.scpt. It uses AppleScript’s builtin delay command to wait three minutes before alarming. The audio alarm comes from the faceless scriptable Play Sound application (which I’ve described before. The visual alarm comes from FastScripts. The dismissing after delay 0 part keeps the alert up on my screen until I deliberately get rid of it. If I’m not in my office when the alarm goes off, this will remind me to go back to the kitchen.

After the script was debugged, I figured I could save it in ~/Library/Scripts and configure FastScripts to run it with a single keystroke combo from within any application. Things didn’t work out that way, which is how I learned more about launchd.

The problem with running Tea-time from FastScripts is that FastScripts gets locked up until the script finishes, three minutes later. This is no good; it’s likely I’ll want to use another FastScripts script during that time, especially if I spend that time processing email, as I use John Gruber’s non-top-posting script for replies. I thought about using the at command. FastScripts, I thought, could run a one-liner shell script that puts the Tea-time script (without the delay line) in the at queue for execution three minutes later. The one-liner would return immediately, freeing FastScripts for other uses.

But the Mac has at disabled by default. Its man page tells you how to enable it but warns of excess power use if you do.

at, batch, atq, atrm are all disabled by default on Mac OS X. Each of these commands depend on the execution of atrun(8) which has been disabled due to power management concerns. Those who would like to use these commands, must first (as root) re-enable atrun by running:

launchctl load -w /System/Library/LaunchDaemons/

Not a problem on my iMac, I suppose, but maybe I should just do things the way Apple wants. Which means launchd.

I’ve used launchd before as a replacement for cron—my library notification script runs at 5:00 every morning through a launch agent—but never for at. So I opened Lingon, Peter Borg’s nifty—albeit abandoned—launchd plist generator, and looked for at-like stuff in the When section.

Nothing really jumps out, does it? I guess that makes sense, because launchd is running all the time, and what I’m looking for is something that runs once. But there is a way to get what I want.

Here’s the setup of a launch agent that runs the Tea-time script (via the osascript shell command) whenever it’s been modified, i.e., whenever its modification time changes.

With this launch agent installed, I can have FastScripts run this little shell script, which I call “Tea time” (no extension) and keep in ~/Library/Scripts.

touch /Users/drang/bin/Tea-time.scpt

The touch command changes Tea-time.scpt’s modification time. That will trigger the launch agent and run the script. The touch command returns immediately, which frees FastScripts for other work.

(Note that I saved Tea-time.scpt in my ~/bin directory, not in ~/Library/Scripts. That was to keep it from appearing in my FastScripts menu.)

The plist file, com.leancrew.tea-time.plist, that Lingon puts in my /Library/LaunchAgents folder is

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

Lingon will put up a warning message that you have to log out and log back in to get the new launch agent working. It’s true that every plist in ~/Library/LaunchAgents gets loaded automatically when you log in, but launch agents can be loaded manually, too. Just run this command from the Terminal:

launchctl load ~/Library/LaunchAgents/com.leancrew.tea-time.plist

It loads the new launch agent immediately, without the log out/log in bother.

So now I have an entry called “Tea time” in my FastScripts menu, which is bound to the ⌃⌥⌘-0 keystroke combination1 (that’s a zero, not an oh). As long as I remember to use it, my tea is perfect. And I learned a neat launchd trick, to boot.

Update 8/10/09
This afternoon, Daniel Jalkut of Red Sweater informed me that FastScripts would not have been blocked had I saved Tea-time as an application ( instead of a script (Tea-time.scpt). I’ve done a bit of testing, and while that’s true (of course it’s true; he knows his own program, doesn’t he?), doing it that way doesn’t give me the behavior I want. With saved in the ~/Library/Scripts folder and run from FastScripts, the Tea-time application puts an icon in the Dock and becomes the frontmost app, forcing me to click or ⌘-Tab back to the app I was in before. Maybe there’s a way around this behavior—I’ll look for one in my copy of Soghoian tonight—but until I learn what that is, I prefer my more convoluted but less obtrusive solution.


  1. Why that combo? Well, it’s easy to remember and I’m already using ⌃⌥⌘-T for something else