$_ BSDHowTo.ch
How To... Why not..? Scripts RSS logo

How to make extra keys on USB keyboards work

Last update: 2018-08-10


This article show you how you can get the extra multimedia keys working. For this I use only software that is part of the base system - with the exception of the media player mpv.

Hardware setup

The keyboard I use at home is Das Keyboard 4 Ultimate. It has the following extra keys available:

Media controls

Key Description / Action
Moon Sleep / Hibernate / Shutdown
Mute Mute / unmute the speakers
Wheel Volume up / Volume down
|<< Previous track
>/|| Play / Pause
>>| Next track

Get the devices

First, you must find out which USB HID devices are detected by OpenBSD during boot. Use dmesg(8) together with grep(1) to get the list:

$ dmesg | grep uhidev
uhidev0 at uhub2 port 2 configuration 1 interface 0 "Logitech USB Receiver" rev
2.00/12.03 addr 3
uhidev0: iclass 3/1
ukbd0 at uhidev0: 8 variable keys, 6 key codes
uhidev1 at uhub2 port 2 configuration 1 interface 1 "Logitech USB Receiver" rev
2.00/12.03 addr 3
uhidev1: iclass 3/1, 8 report ids
ums0 at uhidev1 reportid 2: 16 buttons, Z and W dir
uhid0 at uhidev1 reportid 3: input=4, output=0, feature=0
uhid1 at uhidev1 reportid 4: input=1, output=0, feature=0
uhid2 at uhidev1 reportid 8: input=1, output=0, feature=0
uhidev2 at uhub2 port 2 configuration 1 interface 2 "Logitech USB Receiver" rev
2.00/12.03 addr 3
uhidev2: iclass 3/0, 33 report ids
uhid3 at uhidev2 reportid 16: input=6, output=6, feature=0
uhid4 at uhidev2 reportid 17: input=19, output=19, feature=0
uhid5 at uhidev2 reportid 32: input=14, output=14, feature=0
uhid6 at uhidev2 reportid 33: input=31, output=31, feature=0
uhidev3 at uhub2 port 4 configuration 1 interface 0 "Metadot - Das Keyboard Das
Keyboard" rev 2.00/1.00 addr 4
uhidev3: iclass 3/1
ukbd1 at uhidev3: 8 variable keys, 6 key codes
uhidev4 at uhub2 port 4 configuration 1 interface 1 "Metadot - Das Keyboard Das
Keyboard" rev 2.00/1.00 addr 4
uhidev4: iclass 3/0, 3 report ids
uhid7 at uhidev4 reportid 1: input=0, output=0, feature=7
uhid8 at uhidev4 reportid 2: input=1, output=0, feature=0
uhid9 at uhidev4 reportid 3: input=3, output=0, feature=0

The uhid(4) devices are the ones you are looking for. In my case the devices uhid8 and uhid9 are relevant because there is no driver attached to these devices. And these devices provide HID descriptors of the type input.

Create extra devices

On a fresh installation of OpenBSD you will find only four uhid(4) devices in /dev/:

$ ls /dev/uhid*
/dev/uhid0 /dev/uhid1 /dev/uhid2 /dev/uhid3

If your dmesg(8) shows more uhid(4) devices than are available in /dev you will have to create the extra devices manually. Those devices are only accessible if a matching device file exists in /dev:

$ cd /dev
$ for i in $(jot -ns " " - 4 9) ; do
> doas ./MAKEDEV uhid$i
> done

Get the key codes and names

The OpenBSD tool usbhidctl(1) is able to read and display the data it receives from uhid(4) devices. The tool will try to translate the received report descriptors into human readable strings.

I use script(1) to collect the output of usbhidctl(1) in a file:

$ script -c "doas usbhidctl -l -f /dev/uhid9" usbkeys.txt
Script started, output file is usbkeys.txt
doas (user@fqdn.example.org) password:

You should define an order to press the keys. The translation doesn’t always give you meaningful names for each key you press. And the amount of data you get for each keypress/keyrelease event can be bigger than you might expect.

When you are done pressing keys you can quit script(1) with ^C. The next step is to filter out the relevant information. Use grep(1) for this task:

^CScript done, output file is usbkeys.txt
$ grep =1 usbkeys.txt

Lucky me, Das Keyboard gives me proper names for the keys I’ve pressed. That must not be the case with all the keyboards out there. My former keyboard Logitech K800 produced some weird output.

Configure the action

You can use the captured data to prepare the config file for usbhidaction(1).

$ grep =1 usbkeys.txt | sed 's/_Control//;s/\./:/;s/=/\ /' > usbhidaction.conf
$ cat usbhidaction.conf
Consumer:Mute 1
Consumer:Scan_Previous_Track 1
Consumer:Play/Pause 1
Consumer:Scan_Next_Track 1
Consumer:Volume_Decrement 1
Consumer:Volume_Increment 1

Now you can the command to execute for each key as a separate line below the key. You can also insert comments beginning with a #. When you have finished the file should look similar to mine:

# Das Keyboard 4 Ultimate

Consumer:Mute 1
        mixerctl outputs.master.mute=toggle

Consumer:Scan_Previous_Track 1
        echo "playlist-prev" > /tmp/mpvctl

Consumer:Play/Pause 1
        echo "keypress p" > /tmp/mpvctl

Consumer:Scan_Next_Track 1
        echo "playlist-next" > /tmp/mpvctl

Consumer:Volume_Decrement 1
        mixerctl outputs.master=-8

Consumer:Volume_Increment 1
        mixerctl outputs.master=+8

To test your configuration you can start usbhidaction(1) manually:

$ doas usbhidaction -c /etc/usbhidaction.conf -f /dev/uhid9

Start it automatically

At the time of writing this, there is no rc.d(8) script for usbhidaction(1) yet. You can use this one:

# $OpenBSD: extrakeys.md,v 1.2 2018/08/05 12:10:43 bruno Exp $


. /etc/rc.d/rc.subr

rc_cmd $1

Save the above lines to /etc/rc.d/usbhidaction and run the following commands:

$ doas chmod 555 /etc/rc.d/usbhidaction
$ doas rcctl enable usbhidaction
$ doas rcctl set usbhidaction flags -c /etc/usbhidaction.conf -f /dev/uhid9

That’s it, folks!

Wait, |<<, >/|| and >>| don’t work for me

OpenBSD provides a central volume control with mixerctl(1). But there is no central control for |<<, >/|| and >>|. If you like it or not, you have to find a way to send the right commands to your favorite media player.

I use mpv as my swiss army knife for multimedia files. It has the ability to connect to a FIFO (aka named pipe) and accept simple text strings as commands from it. You can see the commands above in my usbhidaction.conf.

Next, I need a way to create the FIFO automatically when I log in. For this I add the following line to ~/.xsession:

[[ -p /tmp/mpvctl ]] || mkfifo /tmp/mpvctl

As a last step I a add the following line to ~/.config/mpv/mpv.conf:


Whenever I press |<<, >/|| or >>| usbhidaction(1) will write the matching command to the FIFO and if mpv is running, it will react on it.