Keychron K3 Function Keys With Udev Trigger


I wrote previously about how to configure the Keychron K2 / K3 mechanical keyboards) so their function keys work in function key mode (and not the default multimedia mode).

The previous technique uses a one-shot systemd unit to run a command that sets a bit in the keyboard’s /sys device. It works generally well, but my experience has shown that it assumes the keyboard is always present at boot time (when the multi-user systemd target runs). this is not always the case for me because I use the keyboard primarily over Bluetooth; sometimes I turn it off when I’m going to be away for a while / overnight, and the keyboard itself disconnects and sleeps after 10 minutes of inactivity.

Annoyed at having to run the sys command manually every time the keyboard disconnects, I wrote a udev rule to handle this when the keyboard connect or disconnects. This is similar to my old Logitech K380 which uses this to toggle function key settings and uses a udev rule from here.

I first had to determine which parameters to use to write a rule that fires when the device is added. I followed this guide on how to write udev rules, and first obtained the input device for the Keychron keyboard by grepping, with the keyboard connected:

$ grep -ri 'keychron' /sys/bus/hid/devices/*/input/* 2>/dev/null
/sys/bus/hid/devices/0005:05AC:024F.004B/input/input89/uevent:NAME="Keychron K3"
/sys/bus/hid/devices/0005:05AC:024F.004B/input/input89/name:Keychron K3

Next, I checked the attributes for the associated input device thingy:

$ udevadm info -a -p /sys/bus/hid/devices/0005:05AC:024F.004B/input/input89

  looking at device '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.0/bluetooth/hci0/hci0:256/0005:05AC:024F.004B/input/input89':
    KERNEL=="input89"
    SUBSYSTEM=="input"
    DRIVER==""
    ATTR{inhibited}=="0"
    ATTR{properties}=="0"
    ATTR{name}=="Keychron K3"
    ATTR{uniq}=="dc:2c:26:0c:69:7d"
    ATTR{phys}=="dc:41:a9:a1:97:6b"

  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.0/bluetooth/hci0/hci0:256/0005:05AC:024F.004B':
    KERNELS=="0005:05AC:024F.004B"
    SUBSYSTEMS=="hid"
    DRIVERS=="apple"
    ATTRS{country}=="21"

The device itself and its parent provide enough information to write a rule using the “name” attribute and the “DRIVERS” from the hid device. I could have used another parent higher up the tree, but this seemed fine, because apparently the next parent up is either a bluetooth or a USB thing, and I wanted the rule to work whether I connect via either method; so using the hid subsystem’s apple driver which is commeon to both, and the final input device which is unequivocally the Keychron keyboard, works with both.

Assuming one were to want different configurations for multiple connected Keychrons of the same model/name, one could scope the rule by ATTR{phys}.

The resulting udevadm rule can be written to disk like so:


cat <<EOF | sudo tee /etc/udev/rules.d/80-keychron-k3.rules
# Drivers comes from the parent device (hid bluetooth)
# ATTR{name} is from the input subsystem device
ACTION=="add", DRIVERS=="apple", ATTR{name}=="Keychron K3", RUN+="/usr/local/bin/keychron-k3-fnkeys"
EOF

And then the actual script is very simple/unrefined, since it doesn’t depend on any variables that change each time the device is connected (which we might be able to get via udev variables somehow):

#!/bin/bash
# Place this in /usr/local/bin/keychron-k3-fnkeys
# Runs as root so no sudo is needed
echo 0 > /sys/module/hid_apple/parameters/fnmode

remember to make the script executable sudo chmod 755 /usr/local/bin/keychron-k3-fnkeys and it should run from the udev rule upon connecting the Keychron keyboard.