23

my Dell laptop is subject to this bug with kernel 3.14. As a workaround I wrote a simple script

/usr/bin/brightness-fix:

#!/bin/bash

echo 0 > /sys/class/backlight/intel_backlight/brightnes

(and made executable: chmod +x /usr/bin/brightness-fix)

and a systemd service calling it that is executed at startup:

/etc/systemd/system/brightness-fix.service

[Unit]
Description=Fixes intel backlight control with Kernel 3.14

[Service]
Type=forking
ExecStart=/usr/bin/brightness-fix
TimeoutSec=0
StandardOutput=syslog
#RemainAfterExit=yes
#SysVStartPriority=99

[Install]
WantedBy=multi-user.target

and enabled: systemctl enable /etc/systemd/system/brightness-fix.service

That works like a charm and I can control my display brightness as wanted. The problem comes when the laptop resumes after going to sleep mode (e.g. when closing the laptop lip): brightness control doesn't work anymore unless I manually execute my fisrt script above: /usr/bin/brightness-fix

How can I create another systemd service like mine above to be executed at resume time?

EDIT: According to comments below I have modified my brightness-fix.service like this:

[Unit]
Description=Fixes intel backlight control with Kernel 3.14

[Service]
Type=oneshot
ExecStart=/usr/local/bin/brightness-fix
TimeoutSec=0
StandardOutput=syslog

[Install]
WantedBy=multi-user.target sleep.target

also I have added echo "$1 $2" > /home/luca/br.log to my script to check whether it is actually executed. The script it is actually executed also at resume (post suspend) but it has no effect (backlit is 100% and cannot be changed). I also tried logging $DISPLAY and $USER and, at resume time, they are empty. So my guess is that the script is executed too early when waking up from sleep. Any hint?

