HOWTO: Using multimedia keys from uhid(4) devices

Hello,

People that have keyboard with multimedia keys such as "Play" "Volume up" and so on may encounter problems if these keys are not detected by xev(1).

In fact there are two ways to use these keys, a lot of manufacturers usually set these keys are simple keys with a special code that you can deal with xev(1). Some USB keyboards prefer using HID instead. It's better for some reasons like: you can use these keys without X and without setting anything in your window manager usbhidaction(1) is there for this.

1. Detect your USB (uhid) keyboard

First, check if you really have a uhid(4) device, do not forget to add it to your kernel config!

$ dmesg | grep uhid
Code:
uhid0: <BTC USB Multimedia Keyboard, class 0/0, rev 1.10/1.20, addr 3> on usbus0

Good news, uhid0 is my keyboard!

2. Get the usable keys

Now we need to see which keys are available. Let see on mine.

$ usbhidctl -f /dev/uhid0 -r
Code:
Report descriptor:
Collection page=Generic_Desktop usage=System_Control
Input   size=1 count=1 page=Generic_Desktop usage=System_Power_Down, logical range 0..1
Input   size=1 count=1 page=Generic_Desktop usage=System_Sleep, logical range 0..1
Input   size=1 count=1 page=Generic_Desktop usage=System_Wake_Up, logical range 0..1
End collection
Collection page=Consumer usage=Consumer_Control
Input   size=1 count=1 page=Consumer usage=Volume_Increment, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Volume_Decrement, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Mute, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Search, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Home, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Bookmarks, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Help, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AL_Email_Reader, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Stop, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Scan_Next_Track, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Play/Pause, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Scan_Previous_Track, logical range 0..1
Input   size=1 count=1 page=Consumer usage=Eject, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Back, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Forward, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Stop, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Copy, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Cut, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AL_Consumer_Control_Configuration, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AL_Local_Machine_Browser, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AL_Calculator, logical range 0..1
Input   size=1 count=1 page=Consumer usage=AC_Refresh, logical range 0..1
End collection

... skipping useless microsoft usages ...

Total   input size 0 bytes
Total  output size 0 bytes
Total feature size 0 bytes

My keyboard only have the following buttons :
  • Volume up "Volume_Increment"
  • Volume down "Volume_Decrement"
  • Volume muting "Mute"
  • System sleep "System_Sleep"

The other keys described are not on the keyboard but I guess the manufacturer didn't want to remove then, but no worries it will work.

3. Write a usbhidaction(1) config file

Now it's time to setup a usbhidaction(1) file to associate a shell command to the specified button, the syntax is really easy.

Let's try to add an action for the Volume_Increment input:

Collection page=Consumer(1) usage=Consumer_Control(2)
Input size=1 count=1 page=Consumer(3) usage=Volume_Increment(4), logical range 0..1

There is two way to fill an action, by specifying everything like this:

Code:
# Syntax of usbhidaction file
# Collection page(1):Collection usage(2).input page(4):input usage(3) bounce debounce command

Consumer:Consumer_Control.Consumer:Volume_Increment 1 0 mixer vol +2

Or the simpliest method is to remove (2) and (4) and simply use the following:

Code:
# Simplified syntax

Consumer:Volume_Increment 1 0 mixer vol +2

You must admit, it's really better.

But what are these 1 0 ?

As you can see the input has two logical range logical range 0..1, because it's a button it can has two states: pressed and released. 1 means pressed and 0 means released (I guess).

Thus the usbhidaction(1) waits for the bounce value 1 and the debounce value 0 that simply means the key was pressed and released.

4. Passing arguments when calling the action

Whenever the command is called you can pass any arguments you like just like a shell command see :

Code:
# Syntax with arguments passed when calling the command
# $1 is the mixer device
# $2 is the increment value I want to use

Consumer:Volume_Increment 1 0 mixer -f $1 vol +$2

Now if I run the usbhidaction(1) and give the both arguments it will use them each time I press the key.

$ usbhidaction -d -f /dev/uhid0 -c usbaction.conf /dev/mixer1 2
Code:
Setting the mixer vol from 56:56 to 58:58.
Setting the mixer vol from 58:58 to 60:60.
Setting the mixer vol from 60:60 to 62:62.
Setting the mixer vol from 62:62 to 64:64.

As you can see in this example I passed the /dev/mixer device and the increment value I wanted, then I pressed my volume up button fourth times.

