Controlling screen brightness via ACPI
Really, this is just a workaround for an issue I haven’t completely understood. Nevertheless, since I had to work out how I’d handled brightness control via hotkeys and ACPI once before, and I had to do it again in order to get this going on my latest laptop, I thought it best to document the solution/workaround for posterity, if only to save myself time and frustration in the future.
Background
I recently got a new laptop at work (yay!). I installed Debian Jessie (currently stable) however whenever I switched graphics, say when logging out, rebooting or going from X11 to the console, the system would hang (requiring a force reboot) (boo!). My best guess was that graphics drivers weren’t up to date enough for the machine I was trying to install things on (Dell Latitude E5470), therefore, I decided to try installing Debian Stretch (testing, about to go into a development freeze). This version worked stably with graphics changes and thus I continued with the task of getting the system set up to how I like it.
A window manager conundrum
Awesome is a window manager and it’s, well, awesome. Especially if one spends a lot of time on the console and moving to the mouse to do things is a waste of valuable time. Moving from Jessie to Stretch meant that Awesome had also been upgraded from version 3.4 to version 3.5. That doesn’t sound like a big jump, however it actually is, with various changes to the API such that the main configuration file needed to be reworked from scratch. One great thing about Awesome is that it is highly configurable. Unfortunately, I’d thus configured it highly, which meant that the upgrade path from 3.4 to 3.5 involved throwing away all of my old settings and trying to get most of them to work again from scratch. This isn’t such a big deal, it just uses up time, and hey, that’s what weekends are for, right? ;-)
Most hotkey combinations work, but only most
Being able to change sound volume and screen brightness by using the Fn-
hotkey combinations is very handy. Also, not having to use the mouse to go
to a menu to then go to another menu to then double click on settings to
then control screen brightness is definitely a saving in time and annoyance.
Being able to control the screen brightness also means that I can extend my
battery life, something which also comes in useful on my long commutes.
Usually, the screen brightness hotkeys simply work out-of-the-box,
unfortunately this time the screen brightness couldn’t be changed by using
(what on this computer is) Fn-F11
to decrease brightness and Fn-F12
to
increase brightness. Even toggling the keyboard backlight worked
(Fn-F10
), which is odd, since I’ve not been able to get that to go on
other laptops; not that I use it, but hey it might come in handy and gives
further evidence for the fact that the screen brightness hotkeys should be
able to work somehow. The weird thing is that in Gnome the brightness
hotkeys work, so this meant the relevant events were being passed around
and, if they get to the right place, can have the desired effect. Changing
to use Gnome isn’t an option for me (did I mention that Awesome is
awesome?), so some debugging was in order.
The next question was, “if the signals are getting through on Gnome, are
they even getting through to Awesome?”. The way to check this is with
xev
, and after firing this up I found that the keycodes 232 and 233 were
being used for the brightness down and up hotkeys, respectively. This meant
that the key presses were getting through to the X server, so at least that
much is working.
I then realised that I use xmodmap
to change things around on my keyboard
(e.g. Caps Lock is mapped to Compose, some commonly-used accents are mapped
to AltGr, etc.), so maybe xmodmap
is causing the issue? The
XF86MonBrightnessDown
and XF86MonBrightnessUp
events are mapped to
keycodes 232 and 233 in xmodmap
, so at least that’s correct. Restarting
into Gnome and running xmodmap ~/.xmodmap
still meant that the brightness
hotkeys worked. Maybe it’s a combination of Awesome and xmodmap
?
Disabling xmodmap
at Awesome’s startup showed that no, it wasn’t the
combination of Awesome and xmodmap
.
In Awesome I map Mod4-F11
to my screen setup with an external monitor, and
Mod4-F12
to lock the screen, so maybe Awesome is catching the F11
and
F12
key presses and not passing them on? This is when I started to realise
I was clutching at straws trying to work out what the problem was. Well,
disabling the above-mentioned key mappings also didn’t help solve the screen
brightness issue either. Time to come up with other ideas…
Working around an as-yet unsolved problem
After starting to get restless and actually wanting to use the screen brightness hotkeys, I decided to work around the issue instead of solving why it didn’t “just work” (and anyway, there’s more than one way to do it). On a previous laptop I’d had a similar issue and managed to find a workaround there, and part of the reason for this post is me wanting to keep notes for my future self, who will, no doubt, want to solve and/or work around this problem in the future.
My earlier workaround involved capturing the ACPI events and controlling the
screen brightness by echoing the relevant number into the appropriate kernel
parameter which controls the screen brightness. To use ACPI for this one
needs to install the acpi
and acpid
packages:
$ sudo aptitude install acpi acpid
Now the command acpi_listen
should be available and we can use this to
check that the ACPI calls are being intercepted, and to find out the exact
expression for the “brightness down” and “brightness up” events. On my box
this turned out to be these two:
video/brightnessdown BRTDN 00000087 00000000
video/brightnessup BRTUP 00000086 00000000
By checking the output of acpi_listen
also showed me that, yes, the
relevant events are being passed around, it’s just not clear where things
are going wrong so that brightness control doesn’t work out-of-the-box in
Awesome. Strange.
The trick to using ACPI events is to tell the ACPI daemon to keep a eye out
for events matching a given pattern, and if the pattern turns up, to run a
pre-defined action (e.g. a script) based upon the pattern match. Thus I
created two files under /etc/acpi/events/
, one for each event:
$ cat /etc/acpi/events/latitude-brightness-down
# /etc/acpi/events/latitude-brightness-down
event=video/brightnessdown BRTDN 00000087 00000000
action=/etc/acpi/latitude-brightness.sh down
and
$ cat /etc/acpi/events/latitude-brightness-up
# /etc/acpi/events/latitude-brightness-up
event=video/brightnessup BRTUP 00000086 00000000
action=/etc/acpi/latitude-brightness.sh up
The argument to the event
keyword is actually a regular expression to
match ACPI events, however we just want to match one exact string, so
specifying the string directly is sufficient. The action
keyword then
defines the script to run when the event is detected. Note that the script
accepts a down
or up
argument which is then used to control the
brightness change.
The script I used was a modified version of a script to control screen
brightness on Acer systems, which I think I found within the /etc/acpi
directory on a Debian Wheezy system a few years ago (and which provided the
basis for my solution to this problem on a previous laptop), the details of
where I got the script are now unfortunately lost in the mists of time. The
script looks like this, updated to the current situation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ cat /etc/acpi/latitude-brightness.sh
#!/bin/bash
BRIGHTNESSPATH="/sys/class/backlight/intel_backlight"
MAXBRIGHTNESS=$(cat $BRIGHTNESSPATH/max_brightness)
BRIGHTNESS=$(cat $BRIGHTNESSPATH/brightness)
# ensure brightness is in the range 0 .. max_brightness
if [ "$BRIGHTNESS" -gt "$MAXBRIGHTNESS" ]; then
BRIGHTNESS=$MAXBRIGHTNESS
elif [ "$BRIGHTNESS" -lt 0 ]; then
BRIGHTNESS=0
fi
# increment/decrement the brightness by the given percentage
MAXBRIGHTNESS_PERCENT=$(expr $MAXBRIGHTNESS / 100)
INCREMENT_PERCENT=2
INCREMENT=$(expr $INCREMENT_PERCENT \* $MAXBRIGHTNESS_PERCENT)
# handle the "up" and "down" brightness events
if [ "x$1" = "xdown" ]; then
NEWBRIGHTNESS=$(( $BRIGHTNESS - $INCREMENT ))
if [ "$NEWBRIGHTNESS" -ge $INCREMENT ]; then
echo $NEWBRIGHTNESS > $BRIGHTNESSPATH/brightness
else
echo 0 > $BRIGHTNESSPATH/brightness
fi
elif [ "x$1" = "xup" ]; then
NEWBRIGHTNESS=$(( $BRIGHTNESS + $INCREMENT ))
if [ "$NEWBRIGHTNESS" -le "$MAXBRIGHTNESS" ]; then
echo $NEWBRIGHTNESS > $BRIGHTNESSPATH/brightness
else
echo $MAXBRIGHTNESS > $BRIGHTNESSPATH/brightness
fi
else
echo >&2 Unknown argument $1
fi
Note that this script needs to be executable, so make sure to set its executable bit:
$ sudo chmod +x /etc/acpi/latitude-brightness.sh
Although there seems to be a lot going on here, most of it is just logic to
be careful about the brightness value to be set and then to echo this value
to /sys/class/backlight/intel_backlight/brightness
, thus setting the
brightness.
Line 4 extracts the maximum brightness possible as reported by the kernel
(in this case the monitor comes from Intel, hence the intel_backlight
subdirectory; this will be another path on other hardware). Then the
current brightness as reported by the kernel is determined by line 5.
Lines 9-13 make sure that the brightness value is in a sensible range. In other words, it shouldn’t be more than the maximum brightness, or lower than the minimum brightness (0).
We set a brightness increment of 25 here (line 15); this turned out to be a nice value and was fine-grained enough for my purposes. This value will likely be different for other hardware (the brightness range could be much wider or narrower) and of course personal preferences about how large the brightness change should be per key press will differ, hence an increment of 25 most likely won’t always be appropriate.
Lines 18-34 work out if the argument to the script was down
or up
and
calculate the new brightness value from the current value minus (or plus)
the increment. The new brightness value is then set by echoing the value to
the backlight brightness path (lines 20 or 22 for down
and lines 27 or 29
for up
).
Once everything’s all set up we need to restart the ACPI daemon in order that the new rules can be applied and the events detected and handled.
$ sudo service acpid restart
And basically, that’s it.
Conclusion
In the end all that was required was to add three files, install a new daemon and restart it once everything was configured. Most of the hard work was in debugging the problem. After this, it was possible to very easily increase or decrease the monitor brightness via the hotkeys as desired, thus not blinding me in dim light and extending the time until I need to recharge my laptop’s battery. All good!
UPDATE: I got bitten by the problem that the brightness keys stopped working after a BIOS upgrade. By adding the boot parameters
acpi_osi=Linux acpi_backlight=vendor
solved the issue for me. These options need to be added to the
/etc/default/grub
file, specifically the GRUB_CMDLINE_LINUX*
variables
should look like this:
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX="acpi_osi=Linux acpi_backlight=vendor"
After changing this file it’s necessary to run update-grub
in order to
update all of the relevant configuration file so that these options are used
on the next boot.