While using Void Linux on my laptop, I wanted some way to trigger scripts as my user when the machine went to sleep and when it resumed. The main goal of this was to be able to lock my machine using i3lock on suspend.

I currently use xss-lock to register i3lock to be called whenever the screensaver is activated with this command (run at session start):

xss-lock -- ~/bin/i3lock-retry -enfi ~/Pictures/lock.png

With xss-lock running, I set my machine to lock automatically after 600 seconds (10 minutes) of inactivity with:

xset s 600

And locking my machine can be done manually by activating the screensaver with:

xset s activate

Note: i3lock-retry is a simple wrapper-script that calls i3lock continuously until it exits successfully (i.e. when the correct password has been entered). I've had some issues with i3lock crashing in the past and I just have this wrapper in place because of paranoia over ensuring my system locks correctly.

Lid Close Event

Initially, to get the machine to lock automatically when I suspended it, I had naively modified the acpi handler for the button/lid/close event (when the laptop lid is closed). I originally had code like this:

/etc/acpi/handler.sh

button/lid)
    case "$3" in
            close)
                # suspend-to-ram
                logger "LID closed, suspending..."
                xset s activate
                sleep 1
                zzz
                ;;

The handler was set up, by default with Void, to call zzz, I just manually added the lines to activate the screensaver and sleep for a second before calling zzz. This "worked" for the most part, but there were some issues.

The most obvious issue (that I didn't think of at first, of course) was that this didn't lock the machine when it was slept, but instead locked the machine when the lid was closed. This means that manually invoking sleep by running the zzz command - or just invoking sleep by any other means than closing the lid - would leave the system unlocked.

To remedy this problem, I looked more closely into the zzz command that came with Void. By reading the zzz(8) manual, I found that the zzz command had the ability to invoke hooks on suspend and resume!

zzz User Hooks

zzz will call any and all executables that live in /etc/zzz.d/suspend and /etc/zzz.d/resume when the machine suspends and resumes, respectively. Using this knowledge, I wrote a generic script that will:

  1. Figure out the user currently logged in with an active X session
  2. Determine if they have a user hook installed (script in ~/.onsuspend or ~/.onwakeup)
  3. Call the user script with the correct permissions (using sudo) and DISPLAY environmental variable

The code is on GitHub:

https://github.com/bahamas10/zzz-user-hooks

The code was written specifically for Void Linux but should work on any OS that has or uses the zzz command to suspend.

Once the code is checked out, make can be used to install the hooks:

$ sudo make install
cp user-script /etc/zzz.d
cp hooks/resume/99-user-script /etc/zzz.d/resume
cp hooks/suspend/99-user-script /etc/zzz.d/suspend

This will result in the following layout being created:

$ tree /etc/zzz.d/
/etc/zzz.d/
├── resume
│   └── 99-user-script
├── suspend
│   └── 99-user-script
└── user-script

2 directories, 3 files

With these scripts in place, the following scripts will be run for the currently logged in user:

  • ~/.onsuspend - called before the machine is suspended
  • ~/.onresume - called when the machine wakes up

The above scripts will be called with the permissions of the user for whom they are being called (using sudo -Hu <user>). The DISPLAY environmental variable will be set to the currently active display. The timeout command will be used to ensure the user scripts don't take any longer than 5 seconds so they cannot hang the system indefinitely.

Example

I use the following scripts:

~/.onsuspend

#!/usr/bin/env bash

# activate the screensaver (lock)
xset s activate
sleep 1

~/.onresume

#!/usr/bin/env bash

# turn on the keyboard backlight
~/bin/keyboard dim

# ensure the output is set to the laptop display
~/bin/monitor laptop

keyboard and monitor are scripts in my dotfiles.

User scripts being called when using the zzz command directly:

$ sudo zzz
Zzzz... [user-script] called Sun Sep 23 11:33:59 EDT 2018
[user-script] running /home/dave/.onsuspend for user dave (DISPLAY=:0)
[user-script] ran /home/dave/.onsuspend for user dave, exited 0
[user-script] called Sun Sep 23 11:34:05 EDT 2018
[user-script] running /home/dave/.onresume for user dave (DISPLAY=:0)
[user-script] ran /home/dave/.onresume for user dave, exited 0
yawn.

Note that any output generated by the user scripts will show up in the same location as the zzz logs.

User scripts being called when using acpid to trigger zzz when the laptop lid is closed:

$ less /tmp/sv/log/acpid/current
2018-09-27_06:04:11.08679 rule from /etc/acpi/events/anything matched
2018-09-27_06:04:11.08723 executing action "/etc/acpi/handler.sh button/lid LID close"
2018-09-27_06:04:11.13167 Zzzz... [user-script] called Thu Sep 27 06:04:11 UTC 2018
2018-09-27_06:04:11.14506 [user-script] running /home/dave/.onsuspend for user dave (DISPLAY=:0)
2018-09-27_06:04:12.16758 [user-script] ran /home/dave/.onsuspend for user dave, exited 0
2018-09-28_00:57:20.98021 [user-script] called Fri Sep 28 00:57:20 UTC 2018
2018-09-28_00:57:20.98195 [user-script] running /home/dave/.onresume for user dave (DISPLAY=:0)
2018-09-28_00:57:20.99676 [user-script] ran /home/dave/.onresume for user dave, exited 0
2018-09-28_00:57:20.99684 yawn.
2018-09-28_00:57:20.99712 action exited with status 0
2018-09-28_00:57:20.99713 3 total rules matched
2018-09-28_00:57:20.99713 completed input layer event "button/lid LID close"