What do all these three things share?
Yes, mechanical keyboards.
Intro
I know that the realm of Mechanical Keyboards is a land filled with customization and with a community that is passionate and fervent. Although I do not share the drive most of the people I talked to have, I did enjoy some keyboards I tried from my friends. After two months of doubting and pondering if I should get one or not, I gave in and bought this Keychron Mechanical Keyboard Q6 Max.
This specific model, as many others of the same as of different brands, makes it a selling point of having VIA on top of QMK. In a nutshell, VIA is built on top of QMK to make the configuration process as simple and accessible as possible. With QMK, you flash the device with a new firmware with the update keymap. VIA sits on top of QMK so one doesn't have to constantly build new firmware and manually flash the keyboard with the updated keymap.
For a complete discussion about VIA vs QMK, you can google. This one is quite a clear resource, for instance.
But why am I going to talk about Web standards as well? VIA uses an Electron app so that one can connect to a URL, and configure the keyboard. Sounds super accessible, right? Well, it has a price. You need to flush the new firmware on your device connected via USB or Bluetooth from a Web app.
Lastly, the picture couldn't be complete without a permission issue at one point. We will get there with udev.
First problems
Step one: once the keyboard is connected; let's try to visit VIA app or the custom Keychron launcher app.
One tries to click on the connect device
label and can see the device listed as an available one. It seems to be working. At least the device is listed correctly. If you use Chrome-based browsers. Why?
In order to flush the firmware from an electron app running in your browser you need access to the device on your machine.
Web standard
When I realized one is forced to use Chrome-based browsers as Firefox is not supported, I became puzzled. So, with a swift googling, I realized that only chronium-based browsers support the webhid protocol. But why? Surely this is a standard, I read the page on MDN and the fact that is labeled as experimental said nothing to me as I know that Mozilla does work to include also experimental features. So, what's going on there? Is it really a standard?
Engineers at Google made a draft document for webhid and implemented it in blink. Other browser makers, including Mozilla, were either not involved or had divergent opinions. Mozilla, for instance, had a negative opinion of the proposed interface. If you like it, you can read the whole discussion (with some drama unfolding) on this issue.
Wherever you stand on this, I think it is a good case study and it reveals a lot about Google and Mozilla. Personally, I do not like forcing an interface as a standard.
Plus, praise for Mozilla, for having a nice open space to discuss standards: https://mozilla.github.io/standards-positions/.
Udev and permissions
So, let's for once use Chrome. One tries to connect and ...drum roll...nothing. Or better, a generic error.
A very quick search based on the keyboard brand and model showed that I needed to play with the settings in Via as Keychron was providing by default a V2 configuration not compatible with the current VIA. Some details here: https://www.reddit.com/r/Keychron/comments/13nmnph/received_invalid_protocol_version_from_device_and/
I think that varies on a case-by-case basis. Some older production batches may have the old configuration installed. The newer ones have the V3. There is also no reference to V2 on their website.
Anyhow, if VIA does not list the device is good to go and check on the producer's website. For Keychron, if this is your case, you have to download the config json from their website: Q6 Max model
So I checked the logs. A look at chrome://device-log/ showed that we lack the right permissions to access the device.
[20:33:39] Failed to open '/dev/hidraw4': FILE_ERROR_ACCESS_DENIED
Obviously, I did not have a specific udev rule for hid devices.
Udev rules and investigation
You need to set up udev so that your devices have the right permissions. Also stated here: https://github.com/the-via/app/issues/91
I have made a new rule file /etc/udev/rules.d/42-usb-keyboard.conf
cat /etc/udev/rules.d/42-usb-keyboard.conf
SUBSYSTEM=="hidraw*", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
Then you can use either the ad-hoc Keychonr launcher at https://launcher.keychron.com/#/light or the lovely via app. https://usevia.app/
In case of troubles, you can check with a few commands what is going on.
Udev allows you to chec the information on a device basis.
udevadm info /dev/hidraw2
P: /devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1/0003:3434:0861.000A/hidraw/hidraw2
M: hidraw2
R: 2
U: hidraw
D: c 245:2
N: hidraw2
L: 0
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1/0003:3434:0861.000A/hidraw/hidraw2
E: DEVNAME=/dev/hidraw2
E: MAJOR=245
E: MINOR=2
E: SUBSYSTEM=hidraw
E: USEC_INITIALIZED=56512670433
E: ID_PATH_WITH_USB_REVISION=pci-0000:00:14.0-usbv2-0:2:1.1
E: ID_PATH=pci-0000:00:14.0-usb-0:2:1.1
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_2_1_1
E: ID_FOR_SEAT=hidraw-pci-0000_00_14_0-usb-0_2_1_1
E: TAGS=:udev-acl:uaccess:seat:
E: CURRENT_TAGS=:udev-acl:uaccess:seat:
And if you need to debug your rule, nothing more simple that running the test command. Of course, make sure you hare re-loaded the rules first.
udevadm control --reload-rules
Test your device:
$ udevadm test /dev/hidraw2
This program is for debugging only, it does not run any program
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.
Trying to open "/etc/systemd/hwdb/hwdb.bin"...
Trying to open "/etc/udev/hwdb.bin"...
=== trie on-disk ===
tool version: 254
file size: 12540251 bytes
header size 80 bytes
strings 2639515 bytes
nodes 9900656 bytes
Loading kernel module index.
Failed to read $container of PID 1, ignoring: Permission denied
Found cgroup2 on /sys/fs/cgroup/, full unified hierarchy
Found container virtualization none.
Using default interface naming scheme 'v253'.
Parsed configuration file "/lib/systemd/network/99-default.link"
Created link configuration context.
Reading rules file: /lib/udev/rules.d/10-dm.rules
Reading rules file: /lib/udev/rules.d/11-dm-lvm.rules
Reading rules file: /lib/udev/rules.d/13-dm-disk.rules
Reading rules file: /lib/udev/rules.d/40-gentoo.rules
Reading rules file: /etc/udev/rules.d/42-usb-keyboard.rules
Reading rules file: /lib/udev/rules.d/50-udev-default.rules
Reading rules file: /lib/udev/rules.d/60-autosuspend.rules
Reading rules file: /lib/udev/rules.d/60-block.rules
Reading rules file: /lib/udev/rules.d/60-cdrom_id.rules
Reading rules file: /lib/udev/rules.d/60-dmi-id.rules
Reading rules file: /lib/udev/rules.d/60-drm.rules
Reading rules file: /lib/udev/rules.d/60-evdev.rules
Reading rules file: /lib/udev/rules.d/60-fido-id.rules
Reading rules file: /lib/udev/rules.d/60-infiniband.rules
Reading rules file: /lib/udev/rules.d/60-input-id.rules
Reading rules file: /lib/udev/rules.d/60-persistent-alsa.rules
Reading rules file: /lib/udev/rules.d/60-persistent-input.rules
Reading rules file: /lib/udev/rules.d/60-persistent-storage-tape.rules
Reading rules file: /lib/udev/rules.d/60-persistent-storage.rules
Reading rules file: /lib/udev/rules.d/60-persistent-v4l.rules
Reading rules file: /lib/udev/rules.d/60-sensor.rules
Reading rules file: /lib/udev/rules.d/60-serial.rules
Reading rules file: /lib/udev/rules.d/61-gdm.rules
Reading rules file: /lib/udev/rules.d/61-gnome-settings-daemon-rfkill.rules
Reading rules file: /lib/udev/rules.d/61-mutter.rules
Reading rules file: /lib/udev/rules.d/64-btrfs.rules
Reading rules file: /lib/udev/rules.d/69-cd-sensors.rules
Reading rules file: /lib/udev/rules.d/69-dm-lvm.rules
Reading rules file: /lib/udev/rules.d/70-camera.rules
Reading rules file: /lib/udev/rules.d/70-joystick.rules
Reading rules file: /lib/udev/rules.d/70-libfprint-2.rules
Reading rules file: /lib/udev/rules.d/70-libgphoto2.rules
Reading rules file: /lib/udev/rules.d/70-memory.rules
Reading rules file: /lib/udev/rules.d/70-mouse.rules
Reading rules file: /lib/udev/rules.d/70-power-switch.rules
Reading rules file: /lib/udev/rules.d/70-printers.rules
Reading rules file: /lib/udev/rules.d/70-touchpad.rules
Reading rules file: /lib/udev/rules.d/70-uaccess.rules
Reading rules file: /lib/udev/rules.d/71-seat.rules
Reading rules file: /lib/udev/rules.d/73-seat-late.rules
Reading rules file: /lib/udev/rules.d/75-net-description.rules
Reading rules file: /lib/udev/rules.d/75-probe_mtd.rules
Reading rules file: /lib/udev/rules.d/77-mm-broadmobi-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-cinterion-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-dell-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-dlink-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-ericsson-mbm.rules
Reading rules file: /lib/udev/rules.d/77-mm-fibocom-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-foxconn-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-gosuncn-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-haier-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-huawei-net-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-linktop-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-longcheer-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-mtk-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-nokia-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-qcom-soc.rules
Reading rules file: /lib/udev/rules.d/77-mm-quectel-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-sierra.rules
Reading rules file: /lib/udev/rules.d/77-mm-simtech-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-telit-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-tplink-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-ublox-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-x22x-port-types.rules
Reading rules file: /lib/udev/rules.d/77-mm-zte-port-types.rules
Reading rules file: /lib/udev/rules.d/78-sound-card.rules
Reading rules file: /lib/udev/rules.d/80-docker.rules
Reading rules file: /lib/udev/rules.d/80-drivers.rules
Reading rules file: /lib/udev/rules.d/80-iio-sensor-proxy.rules
Reading rules file: /lib/udev/rules.d/80-libinput-device-groups.rules
Reading rules file: /lib/udev/rules.d/80-mm-candidate.rules
Reading rules file: /lib/udev/rules.d/80-net-setup-link.rules
Reading rules file: /lib/udev/rules.d/80-udisks2.rules
Reading rules file: /lib/udev/rules.d/81-net-dhcp.rules
Reading rules file: /lib/udev/rules.d/84-nm-drivers.rules
Reading rules file: /lib/udev/rules.d/85-nm-unmanaged.rules
Reading rules file: /lib/udev/rules.d/90-fwupd-devices.rules
Reading rules file: /lib/udev/rules.d/90-iocost.rules
Reading rules file: /lib/udev/rules.d/90-libinput-fuzz-override.rules
Reading rules file: /lib/udev/rules.d/90-nm-thunderbolt.rules
Reading rules file: /lib/udev/rules.d/90-pipewire-alsa.rules
Reading rules file: /lib/udev/rules.d/90-pulseaudio.rules
Reading rules file: /lib/udev/rules.d/90-vconsole.rules
Reading rules file: /lib/udev/rules.d/95-cd-devices.rules
Reading rules file: /lib/udev/rules.d/95-dm-notify.rules
Reading rules file: /lib/udev/rules.d/95-upower-hid.rules
Reading rules file: /lib/udev/rules.d/95-upower-wup.rules
Reading rules file: /lib/udev/rules.d/96-e2scrub.rules
Reading rules file: /lib/udev/rules.d/97-hid2hci.rules
Reading rules file: /lib/udev/rules.d/99-fuse.rules
Reading rules file: /lib/udev/rules.d/99-systemd.rules
hidraw2: /etc/udev/rules.d/42-usb-keyboard.rules:1 GROUP 272
hidraw2: /etc/udev/rules.d/42-usb-keyboard.rules:1 MODE 0660
hidraw2: /lib/udev/rules.d/50-udev-default.rules:17 Importing properties from results of builtin command 'hwdb'
hidraw2: hwdb modalias key: "hid:b0003g0001v00003434p00000861"
hidraw2: hwdb modalias key: "usb:v3434p0861d0100dc00dsc00dp00ic03isc00ip00in01"
hidraw2: hwdb modalias key: "usb:v3434p0861:Keychron Q6 Max"
hidraw2: No entry found from hwdb.
hidraw2: /lib/udev/rules.d/50-udev-default.rules:17 Failed to run builtin 'hwdb': No data available
hidraw2: /lib/udev/rules.d/60-fido-id.rules:5 Importing properties from results of 'fido_id'
hidraw2: Starting 'fido_id'
Successfully forked off '(spawn)' as PID 67739.
Skipping PR_SET_MM, as we don't have privileges.
hidraw2: 'fido_id'(err) 'Failed to get current device from environment: Invalid argument'
hidraw2: Process 'fido_id' failed with exit code 1.
hidraw2: /lib/udev/rules.d/60-fido-id.rules:5 Command "fido_id" returned 1 (error), ignoring
hidraw2: /lib/udev/rules.d/71-seat.rules:74 Importing properties from results of builtin command 'path_id'
hidraw2: /lib/udev/rules.d/73-seat-late.rules:16 RUN 'uaccess'
hidraw2: Preserve permissions of /dev/hidraw2, uid=0, gid=272, mode=0660
hidraw2: Failed to create symlink '/dev/char/245:2' to '/dev/hidraw2': Permission denied
hidraw2: Failed to create device symlink '/dev/char/245:2': Permission denied
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1/0003:3434:0861.000A/hidraw/hidraw2
DEVNAME=/dev/hidraw2
MAJOR=245
MINOR=2
SUBSYSTEM=hidraw
ACTION=add
TAGS=:seat:udev-acl:uaccess:
CURRENT_TAGS=:uaccess:udev-acl:seat:
ID_PATH_WITH_USB_REVISION=pci-0000:00:14.0-usbv2-0:2:1.1
ID_PATH=pci-0000:00:14.0-usb-0:2:1.1
ID_PATH_TAG=pci-0000_00_14_0-usb-0_2_1_1
ID_FOR_SEAT=hidraw-pci-0000_00_14_0-usb-0_2_1_1
run: 'uaccess'
Unload kernel module index.
Unloaded link configuration context.
Happy via now. The keyboard can be access and configured (flashed) from the Web app.