Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

A simple iTunes sleep timer

Jerry Stratton, December 23, 2005

I’ve already set up an iTunes alarm to wake me up on working days, giving me music in the morning. It has been working great. It “knew” today was a holiday and let me sleep.

Long ago, I enjoyed listening to music at night before I went to sleep, but always had to turn it off sometime around 3 AM or so when I woke up and the music was still blaring on the radio. Using AppleScript, it is easy to set iTunes to play for only a limited time. Not only that, but AppleScript can go ahead and put the computer to sleep when iTunes is done playing, and it can slowly reduce the volume that iTunes plays at. I’ve found that I need--and want--much softer volumes late at night than I do during the day.

I designed this Sleep Timer to:

  • ask me how long I want iTunes to play
  • set iTunes to the appropriate volume
  • after the halfway point, slowly drop iTunes down to a lower volume level as it plays
  • quietly stop iTunes by dropping the volume rather than abruptly stop the music
  • put the computer to sleep when it’s done

[toggle code]

  • --time, in minutes, before it goes to sleep
  • property outMinutes : 35
  • --time, in seconds, for volume to quickly drop to zero when the time is up
  • property downTime : 15
  • --volumes for nighttime
  • property systemVolume : 50
  • property startVolume : 60
  • property minimumVolume : 35
  • property pmset : "/usr/bin/pmset "
  • --number of volume drops after halfway point
  • property stepCount : 10
  • on run
    • --ask for sleep time in minutes
    • display dialog "How many minutes before sleeping? " default answer outMinutes giving up after 7
    • set outMinutes to the text returned of the result
    • --how long before we start dropping the volume?
    • set outTime to outMinutes * 60 * 0.5
    • --set display to sleep quickly
    • set savedDisplay to sleepDisplaySoon()
    • --set volumes appropriately for sleep
    • set volume output volume systemVolume
    • tell application "iTunes"
      • set sound volume to startVolume
      • if player state is paused or player state is stopped then
        • playpause
      • end if
    • end tell
    • delay outTime
    • --slowly drop volume
    • tell application "iTunes"
      • set currentVolume to sound volume
    • end tell
    • if currentVolume ≥ minimumVolume then
      • set stepTime to outTime / stepCount
      • set stepVolume to (currentVolume - minimumVolume) / stepCount
      • repeat while currentVolume > minimumVolume
        • tell application "iTunes"
          • set sound volume to currentVolume
        • end tell
        • set currentVolume to currentVolume - stepVolume
        • delay stepTime
      • end repeat
    • else
      • --there is something wrong with our volumes, so just delay the remaining time
      • --without slowly dropping volume
      • delay outTime
    • end if
    • --now, drop volume the rest of the way and pause iTunes
    • tell application "iTunes"
      • set topVolume to sound volume
      • repeat with counter from downTime to 1 by -1
        • delay 1
        • set sound volume to topVolume * counter / downTime
      • end repeat
      • playpause
    • end tell
    • --reset display sleep setting to normal
    • if savedDisplay is not false then
      • set powerType to the power of savedDisplay
      • set sleepSetting to the displaysleep of savedDisplay
      • set restoreResponse to do shell script pmset & "force -" & powerType & " displaysleep " & sleepSetting
      • if restoreResponse is not "" then
        • display dialog restoreResponse giving up after 30
      • end if
    • end if
    • --put computer to sleep
    • tell application "System Events"
      • sleep
    • end tell
    • --restore iTunes volume from zero
    • tell application "iTunes"
      • set sound volume to startVolume
    • end tell
  • end run
  • on sleepDisplaySoon()
    • --get the current display sleep settings
    • set origSettings to do shell script pmset & "-g"
    • set settingsList to the paragraphs of origSettings
    • set powerType to ""
    • set displaySleepSetting to ""
    • --go through each line looking for the settings we want
    • repeat with settingLine in settingsList
      • --first, we get the kind of power: AC or battery
      • if powerType is "" then
        • if settingLine ends with "*" then
          • --this is the currently active profile
          • if settingLine begins with "AC Power" then
            • set powerType to "c"
          • else if settingLine begins with "Battery" then
            • set powerType to "b"
          • end if
        • end if
      • else
        • --we have a power type, look for display sleep time
        • if settingLine begins with " displaysleep" then
          • set displaySleepSetting to the second word of settingLine
          • set pmsetCMD to pmset & "force -" & powerType & " displaysleep 1"
          • set displayResponse to do shell script pmsetCMD
          • if displayResponse is not "" then
            • display dialog displayResponse giving up after 6
          • end if
          • return {power:powerType, displaysleep:displaySleepSetting}
        • end if
      • end if
    • end repeat
    • display dialog "Display not slept." giving up after 4
    • return false
  • end sleepDisplaySoon