5. Automatically run usbhidaction when the device is found

Now you may guess, using the /dev/uhid0 device is not well, imagine you have a joypad detected as /dev/uhid0 before the keyboard you will need to check which one is the good one. So now we will add a devd(8) appropriate entry to match only our keyboard.

First we need to get the keyboard hid vendor Id and product Id with usbconfig(8)

I know that my keyboard hid is /dev/uhid0 but to know the vendor Id and product Id I need to deal with ugenx.y devices.

[cmd=]$ usbconfig show_ifdrv[/cmd]
Code:
ugen0.1: <EHCI root HUB Intel> at usbus0, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=SAVE
ugen0.1.0: uhub0: <Intel EHCI root HUB, class 9/0, rev 2.00/1.00, addr 1>
ugen1.1: <EHCI root HUB Intel> at usbus1, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=SAVE
ugen1.1.0: uhub1: <Intel EHCI root HUB, class 9/0, rev 2.00/1.00, addr 1>
ugen1.2: <product 0x0020 vendor 0x8087> at usbus1, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=ON
ugen0.2: <product 0x0020 vendor 0x8087> at usbus0, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=SAVE
ugen0.2.0: uhub3: <vendor 0x8087 product 0x0020, class 9/0, rev 2.00/0.00, addr 2>
ugen0.3: <USB Multimedia Keyboard BTC> at usbus0, cfg=0 md=HOST spd=LOW (1.5Mbps) pwr=ON
ugen0.3.0: ukbd0: <BTC USB Multimedia Keyboard, class 0/0, rev 1.10/1.20, addr 3>
ugen0.3.1: uhid0: <BTC USB Multimedia Keyboard, class 0/0, rev 1.10/1.20, addr 3>
ugen0.4: <USB Laser Mouse Logitech> at usbus0, cfg=0 md=HOST spd=LOW (1.5Mbps) pwr=ON
ugen0.4.0: ums0: <Logitech USB Laser Mouse, class 0/0, rev 2.00/31.00, addr 4>
ugen0.5: <EDRClassone vendor 0x0a12> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON
ugen0.5.0: ubt0: <vendor 0x0a12 EDRClassone, class 224/1, rev 2.00/19.58, addr 5>

Now I know that my keyboard hid is located at ugen0.3, directly attached to the same ukbd(4) device.

We need to get the vendor Id and product Id

$ usbconfig -d ugen0.3 dump_device_desc
Code:
ugen0.3: <USB Multimedia Keyboard BTC> at usbus0, cfg=0 md=HOST spd=LOW (1.5Mbps) pwr=ON

  bLength = 0x0012 
  bDescriptorType = 0x0001 
  bcdUSB = 0x0110 
  bDeviceClass = 0x0000 
  bDeviceSubClass = 0x0000 
  bDeviceProtocol = 0x0000 
  bMaxPacketSize0 = 0x0008 
  [I][B]idVendor = 0x046e (1)[/B][/I]
  [I][B]idProduct = 0x55a5 (2)[/B][/I]
  bcdDevice = 0x0120 
  iManufacturer = 0x0001  <BTC>
  iProduct = 0x0002  <USB Multimedia Keyboard>
  iSerialNumber = 0x0000  <no string>
  bNumConfigurations = 0x0001

Now, we need to add the devd.conf(5) entry:

Code:
attach 100 {
        device-name "uhid[0-9]+";                                                           
        match "vendor"  "0x046e"; # (1)
        match "product" "0x55a5"; # (2)

	#
	# Do not add -d flag ! if you do it devd will never detach to background
	# because usbhidaction will run in foreground !
	#
	# Remember you can add arguments after /etc/usbhidaction.conf like
	# action "/usr/bin/usbhidaction -f $device-name -c /etc/usbhidaction.conf /dev/mixer1 2"
	#
        action "/usr/bin/usbhidaction -f $device-name -c /etc/usbhidaction.conf";
};

Not so hard, and of course the usbhidaction(1) config in /etc/usbhidaction.conf like this:

Code:
#
# For Emprex keyboard
#

Consumer:Volume_Increment	1 0 mixer vol +2
Consumer:Volume_Decrement	1 0 mixer vol -2
Consumer:Mute			1 0 mixer vol 0

That's it! Now each time you reboot it will automatically run usbhidaction(1) only for your keyboard hid and not any other uhid(4) devices.