8
  • 2
    WantedBy=sleep.target...
    – jasonwryan
    Apr 11, 2014 at 6:37
  • Really?! Is that so simple?! :) Can I add 'sleep.target' to my script above or shall I create a new dedicated systemd service script for it?
    – lviggiani
    Apr 11, 2014 at 6:41
  • ...according to documentation "This option may be used more than once, or a space-separated list of unit names may be given". I'm gonna try now.
    – lviggiani
    Apr 11, 2014 at 6:44
  • you must add it to your existing systemd service file (which, by the way, is not a script; it's a static configuration file). and as a side note, the Filesystem Hierarchy Standard states that the proper place to put scripts you wrote yourself is /usr/local/bin, not /usr/bin. that directory is reserved for the package manager only.
    – strugee
    Apr 11, 2014 at 6:45
  • 2
    I believe using the sleep.target will run the unit when the computer sleeps, rather than when it resumes. See my answer below for a unit file that worked for me with a similar problem.
    – jat255
    Sep 3, 2015 at 15:09

5 Answers 5

31

I know this is an old question, but the following unit file worked for me to run a script upon resume from sleep:

[Unit]
Description=<your description>
After=suspend.target

[Service]
User=root
Type=oneshot
ExecStart=<your script here>
TimeoutSec=0
StandardOutput=syslog

[Install]
WantedBy=suspend.target

I believe it is the After=suspend.target that makes it run on resume, rather than when the computer goes to sleep.

3
  • 6
    Works with After=suspend.target in Unit and WantedBy=multi-user.target sleep.target in Install.
    – Emmanuel
    May 10, 2016 at 17:40
  • I'm using the following units successfully here on Ubuntu 16.04 (elementary Loki). Dec 22, 2017 at 2:51
  • @Emmanuel I don't understand why the WantedBy parameter is not enough. Once I click on the power button to resume the computer, am I not back inside the multi-user.target ? Can you please explain the WantedBy parameter because the man systemd.unit is not clear to me ?
    – SebMa
    Feb 28, 2020 at 13:36
13

As an alternative to writing and enabling a unit file, you can also put a shell script (or a symlink to your script) into /lib/systemd/system-sleep/.

It will be called before sleep/hibernate, and at resume time.

From man systemd-suspend.service :

Immediately before entering system suspend and/or hibernation systemd-suspend.service (and the other mentioned units, respectively) will run all executables in /usr/lib/systemd/system-sleep/ and pass two arguments to them. The first argument will be "pre", the second either "suspend", "hibernate", or "hybrid-sleep" depending on the chosen action. Immediately after leaving system suspend and/or hibernation the same executables are run, but the first argument is now "post". All executables in this directory are executed in parallel, and execution of the action is not continued until all executables have finished.

Test it with this:

#!/bin/sh
## This file (or a link to it) must be in /lib/systemd/system-sleep/

logger -t "test" "\$0=$0, \$1=$1, \$2=$2"
3
  • The man page you link mentions a file placed in /usr/lib but all your examples refer to files under /lib
    – qdii
    Sep 7, 2017 at 23:54
  • 1
    @qdii : it may depend on the distribution and/or version. In Debian 8 Jessie and Ubuntu 16.04, the system-sleep directory appears to be in /lib/systemd/, and /usr/lib/systemd contains other stuff.
    – mivk
    Sep 9, 2017 at 10:11
  • 1
    Just for awareness, this solution is a bit of a hack. Because the manpage also says: "Note that scripts or binaries dropped in /usr/lib/systemd/system-sleep/ are intended for local use only and should be considered hacks."
    – tanius
    Oct 11, 2020 at 22:21
2

Followup to mivk's answer, in which I avoid mucking with a new unit file (see my question here How to react to laptop lid events?). Here's my solution; it's not 100% straightforward (sigh) because the system is not stable when it's coming out of sleep:

On my Fedora 26 box I put a symlink here: /usr/lib/systemd/system-sleep/sleepyhead which points here: /root/bin/sleepyhead, which contains:

#!/bin/sh
## This file (or a link to it) must be in /lib/systemd/system-sleep/

# This is called when the lid is closed, as follows:
# $0=/usr/lib/systemd/system-sleep/sleepyhead, $1=pre, $2=suspend
# ...and when the lid is opened, as follows:
# $0=/usr/lib/systemd/system-sleep/sleepyhead, $1=post, $2=suspend


touch /tmp/sleepyrun
logger -t "sleepyhead" "Start: \$1=$1, \$2=$2"
if [ "$1" = "post" ] ; then
    action="RUN trackpoint"
    bash /root/bin/trackpoint >/tmp/trackpoint-run 2>&1
else
    action="NO ACTION"
fi
logger -t "sleepyhead" "${action}: " "\$1=$1, \$2=$2"

The /root/bin/trackpoint script follows. Note that the first sleep is critical. The device is set up every time the lid is opened, so it doesn't exist at first. If I try to do anything but sleep, the "sleepyhead" script takes a really long time to exit and my pointer will be frozen for at least 60 seconds. Furthermore, note that you cannot put the /root/bin/trackpoint script in the background in sleepyhead, above. If you do, the process will be killed when sleepyhead exits.

#!/bin/bash
# This is /root/bin/trackpoint

echo "Start $0"
date

found=false
dir=""
# dirlist can look like:
# /sys/devices/platform/i8042/serio1/serio25/speed
# /sys/devices/platform/i8042/serio1/serio24/speed
# ...the older one appears to get cleaned a little later.

sleep 1 # If I don't put this in here, my pointer locks up for a really long time...
for i in 1 2 3 4; do
    speedfiles=$(find /sys/devices/platform/i8042 -name speed) # There may be multiple speed files at this point.
    [ -z "$speedfiles" ] && { sleep 1; continue; }
    dirlist=$(dirname $speedfiles)
    printf "Speed file(s) at $(find /sys/devices/platform/i8042 -name speed | tail -1) \n"
    # All this remaking of the path is here because the filenames change with
    # every resume, and what's bigger: 9 or 10? ...Depends if you're
    # lexicographical or numerical. We need to always be numerical.
    largest_number="$(echo $dirlist | tr ' ' '\n' | sed -e 's/.*serio//' | sort -n | tail -1)"
    dir="$(echo $dirlist | tr ' ' '\n' | egrep serio${largest_number}\$ )"
    echo "Dir is $dir number is $largest_number" 
    [ -n "$dir" ] && found=true && break
done
$found || exit 1


date
echo -n 4 > $dir/inertia
echo -n 220 > $dir/sensitivity
echo -n 128 > $dir/speed
date
echo "Done $0"
3
  • 1
    Very nicely organized and documented. I'd give you multiple up votes if I could!
    – MountainX
    Dec 23, 2018 at 17:56
  • The first script says "RUN trackpoint in background" but then runs it in the foreground. I guess that's from before you found out that it cannot be run in the background? Because you also write "you cannot put the … trackpoint script in the background … if you do, the process will be killed when sleepyhead exits."
    – tanius
    Oct 11, 2020 at 22:19
  • Fixed. Sorry for the confusion.
    – Mike S
    Oct 12, 2020 at 20:42
2

Here's a more complete example. Files in /etc/ are prone to getting lost when setting new machines, so this variation stores the config file in your home directory, where you might keep track of it with a dotfile manager.

  1. Create a file named ~/.config/systemd/system/after-suspend.service with contents similar to the following. Adjust the ExecStart= commands for your needs. The file will be run as root.
[Unit]
Description=On Resume
After=suspend.target

[Service]
Type=oneshot
# The leading dash before the paths tells to systemd to tolerate these commands failing
# --no-block makes the commands non-blocking
ExecStart=-/usr/bin/systemctl --no-block restart iwd
ExecStart=-/usr/bin/systemctl --no-block restart bluetooth.target

[Install]
WantedBy=suspend.target
  1. Link the file into systemd, restart systemd to load it, then enable it.
sudo systemctl link ~/.config/systemd/system/after-suspend.service 
sudo systemctl daemon-reload
sudo systemctl enable after-suspend
1
  • As a side note... I tended to use a similar idea (leaving the actual service unit file inside a user's directory) but I wasn't even aware that systemd has a specific place for user-level unit files! Your solution combines the best of both: locating the unit file inside a user's expected location, but running it as root! Thanks for the immensely precious tip. Jun 30 at 9:25
0

Just for the record:

If you need to do something as root, you need to specify the user as well.
And also WantedBy=multi-user.target to have it run automatically on system-startup.

StandardOutput=syslog

is deprecated and can safely be omitted.
StandardOutput=journal is the new default.

Here an example that mutes the volume on startup and on return from standby:

[Unit]
Description=Mute Audio on Startup
After=suspend.target

[Service]
User=root
Type=oneshot
ExecStart=/usr/bin/amixer -D pulse sset Master mute
TimeoutSec=0

[Install]
WantedBy=multi-user.target suspend.target

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .