i3 & XF86MonBrightness

The i3 tiling manager reignited my interest in desktop environments. The ability to manipulate tiles with customizable key combinations was a clear productivity win. The eye candy from /r/unixporn and the ricing posts on 0x00sec were enough to reduce my anxiety about changing my daily workflow. With an increase in customizations, there is some additional complexity. Media and screen brightness keys that worked in other desktop environments seemed to be going to /dev/null. Their functionality was also inconsistent across laptop hardware. There are various solutions to fixing screen brightness keys in i3, most of which are available in this stack exchange post. YMMV, but these never worked well; some don't actually adjust the backlight. So, I need to write some code to increase and decrease the backlight. Is performing those adjustments as easy as updating a file like everything else in unix? Yes it is, with some caveats. In i3, you can specify key bindings with bindsym in ~/.config/i3/config. This means if we know which key symbol is controlling the backlight, we can map it to run our program when the key is pressed! The link referenced hints to some useful commands to interrogate our keys, xmodmap and xev.

Interrogating Keys

Using xmodmap, we can get the keymap table, telling us which keycodes are mapped to which key symbols. grep for the XF86Mon keys to see which keycode they correspond to, if anything. $ xmodmap -pke | grep XF86Mon keycode 232 = XF86MonBrightnessDown NoSymbol XF86MonBrightnessDown keycode 233 = XF86MonBrightnessUp NoSymbol XF86MonBrightnessUp keycode 251 = XF86MonBrightnessCycle NoSymbol XF86MonBrightnessCycle Which keys are those though? They are more than likely the keys that look like a dimming and brightening sun or lightbulb on your keyboard, but lets verify using xev. xev will spawn a window that will ask the X server to send it events; if you move the mouse or type in the window, you'll see these events in the console. I'm only inteterested in the screen brightness controls, but when hitting those keys I don't see their respective keycodes in the console. KeymapNotify event, serial 34, synthetic NO, window 0x0, keys: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 FocusOut event, serial 35, synthetic NO, window 0x2000001, mode NotifyGrab, detail NotifyAncestor FocusOut event, serial 35, synthetic NO, window 0x2000001, mode NotifyUngrab, detail NotifyPointer FocusIn event, serial 35, synthetic NO, window 0x2000001, mode NotifyUngrab, detail NotifyAncestor KeymapNotify event, serial 35, synthetic NO, window 0x0, keys: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Depending on how your BIOS is configured (fn lock), you may need to use a modifier to activate these keys. With my BIOS set to have the function keys use their secondary function, I had to use Super_L to validate the keycode. With the function keys behaving as thier primary function, fn + Super_L activated the secondary function. Here, you can see the KeyPress and KeyRelease events. KeyPress event, serial 34, synthetic NO, window 0x2000001, root 0x7a2, subw 0x0, time 29492284, (-142,565), root:(822,607), state 0x0, keycode 133 (keysym 0xffeb, Super_L), same_screen YES, XLookupString gives 0 bytes: XmbLookupString gives 0 bytes: XFilterEvent returns: False MappingNotify event, serial 34, synthetic NO, window 0x0, request MappingKeyboard, first_keycode 8, count 248 KeyPress event, serial 34, synthetic NO, window 0x2000001, root 0x7a2, subw 0x0, time 29493797, (-142,565), root:(822,607), state 0x40, keycode 232 (keysym 0x1008ff03, XF86MonBrightnessDown), same_screen YES, XLookupString gives 0 bytes: XmbLookupString gives 0 bytes: XFilterEvent returns: False KeyRelease event, serial 34, synthetic NO, window 0x2000001, root 0x7a2, subw 0x0, time 29493797, (-142,565), root:(822,607), state 0x40, keycode 232 (keysym 0x1008ff03, XF86MonBrightnessDown), same_screen YES, XLookupString gives 0 bytes: XFilterEvent returns: False KeyPress event, serial 35, synthetic NO, window 0x2000001, root 0x7a2, subw 0x0, time 29494499, (-142,565), root:(822,607), state 0x40, keycode 233 (keysym 0x1008ff02, XF86MonBrightnessUp), same_screen YES, XLookupString gives 0 bytes: XmbLookupString gives 0 bytes: XFilterEvent returns: False KeyRelease event, serial 35, synthetic NO, window 0x2000001, root 0x7a2, subw 0x0, time 29494499, (-142,565), root:(822,607), state 0x40, keycode 233 (keysym 0x1008ff02, XF86MonBrightnessUp), same_screen YES, XLookupString gives 0 bytes: XFilterEvent returns: False

Manipulating the backlight

The Dell XPS 9315 exposes via ACPI the backlight through a sysfs directory at /sys/class/backlight/. The brightness and max_brightness files are relevant here. $ ls -l /sys/class/backlight/intel_backlight/ total 0 -r--r--r-- 1 root root 4096 Feb 17 22:32 actual_brightness -rw-r--r-- 1 root root 4096 Feb 20 23:09 bl_power -rw-r--r-- 1 root root 4096 Feb 17 22:32 brightness lrwxrwxrwx 1 root root 0 Feb 17 22:32 device -> ../../card0-eDP-1 -r--r--r-- 1 root root 4096 Feb 17 22:32 max_brightness drwxr-xr-x 2 root root 0 Feb 17 22:32 power -r--r--r-- 1 root root 4096 Feb 20 23:09 scale lrwxrwxrwx 1 root root 0 Feb 17 22:32 subsystem -> ../../../../../../../class/backlight -r--r--r-- 1 root root 4096 Feb 17 22:32 type -rw-r--r-- 1 root root 4096 Feb 17 22:31 uevent You can check the brightness and max_brigthness settings by cat'ing the respective files: $ cat /sys/class/backlight/intel_backlight/{brightness,max_brightness} 65762 96000 sysfs files are owned by root:root, with most having o+r available. We can read, but not write. This makes updating the brightness as a non root user difficult. Increasing or decreasing the backlight is straight forward with bash: $ echo $NUM | sudo tee /sys/class/backlight/intel_backlight/brightness Providing my password everytime I want to update the brightness sounds like a poor user experience. Perhaps changing the ownership of the brightness file or the bitmask to permit o+rw would work? Maybe using a udev rule to permit my users group write access or a systemd service to change the permissions on boot? This doesn't feel easily portable, also seems invasive.


Hello setuid

setuid allows programs to run with permissions of the owning user. Have you ever taken a look at the ping utility and wondered how you can send ICMP from a raw socket not as root? setuid is why. Use chmod to enable the setuid bit: $ chmod 4755 /path/to/executable Ensure to change that executable to the user you want the permissions to inherit. There are a couple of things that don't run as expected with the setuid bit; namely scripts like bash or interpreted languages such as Python. Python is always my first stop for dev work. When Python faults, C is next. It's rare I write tools in C but generally elated when I do. It's a great reminder of how much I take for granted in other languages. Working with C feels clean, compact, and when everything is compiled, pure.


Code

The C code is nothing spectacular. It opens a file and writes the specified value to it. The inputs are received from getopt, allowing the user to decrement, increment, or specify a new value directly, as well as to output the new value upon being written. $ backlight -h Usage: backlight [options] Options: -d Decrement brightness by 500 -i Increment brightness by 500 -s value Set brightness to arbitrary value (MAX BRIGHTNESS = 96000) -v Print the new brightness value to stdout read from /sys/class/backlight/intel_backlight/brightness View the code in the code section. Download: [tar] [sha1sum] @nullanvoid