Hacking volume and brightness status indicators into existence

A while ago I wanted to make a status indicator show up on my laptop's screen whenever I change the volume or brightness. The kind that shows a pretty percentage bar in the middle of the screen. This would come for free if I had a big fancy desktop environment to do it for me, but unfortunately I'm a Cultured Tiling Window Manager User, so instead I had to spend hours of my life reading documentation and writing configuration files and shell scripts until I got it working.

Note: Some scripts and configurations are ommitted because I'm too lazy to make my chaotic dotfiles presentable to the public.

Actually displaying something on the screen is the easy part, because someone's already done it for me. I'm using sway, which is a wayland compositor, so I can use wob to do the displaying (a). wob will read a series of lines into standard input, each of containing a number representing a percentage to display and optionally a descrption of what colors should be used to display it.

(a) wob

Determining what number to send over to wob takes a bit more effort but still isn't too difficult. The needed numbers can be gotten with a program from sutils (a), and from brightnessctl (b).

# volume:
volume -f '%i'
# brightness:
brightnessctl -m -d acpi_video0 | awk -F, '{ print $4 }' | sed 's/.$//'

(a) sutils

(b) brightnessctl

The next step was deciding how to actually run wob. It should ideally be run as a daemon, and there should be a way to get a command piped into it from any other process on the system. That ended up being a bit difficult though. my first attempt was to make a FIFO file and to read that into wob's standard input, with the intention of letting any program open it, write to it, and close it again. This causes a problem though, since if a process closes the FIFO file after writing to it, an EOF is sent, which means once that process is done no others can talk to the wob daemon. So, I wrote a small C program, fifoexec (a), which would run another program, pipe the contents of a FIFO file into it, and re-open that FIFO file if an EOF indicator was reached instead of dying. This worked exactly as intended.

rm -f "$fifo_path"
mkfifo "$fifo_path"
exec fifoexec "$fifo_path"

(a) fifoexec

Now, anyone can just echo percentage > "$fifo_path" and it'll show up on the screen. I wouldn't be suprised if I made this much more complicated than it needed to be.

The final step should be pretty easy in theory. Take the keybindings the window manager has already been told to use to adjust volume and brightness, and additionally tell wob what the new values are when these keybindings are triggered. And exactly that was done for volume, which worked as expected! The brightness keybindings, however... did not exist. They showed up nowhere in the window manager configuration file. Through some sort of black magic, they instead simply worked. After a bit of searching I found the real reason: my thinkpad t400 uses the Linux kernel module "thinkpad_acpi", which handles the backlight keybindings, among other things, and also tells me I shouldn't mess with them (a):

Don't mess with the brightness hotkeys in a Thinkpad. If you want notifications for OSD, use the sysfs backlight class event support.

The driver will issue KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN events automatically for the cases were userspace has to do something to implement brightness changes. When you override these events, you will either fail to handle properly the ThinkPads that require explicit action to change backlight brightness, or the ThinkPads that require that no action be taken to work properly.

(a) thinkpad-acpi documentation

Kernel hackers are scary, so I'll try not to defy their authority. Instead I'll just make a udev rule that talks to wob whenever the brightness is changed:

ACTION=="change", 
SUBSYSTEM=="backlight", 
RUN+="/usr/local/bin/wob_backlight"

And that just about does it! I'm still suprised at how resilient the setup managed to be. I don't think it's broken once so far. I had fun with it!