Controlling screen brightness via ACPI

9 minute read

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.