I hope this helps!
 
When I configure usbhidaction on the command line it all works perfectly. Once I add it to devd.conf and reboot my uhid2 for my keyboard is none existent (even though the keyboard works fine), and I get a error when devd starts up that the uhid is missing Consumer:Volume_Increment. I double checked my vendor and product id's in my devd.conf. Does anyone know what could cause this?

Code:
Consumer:Volume_Increment 1 0 /usr/local/bin/vol up
Consumer:Volume_Decrement 1 0 /usr/local/bin/vol down
Consumer:Mute 1 0 /usr/local/bin/vol toggle

Code:
attach 100 {
        device-name "uhid[0-9]+";                                                          
        match "vendor"  "0x04d9";
        match "product" "0x0348";

    #
    # Do not add -d flag ! if you do it devd will never detach to background
    # because usbhidaction will run in foreground !
    #
    # Remember you can add arguments after /etc/usbhidaction.conf like
    # action "/usr/bin/usbhidaction -f $device-name -c /etc/usbhidaction.conf /dev/mixer1 2"
    #
        action "/usr/bin/usbhidaction -f $device-name -c /etc/usbhidaction.conf";
};

Code:
usbconfig -d ugen2.2 dump_device_desc
ugen2.2: <DuckyChannel International Co., Ltd. Ducky Keyboard> at usbus2, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (100mA)

  bLength = 0x0012 
  bDescriptorType = 0x0001 
  bcdUSB = 0x0200 
  bDeviceClass = 0x0000  <Probed by interface class>
  bDeviceSubClass = 0x0000 
  bDeviceProtocol = 0x0000 
  bMaxPacketSize0 = 0x0040 
  idVendor = 0x04d9 
  idProduct = 0x0348 
  bcdDevice = 0x0110 
  iManufacturer = 0x0003  <DuckyChannel International Co., Ltd.>
  iProduct = 0x0001  <Ducky Keyboard>
  iSerialNumber = 0x0000  <no string>
  bNumConfigurations = 0x0001
 
When I ran, usbhidctl -f /dev/uhid0 -r for my keyboard device, nothing specific about keys came up.
Code:
Report descriptor:
Collection type=Application page=Microsoft usage=0x0001
Input   rid=16 pos=0 size=8 count=6 page=Microsoft usage=0x0001 Array, logical range 0..255
Output  rid=16 pos=0 size=8 count=6 page=Microsoft usage=0x0001 Array, logical range 0..255
End collection
Collection type=Application page=Microsoft usage=0x0002
Input   rid=17 pos=0 size=8 count=19 page=Microsoft usage=0x0002 Array, logical range 0..255
Output  rid=17 pos=0 size=8 count=19 page=Microsoft usage=0x0002 Array, logical range 0..255
End collection
Collection type=Application page=Microsoft usage=0x0004
Input   rid=32 pos=0 size=8 count=14 page=Microsoft usage=0x0041 Array, logical range 0..255
Output  rid=32 pos=0 size=8 count=14 page=Microsoft usage=0x0041 Array, logical range 0..255
Input   rid=33 pos=0 size=8 count=31 page=Microsoft usage=0x0042 Array, logical range 0..255
Output  rid=33 pos=0 size=8 count=31 page=Microsoft usage=0x0042 Array, logical range 0..255
End collection
Total   input size 32 bytes
Total  output size 32 bytes
Total feature size 0 bytes
/usr/share/misc/usb_hid_usages shows a lot of inputs for keyboards and other devices for usbhid(3) and usbhidctl(1).
Code:
0x7F    Keyboard Mute
0x80    Keyboard Volume Up
0x81    Keyboard Volume Down
Is there something else for usbhidctl to provide more detail, or a way to make more out of the information it gave me?

I rather use uhid(4) for multimedia keys, because sysutils/uhidd and sysutils/iichid conflict with this base HID driver.



There's xmodmap -pke that shows what seems to be multimedia keys. I'm not sure if this output is for my specific keyboard, or generic keycodes. Thread x11-keyboard.62279.

Here's some output from it:
Code:
keycode 121 = XF86AudioMute NoSymbol XF86AudioMute
keycode 122 = XF86AudioLowerVolume NoSymbol XF86AudioLowerVolume
keycode 123 = XF86AudioRaiseVolume NoSymbol XF86AudioRaiseVolume
keycode 172 = XF86AudioPlay XF86AudioPause XF86AudioPlay XF86AudioPause
I tried inserting <Key key="XF86AudioMute">exec:mixer 0</Key> in .jwmrc for my window manager, but it didn't work. Using a non-multimedia key for this command worked (but this sometimes has two functions).

Xmodmap makes use of ~/.xmodmaprc:
Code:
keycode 121 = XF86AudioMute
keycode 122 = XF86AudioLowerVolume
keycode 123 = XF86AudioRaiseVolume
keycode 172 = XF86AudioPlay
This was loaded with xmodmap ~/.xmodmaprc, but these keys didn't work.

Thread keyboard-with-additional-keys-for-freebsd.48245 has use of xbindkeys. In ~/.xbindkeysrc, I put XF86Audio key codes in, which has a different syntax, and started xbindkeys, but that didn't work. xbindkeys --key from x11/xbindkeys also didn't pick up my multimedia keys.

I found this Thread multimedia-keys.6253 that said that xev didn't know how to find all key codes, but other operating systems could do it. xev doesn't detect my multimedia keys as was mentioned. Also, that thread said that multimedia keys worked on PS/2 keyboards, and generally not USB keyboards.

I tried x11/xkeycaps, but it didn't show access to multimedia keys, and it's easy to make a keyboard dysfunctional, until manually rebooting the computer.

Is there a good port that can find multimedia keys, whether in console or desktop mode?
 
In reply to Sidetone: Did you ever get anywhere with this? I'm guessing you have the same keyboard as me as the output looks identical. I have a Cherry G80-3850, which has the volume control buttons, but nothing sees them and they are completely useless so far.
 
In reply to Sidetone: Did you ever get anywhere with this? I'm guessing you have the same keyboard as me as the output looks identical. I have a Cherry G80-3850,
I haven't tried since then. I'm on FreeBSD 13.0, and now would rather try uhidd and iichid than uhid for multimedia keys (as opposed to what I previously wrote).

Uhid conflicts, but iichid is in base of this release. In a previous release, I enabled iichid, and some keys didn't function the same way all the time, so it had reduced capability.

I was hoping to see someone post about disabling uhid, and enabling iichid or uhidd on FreeBSD 13.0.

My keyboard is a Logitech K360.
 
@ neal and sidetone
Has any of you managed to get those Fn keys recognized on FreeBSD?
I also have a Logitech keyboard and FreeBSD fails to properly identify it or make use of those keys.

For usbhidctl -f /dev/uhid0 -r I get the exact same generic message you posted.
Interestingly, on newest Fedora 36 with Linux 5.18.xx, libinput sees the usb receiver correctly and the Fn/Media buttons work out-of-the-box.
Code:
$ libinput list-devices
Device:           Logitech ERGO K860
Kernel:           /dev/input/event9
Group:            6
Seat:             seat0, default
Capabilities:     keyboard pointer
...

FreeBSD 13.1-RELEASE, however, sees two separate, more generic event providers and the Fn buttons don't work at all:

Code:
$ libinput list-devices
Device:           Logitech USB Receiver, class 0/0, rev 2.00/24.11, addr 5
Kernel:           /dev/input/event10
Group:            3
Seat:             seat0, default
Capabilities:     pointer
...

Device:           Logitech USB Receiver, class 0/0, rev 2.00/24.11, addr 5
Kernel:           /dev/input/event8
Group:            10
Seat:             seat0, default
Capabilities:     keyboard
...

Of course, I can't make use of xev/wev to get Fn keys info, as they are hardware-only, and the procedure specified above ends with the generic usbhidct output.

I suppose Linux may be benefiting from these Logitech drivers: hid-logitech-dj.c
 
arturb & neal: Thread howto-enabling-multimedia-keys-gamepads-joysticks-for-desktop-usbhid.84464

It's solved there, at least if you're using FreeBSD 13.1. It's with enabling a newer system, and it's also for using other HID devices like gamepads. There's in depth detail there. The reference on this thread is part of it.
neal but if you are on FreeBSD 13 try adding this to your /boot/loader.conf and rebooting.
Code:
usbhid_load="YES"
hw.usb.usbhid.enable=1
I can't make use of xev/wev to get Fn keys info
Xev outputs will show up in X after setting up usbhid, specific drivers, and if applicable device permissions. Haven't heard of wev, until now: the description says, it's for Wayland. With that, you can set up your Fn and Multimedia keys.
 
Back
Top