A stay-open AppleScript application containing an idle handler receives periodic idle events from the system, even if the application is running in the background. The idle handler is automatically called for the first time as soon as the application’s run or open handler terminates (no explicit call to the idle handler is required). The idle handler is run again every thirty seconds thereafter by default. It can be made to run at different intervals by causing the idle handler to return a positive integer number of seconds (or, starting with Mac OS X 10.3 (Panther), a positive real number) as the desired “sleep” period until the next idle event. Any other return value, such as 0, causes it to repeat at the previously established interval. It can be made to run at random intervals, for example, by calling the Random Number command in the Standard Additions scripting addition as the last statement in the idle handler. The idle handler can also be run on demand by calling it explicitly.
Note carefully that this all works only if you save your script as an application with the Stay Open option enabled.
The key to effective use of the idle handler is to think of a script application’s run or open handler as an initialization routine, and to build the main functionality of the application into its idle handler. Since the idle handler is called by the system at intervals, it can easily be coded to do simple, unchanging tasks at every call, such as beeping periodically forever. A more useful application must perform one or more tests in the idle handler to ascertain what to do every time the system calls it. For example, using variables that track the current state of the application, the idle handler can perform a series of tasks in turn, waiting until each task is completed before performing the next. After the final task is completed, the stay-open application can quit, or it can repeat the whole cycle indefinitely.
In effect, the system’s periodic calls to the idle handler can function as a traditional Macintosh event loop. In older versions of the Mac OS, the minimum interval between system calls to the idle handler was one full second, making it impractical to use this technique for applications that require quick responsiveness. The minimum interval changed in Mac OS X 10.3 (Panther), when intervals expressed as real numbers were first allowed. Now, for example, you can trigger the idle handler every tenth of a second, or even more frequently.)
A common mistake is to code the idle handler as a repeat loop. A repeat loop is not needed, because the system itself calls the idle handler repeatedly.
Here are some strategies for using the idle handler:
- To repeat a fixed routine at regular intervals, simply code it in the idle handler and set the desired repetition interval. The idle handler will automatically start firing as soon as the run handler terminates, usually after running some initialization routines written into the run handler, and it will continue to fire periodically until the user quits the application. A common example is an “alarm clock” script that checks the current time periodically, watching for the alarm time, then beeps or plays an iTunes song when the set time arrives. This example is a very slow “metronome,” beeping once every five seconds.
global numberOfBeeps on idle beep numberOfBeeps return 5 end idle on run set numberOfBeeps to 1 end run
- To pause and resume idle-time processing, set and reset a Boolean flag or a counter variable under appropriate conditions. The idle handler should first test the flag, then its idle-time code will either lie dormant or run depending on the state of the flag. The following example emits two quick beeps while counting down from 3 to 1 at five-second intervals, but it skips the second cycle.
global numberOfBeeps, beepCycle on idle if beepCycle is greater than 0 then if beepCycle is not 2 then beep numberOfBeeps --skip second cycle set beepCycle to beepCycle - 1 end if return 5 end idle on run set numberOfBeeps to 2 set beepCycle to 3 end run
An alternative technique is to set the idle interval to a very long time – effectively forever – as soon as idle handling is to be paused. Then the idle handler will not even be called by the system until other code – perhaps an external AppleScript application – explicitly calls the idle handler again, at which time the idle interval can be reset to a shorter time to “unpause” it.
- To stop the idle handler from running altogether, use one of several techniques: (1) quit immediately from within the idle handler or a handler that is called from the idle handler; (2) code a flag in the idle handler that causes it to do nothing thereafter; or (3) set the idle interval to a very long time, such as a day or a week (if the computer is not left running that long). The next example modifies the previous example by quitting once the countdown ends.
global numberOfBeeps, beepCycle on idle if beepCycle is greater than 0 then if beepCycle is not 2 then beep numberOfBeeps --skip second cycle set beepCycle to beepCycle - 1 else quit end if return 5 end idle on run set numberOfBeeps to 2 set beepCycle to 3 end run
- To call the idle handler off-cycle in the middle of a script’s logical flow, put the initialization routines and the first part of the program in the run handler, then call the idle handler explicitly. Place test routines in the idle handler and, when the desired condition arises, run the rest of the program’s logic from the idle handler until done.
global numberOfBeeps, beepCycle on goodbye() quit end goodbye on idle if beepCycle is greater than 0 then if beepCycle is not 2 then beep numberOfBeeps --skip second cycle set beepCycle to beepCycle - 1 else goodbye() end if return 5 end idle on run set numberOfBeeps to 2 set beepCycle to 4 idle --more code here, if needed end run
- It may sometimes be convenient to call a script application’s run handler from the idle handler. The run handler would first run its initialization code, then the idle handler would automatically be called by the system (or you could call it explicitly). The idle handler would simply call the run handler again. A flag must be used to tell the run handler to skip the initialization code after it is run the first time (this works only if the flag is declared as a pre-initialized property; don’t forget to reset it again in a quit handler if this is needed to ensure that the application will function correctly the next time it is launched).
property initialized : false on idle run return 3 end idle on run if initialized then beep else set initialized to true end if end run on quit set initialized to false continue quit end quit
- An efficient way to code a “wait loop” in AppleScript is to use the idle handler. This is useful, for example, when launching an application that takes a while to become ready to handle Apple events, or when opening a PPP connection via modem that takes some time to open. The CPU is released to run other processes between calls to the idle handler. Even if the interval between calls to the idle handler is as short as one second, the CPU can accomplish a lot.
A common mistake is to overlook the idle handler and instead code a simple repeat loop that counts to a fixed number, testing for the desired condition at each iteration. The CPU then spends most of its time counting and testing, and the other processes will take much longer to complete – some, such as modem connections, may not work at all because of sensitive timing requirements. The speedup in other processes that is accomplished by using the idle handler to construct a wait loop can be astonishing.
In recent versions of Mac OS X, you can use a repeat loop instead of an idle handler if you include a delay command in the repeat loop. A statement like delay 1 gives the CPU plenty of time to do other work.
on idle tell application "Finder" if process "TextEdit" exists then beep activate application "TextEdit" quit me end if end tell return 1 end idle on run launch application "TextEdit" end run
- To set up a more complex application, code a series of if/else if/else tests in the idle handler to select among multiple triggering conditions. This strategy can be used for a progressive process that proceeds in stages depending upon completion of preceding stages and ending with a quit command. Alternatively, it can be used for an unordered, continuous “event loop” that runs different routines when different events are detected. An example of a progressive process is an internet connection script that tells Internet Connect to connect and waits for the connection to succeed, then launches Mail and waits for it to start running, then tells Mail to get the mail and waits until all the mail is retrieved, then processes the mail and tells itself to quit. This example cycles through three stages, then quits itself.
global stage1, stage2 on idle if stage1 then beep 1 --place stage 1 code here set stage1 to false set stage2 to true else if stage2 then beep 2 --place stage 2 code here set stage2 to false else beep 3 quit end if return 3 end idle on run set stage1 to true end run
- A particularly powerful technique is to assign various script objects in turn to a single variable. The idle handler can simply run the variable, which will cause the script object that is currently assigned to the variable to run. The identity of the currently-installed script object can be tested to help control the substitution of new script objects in stages. This example cycles through the three stage objects endlessly until you quit it.
global theScript script Stage1 beep 1 end script script Stage2 beep 2 end script script Stage3 beep 3 end script on idle run script theScript if theScript is Stage1 then set theScript to Stage2 else if theScript is Stage2 then set theScript to Stage3 else set theScript to Stage1 end if return 3 end idle on run set theScript to Stage1 end run