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

How to make extra keys on USB keyboards work

Last update: 2021-01-03

Introduction

This article shows 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 moc.

The latest update on this article happened for two reasons: OpenBSD has changed from mixerctl(1) to sndioctl(1) as the preferred tool for controlling the mixer from user space. And I have changed from mpv to moc as my music player ;-)

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
Sleep / Hibernate / Shutdown
🔇 Mute / unmute the speakers
Wheel Volume up / 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
$

Only the user root has read and write access to the newly created devices. Make sure your regular user has read and write access to the device that is used for the special keys:

$ doas chown <user> /dev/uhid9

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 "usbhidctl -l -f /dev/uhid9" usbkeys.txt
Script started, output file is usbkeys.txt

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 key press/key release 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
Consumer_Control.Mute=1
Consumer_Control.Scan_Previous_Track=1
Consumer_Control.Play/Pause=1
Consumer_Control.Scan_Next_Track=1
Consumer_Control.Volume_Decrement=1
Consumer_Control.Volume_Increment=1
$

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
        sndioctl output.mute=!

Consumer:Scan_Previous_Track 1
        /usr/local/bin/mocp -r

Consumer:Play/Pause 1
        /usr/local/bin/mocp -G

Consumer:Scan_Next_Track 1
        /usr/local/bin/mocp -f

Consumer:Volume_Decrement 1
        sndioctl output.level=-0.01

Consumer:Volume_Increment 1
        sndioctl output.level=+0.01

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

$ 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:

#!/bin/ksh
#
# $OpenBSD: usbhidaction,v 1.4 2019/08/25 15:47:51 user Exp $

daemon="/usr/bin/usbhidaction"

. /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 user <user>
$ doas rcctl set usbhidaction flags -c /etc/usbhidaction.conf -f /dev/uhid9

Replace with the login name of the user usbhidaction should run as. That's it, folks!

Wait, |<<, >|| and >>| don't work for me

OpenBSD provides a central volume control with sndioctl(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 moc as my audio player. It can be controlled from the command line by parameters once it is running. You can see the commands above in my usbhidaction.conf.

Whenever I press |<<, >|| or >>| usbhidaction(1) will execute the matching command and if moc is running, it will react on it.

And what about the ☾ key?

This key is not reported on the same uhid(4) device as the other keys on the keyboard. It comes with its own uhid(4) device, usually the one with the lower number, in my example this is /dev/uhid8 instead of /dev/uhid9.