Pinebook Pro Keyboard Shenanigans
I'm not the fastest typer, and I don't really use 10 fingers- I tend to use 7-8, but in general, I try to minimize the amount of keypresses that I make. This means that I use shortcuts and dedicated keys as much as I can. One example of this (that involves the delete key) is how I press delete instead of right arrow and backspace.
And ever since I got my Pinebook Pro, I felt the distinct lack of a delete key.
What's worse was the fact that in place of a delete key was a power key, one that, once tapped, depending on the DE either showed a power menu, or shut off the PBP:
One of the first things after I installed Manjaro ARM was disabling the power button's system shutdown effects in /etc/systemd/logind.conf
, by setting HandlePowerKey=ignore
(and restarting systemd-logind
, which fyi kills X session)
Later on, to actually get it to work as a delete key I used something I did long ago, and just got the keycode from xev and set it with xmodmap to delete.
This wasn't perfect by any means, it had some delay, and some software like gimp just ignored it (which made image editing a lot more painful).
Then the project working on improving the keyboard and touchpad ended up releasing an update, one that allowed people to make their own keymappings.
I saw this while at work, put a note for myself:
I've been meaning to put aside some time to try and implement this behavior in the firmware itself, but I just couldn't find the time or the energy.
Until today.
The setup
I don't have much of a story to tell tbh. I cloned the repo, downloaded the requirements, compiled the tools.
I compiled and installed firmware/src/keymaps/default_iso.h
(by following instructions on firmware/src/README.md
) just to see if it works or not, it did, so I continued on.
After setting up this new firmware, I did notice that some functionality worked differently though, such as:
- numlock didn't turn ha3f 6f the 2eyb6ard 5nt6 a n40-ad (numlock didn't turn half of the keyboard into a numpad), but simply allowed the numpad area on the keyboard to be used with fn keys, which is a much better way of doing things.
- Fn+F3 no longer pressed p. p.
- Keyboard/Touchpad name changed from the actual part name to “Pine64 Pinebook Pro”, breaking my
xinput set-prop
settings. Simply renaming the device on the commands fixed this. - Sleep button combination (Fn+Esc) did not work (I don't use this combination, but the fact that it had the image on the keyboard and worked prior to the flashing bothered me).
The tinkering
I copied the file default_iso.h
to ave_iso.h
, trying to figure out how it's structured. I tried to find the power button, and I couldn't find it.
There was this vague keyboard shaped array with key mappings, and I did get how one half of them worked, but I couldn't understand how the other half did:
Well, I dug in the codebase for a couple hours, trying to figure out everything, and it finally made sense.
FR, FS and FK are just arrays that are mapped to fns_regular
, fns_special
and fns_keypad
arrays in the same file respectively. This is all explained on the firmware/src/README.md
.
The number (such as 6 on FR(6)
) given as argument is the index from said array.
An example entry of REG_FN(KC_Z, KC_NUBS)
means that default action is KC_Z
, while action when Fn is held down is KC_NUBS
.
KC
means keycode, and they're mapped in firmware/src/include/keycodes.h
. Do note that not all descriptions are correct in practice though, one example is that KC_SYSTEM_POWER
says 0xA5
, but 0xA5
is actually used for brightness up (I explain why this is the case later).
The R()
function used on rest of the keyboard are “Regular” keys, ones that have no actions with Fn. They're directly passed on to their KC_
versions.
If you hate yourself, you can also supply regular integers in place of any of the aforementioned functions and anywhere where you see a KC_
, and this did help when I was trying to understand how things work.
FK is only able to be used with Fn keys when numlock is open. I'm not exactly sure what the difference of FR and FS are outside of semantics. (Looking at my own PR, I regret using FR instead of FS as I'm not fitting the semantics properly. Functionality seems the same though.)
I ended up implementing the sleep button combination, and I learned a lot about keyboards while trying to figure out how I could even emulate the power button. I have some links that I used during my adventure at the bottom of this article. I sent a PR with that patch and it got merged.
The realization
After asking around on the Pine64 #pinebook channel, I was told by a helpful person that the power button is wired to the SoC directly, and that SoC sends the power key input itself (or rather, that this input was handled by the device tree in the linux kernel and turned into an actual emulated keypress).
Most importantly however, they said that it could be remapped with udev. Now, I had only used udev rules to date,and it got me rather confused as I had no idea how one would remap anything with that. That got me to research how to do that, and I learned about a tool that I never used before: evtest
.
And sure enough, I found it:
Upon picking gpio-key-power
and hitting the key, I immediately saw the keypress (this image was taken after the change, so it says KEY_DELETE
, before the change it used to say KEY_POWER
):
Upon more research, I learned how to write hwdb
entries in udev, not rules
. Similarly, I found an already existing hwdb file in /etc/udev/hwdb.d/10-usb-kbd.hwdb
, which explained why the KC_SYSTEM_POWER
key was mapped to brightness up: Because the hwdb was set up this way. For reference, here's what it looks like:
evdev:input:b0003v258Ap001E*
KEYBOARD_KEY_700a5=brightnessdown
KEYBOARD_KEY_700a6=brightnessup
KEYBOARD_KEY_70066=sleep
This also explained to me why KC_POWER
caused a sleep action and not a power key action when done through the builtin keyboard (but not through the dedicated power button).
The ending
I quickly wrote a hwdb file myself on /etc/udev/hwdb.d/20-power-button.hwdb
:
evdev:name:gpio-key-power*
KEYBOARD_KEY_0=delete
And upon recreating the hwdb file with # systemd-hwdb update
and triggering the hwdb with # udevadm trigger /dev/input/event2
, the power button started working as a proper delete key.
evtest
saw it as KEY_DELETE
, the delay when tapping it rapidly vanished, and stuff like gimp started to acknowledge it. Now I just need to avoid holding it down.
Handy resources
- Detailed definitions for the keycodes: https://github.com/qmk/qmk_firmware/blob/master/docs/keycodes.md
- Explanation of the difference between KCPOWER and KCSYSTEMPOWER (aka KCPWR) (was helpful as I was confused as to why they were different): https://github.com/qmk/qmk_firmware/issues/1994
- More keycode definitions: http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc
- A question from askubuntu that made me realize that udev hwdbs exist: https://askubuntu.com/questions/877404/how-to-remap-xf86sleep-key-to-space-xmodmap-xkbcomp-udev-fail
- A blog post that was essential to me figuring out how to use hwdbs: http://who-t.blogspot.com/2019/02/adding-entries-to-udev-hwdb.html