Introduction
In a previous post, I talked about my configuration for managing power (lock, suspend, hibernation, …). It works as expected, meaning it locks the screen after 10min via xautolock
, and auto suspend via systemd logind.conf
and sleep.conf
files.
It is all fine, but usually when I am home I tend to leave the laptop switched on if I know I’m going to reuse it more or less quickly. And usually when I go back to it I don’t like seeing the laptop powered off. When I know I want it to go in sleep or hibernation, either I just unplug it or directly put it to sleep. Normally, software that needs to prevent system lock sends a signal to inhibit the system to shutdown/sleep. Read the freedesktop.org documentation to learn about the 7 inhibitor lock types and what you can do with it. In specific case, I couldn’t really use that so I went another way with systemd.
So what about my requirements? Well it is simple, I want to prevent lock/sleep/hibernation when I the laptop is used at home and the battery is charging.
I’m not at all an expert in systemd (I tried for a long time to avoid distro with systemd), so there might be errors or unnecessary things in this post, but I made it work for me… Let me know if you know how I could improve it :).
Let’s try!
The wrong start
Nota: Don’t copy past code from that section, they don’t work as expected.
To do so, I started creating a systemd service that will run before the sleep related services. To find sleep related services, one can list all available systemd target using this command:
systemctl list-unit-files --type target
In this example, the new service needs to run before the following target:
sleep.target
hibernate.target
hybrid-sleep.target
suspend.target
suspend-then-hibernate.target
Knowing the target, we can create a simple service that will run before any of these sleep related events. I created /etc/systemd/system/inhibitsleep.service
with the following content:
[Unit]
Description=Inhibit suspend when home and plugged to electricity
Before=sleep.target hibernate.target hybrid-sleep.target suspend.target suspend-then-hibernate.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/inhibitsleep.sh
[Install]
RequiredBy=sleep.target hibernate.target hybrid-sleep.target suspend.target suspend-then-hibernate.target
Important part here are:
- the
Before=
in the[Unit]
that tells this service needs to run before the mentioned services - the
RequiredBy=
in the[Install]
that indicates that this service is required by the sleep related services - The
[service]
is of typeoneshot
so the service manager will consider the unit up after the main process exits . TheExecStart=
is just pointing to a simple shell script.
To enable this new service, a simple systemctl enable inhibitsleep.service
will do the trick.
In my head, I would simply need to check if I was connected to my home wifi and if the laptop battery was charging… And I did just that in a small shell script that basically looked like this:
wifi=$(nmcli -t -f device,state,connection d | grep "${ssid}" | wc -l)
battery=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep state | /usr/bin/awk '{print $2}')
if [[ "${wifi}" == "1" && "${battery}" == "charging" ]]; then
/usr/bin/echo "Preventing sleep."
exit 1
else
/usr/bin/echo "Not preventing sleep."
exit 0
fi
What I didn’t know is that when that script is triggered, some other services ran before and wifi was already disconnected before sleep… So that didn’t work at all!
Switching gears
The new idea was:
- Creating a udev rule to run on AC plug that would trigger a shell script that would look at the status (charging or not) as well as the ssid I’m connected to (if any). Based on that, I write a value of the status in a file.
- In the script fired by the systemd service above (
inhibitsleep.service
), check that value and act upon it.
Using this strategy would also simplify the manual trigger to enable/disable sleep (more on that in another post).
Udev rule on AC (un)plug
To run script when AC is (un)plugged, create the file /etc/udev/rules.d/90-ac-on.rules
with the following content:
SUBSYSTEM=="power_supply", ATTR{online}=="1", RUN+="/usr/local/bin/powerSupplyCharging.sh"
SUBSYSTEM=="power_supply", ATTR{online}=="0", RUN+="/usr/local/bin/powerSupplyDischarging.sh"
Then, of course, we need to create both scripts (powerSupplyCharging.sh
and powerSupplyDischarging
)
/usr/local/bin/powerSupplyCharging.sh
:
#!/usr/bin/env bash
ssid="<MySSID>"
wifi=$(/usr/bin/nmcli -t -f device,state,connection d | /usr/bin/grep "${ssid}" | wc -l)
if [[ "${wifi}" == "1" ]]; then
/usr/bin/echo "1" > /tmp/.preventSleep
else
/usr/bin/echo "0" > /tmp/.preventSleep
fi
/usr/local/bin/powerSupplyDischarging.sh
:
#!/usr/bin/env bash
/usr/bin/echo "0" > /tmp/.preventSleep
Then reload udev rules:
sudo udevadm control --reload
Now I have either 0 or 1 in /tmp/.preventSleep
each time to (un)plug the laptop. Now let’s use that to block locking.
Update the script launched by systemd
Update the script launched by systemd (see above) /usr/local/bin/inhibitsleep.sh
to:
#!/usr/bin/env bash
toggle=$(cat /tmp/.preventSleep)
if [[ "${toggle}" == "1" ]]; then
exit 1
else
exit 0
fi
Then enable the systemd service:
sudo systemctl enable inhibitsleep.service
And voilà, it should prevent sleeping when charging and on my home WIFI. Every other situation, the usual happens, meaning that the laptop will go into suspend-then-hibernate mode.
Drawback of the solution
The solution isn’t perfect, though, and the issues I’ve noticed so far are:
- The wifi get disconnected. Only for a short period and after hibernation is prevented, the wifi is connected again. Usually it doesn’t create any issue as the reconnection is fast.
- The screen is still getting locked. This is both a good and a bad thing. Most of the time, I do prefer the automatic screen locking, even home (since now it is easy to unlock i3lock via fingerprint). But sometime I would prefer it didn’t. I have a manual weird way of blocking this that I’ll describe in the next post.
- It also prevent manual sleep / hibernation if in the same set of conditions (in this case, at home and battery charging). I don’t mind because if I manually put the laptop to sleep, I want to make sure I’ve unplugged it anyway, so it is always a good reminder.
Conclusion
That’is for now, you can find the latest version of these scripts are on sr.ht, more specifically the bash scripts, the systemd config and the udev rules.