If you want to use this script, you’ll need to save it as an application. It does not need to stay open.

Sleeping the display

If you are listening to your music while sleeping, you probably don’t want to have your display fully lit. You could just go in to your system preferences and put your display to sleep, right? No, there is no such option: you can put your computer to sleep, but not your display. You could drop your brightness all the way down, but that setting will “stick”: when you awaken your computer in the morning, your display will still be off.

The traditional work-around is to set the display’s sleep time to one minute. The display won’t go to sleep immediately, but it will go to sleep in a minute. Again, though, you’ll have to remember to reset your display’s sleep time to a more reasonable setting the next morning, and every time you use this script.

This is the sort of thing that AppleScript is supposed to be useful for: change the setting automatically, and then change it back automatically. Unfortunately, there is no Applescript means of changing this setting. There is a means that you can use from the command line, so get ready for a real hack.

Most of this AppleScript is readily understandable. Right up until you get to the “on sleepDisplaySoon()”.

What that function does is tell the display to go to sleep as soon as possible--in one minute. It also returns the previous settings, so that they can be reset. But the way it works is using a command line utility called “pmset”.

If you go to the terminal and type “pmset -g” you’ll see your power management settings, of which one is the “displaysleep”. That’s how many minutes until your display blanks out. We’re using “pmset -g” and then just going through it line by line looking for the kind of power (wall power or battery power) and the current displaysleep setting.

The pmset utility can show anybody the settings, but it can normally only change sleep settings when run as root. Here, we are using the “force” option to force it to run anyway; it will change the settings but they won’t hold. They are only in memory, and not saved into the power management settings file. They won’t last over a restart, and they won’t be reflected in the Energy Saver system preference. They will, however, still work: you can put your computer to sleep or (in our case) put the display to sleep.

The function returns the kind of power whose settings we changed, and the displaysleep setting that we just changed. The last thing the script does, Just before we put the computer to sleep, is use pmset to set the displaysleep option back to whatever it was before we set it to one.

December 28, 2005: Mac OS X Server and pmget

In iTunes Sleeper, I wrote that pmget might be used by unprivileged accounts to put your computer to sleep, resulting in a denial-of-service attack if an unprivileged account is hacked.

I’ve done some further testing on Mac OS X Server 10.4.3. As opposed to Mac OS X Client 10.4.3, the server version does not appear to respond to pmget at all from an unprivileged account. Since most servers that have lots of unprivileged users will be Mac OS X Server, pmget does not appear to be a major flaw.

By default, Mac OS X Server is not set to go to full sleep, nor is it set to sleep the disks, so that may be part of the reason it doesn’t affect Server. But Mac OS X Server does not appear to even pay attention to pmget forcing a displaysleep change: by default, the display is set to sleep after 30 minutes, and a “pmset force -c displaysleep 2” from an unprivileged account does not change this.

In fact, it appears that “pmset force” does not apply even from a privileged account. It does apply (to displaysleep, but not to sleep) when using sudo.

Mac OS X Client tests were performed on a June 2005 iMac G5; Mac OS X Server tests were performed on an older grey G4 tower.

  1. <- Long Menus
  2. Simple POV ->