Prevent suspend on lid close with systemd

By default systemd suspends your system when you close the lid (of your laptop). While this default behavior is acceptable in most cases, for me it posed a problem.

This post explains how you can prevent your system from sleeping when closing the lid of your laptop only when connected to AC.

When I work on my laptop and close the lid I like the fact that my system goes to sleep. This is similar behaviour Apple Macbooks have when you close the lid. And I like it.

At work we use docking stations. Many times a day I get up from my desk an have to take my laptop with me. Resulting in many docking/undocking actions a day.

Everytime I dock my laptop I close the lid. Within a few seconds my system goes to sleep and I need to wake it up by pressing the power button on my docking station before I can start working again.

Months go by and I am getting more and more frustrated by these precious time I loose suspending/unsuspending my system. I don’t want to drag in any 3rd party tools/scripts/daemons that can lift this burdon. I like to stick to plain system and a plain systemd and keep my system as clean as possible.

At some point I decided that everytime I dock my laptop I just leave my lid open just a bit, preventing it from going in to sleep mode and allowing me to use that precious time.

This worked for a while but very soon you are being ridiculed by your collegues as the systemd user with his half-closed-lid and everytime they pass by they can’t help giving my laptop a friendly tap-on-the-back, forcing it to go in to sleep mode. I can’t blame them.

The following solution came to mind.

I created the following script /usr/local/bin/suspend-prevent

% cat /usr/local/bin/suspend-prevent
#!/bin/sh

SYSTEMCTL=/usr/bin/systemctl

case "$1" in
    battery)
        echo 'Running on battery. Making sure to remove the inhibit lock'
        $SYSTEMCTL stop suspend-prevent.service
        ;;

    ac)
        echo 'Running on AC. Making sure to add the inhibit lock'
        $SYSTEMCTL start suspend-prevent.service
        ;;

    -h|--help|help)
        echo "Usage: $(basename $0) [battery|ac|--forever]"
        exit 1
        ;;

    --forever)
        /usr/bin/systemd-inhibit --what=handle-lid-switch:sleep --who=$(id -un $(whoami)) --why="Prevent suspend on lid close when on AC" --mode=block /usr/bin/bash -c "while true; do sleep 60; done"
        ;;

    *)
        echo 'Automatically detecting power source...'

        if cat /sys/class/power_supply/AC/online | grep 0 > /dev/null 2>&1
        then
            $0 battery
        else
            $0 ac
        fi

        exit $?
        ;;
esac

exit 0

The script is very easy to understand. By either using command line arguments battery and ac (or by omitting command line arguments) the script starts or stops a systemd service, depending on the current status of the AC adapter.

The systemd service suspend-prevent.service looks like this:

% cat /etc/systemd/system/suspend-prevent.service
[Unit]
Description=Prevent suspend on lid close when on AC

[Service]
Type=simple
ExecStart=/usr/local/bin/suspend-prevent --forever

As you can see it calls the previous script with the --forever command line option. This effectively creates a systemd inhibit lock preventing the system from going to sleep when the lid is closed.

When the suspend-prevent.service is stopped, the inhibit lock is removed.

If combined with the following udev rules:

% cat /etc/udev/rules.d/50-suspend-prevent.rules
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="/usr/local/bin/suspend-prevent battery"
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="/usr/local/bin/suspend-prevent ac"

Everytime you plug your laptop to AC (or docking it) a systemd inhibit lock is set which looks like this:

% systemd-inhibit
     Who: root (UID 0/root, PID 303/systemd-inhibit)
    What: sleep:handle-lid-switch
     Why: Prevent suspend on lid close when on AC
    Mode: block

1 inhibitor listed.

If you undock your laptop (or unplug it from AC) the inhibit lock is removed.

Now I can dock my laptop and close the lid of my laptop without my system going in to sleep mode. And when I am on battery, if I close my lid, I still get the ‘Macbook’ style behaviour and my system is going to sleep. This is exactly the behaviour I was aiming for.

A finishing touch to all this is another systemd service which runs at boot at determines if the laptop is using AC or battery. Depending on the outcome it will set the inhibit lock. I needed this extra service allowing me to dock my laptop while powered off (e.g. when I arrive at work in the morning):

% cat /etc/systemd/system/suspend-prevent-atboot.service 
[Unit]
Description=Determine during boot if inhibit lock needs to be set or not
After=dbus.service

[Service]
ExecStart=/usr/local/bin/suspend-prevent

[Install]
WantedBy=multi-user.target

Interesting reads that gave me this idea:

  • https://bbs.archlinux.org/viewtopic.php?id=171149
  • http://www.robert.orzanna.de/simplistic-powersaving-with-systemd-service-files-and-udev-rules/