XKB Input Flow
You can use xkb with just 5 concepts thus reducing the complexity from ~20 to 5:
- scancodes (evdev) -> keycodes
- keycodes --(keytype)--> keysymbol (actual character).
- modifiers actions -> keytypes
Useful but not needed: Keycode-aliases, indicators
Scrapped: All .lst, .xml, Rules, Models, Layouts, Variants, Options, virtual modifiers, explicit, implicit, indirect.
It's possible to write a full X11 compatible map with 8 modifiers, 4 groups, 8 levels getting (108) 4 8 = 3456 characters.
Then you only send this map and actually know whats going on - never needing strange overrides, etc. - which feels great.
It took me 4 days, you can likely do it in 1-2.
Just start
apt install libxkbcommon-tools for xkbcli
- wayland only, although X11 compatible map is done here and doable
Read this, do not read blogs: https://xkbcommon.org/doc/current/keymap-text-format-v1-v2.html
0 Hardware stage (output USB HID)
-
A key is pressed on a keyboard.
-
The keyboard translates this key press to a RAW keycode and sends it to the operating system.
- QMK firmware does this. See: https://github.com/qmk/qmk_firmware/blob/master/quantum/keycodes.h
1 Kernel-stage (input USB HID - output Kernel keycode, Kernel key)
-
The operating system uses its input system to read the RAW keycode.
-
The operating system uses its EVent DEVice (evdev) framework to translate the keycode into a key of event type EV_KEY.
-
Debug as root:
evtest- sometimes keyboards expose multiple events like:-
/dev/input/event1: BFO-9000 BFO-9000:
type 4 (EV_MSC), code 4 (MSC_SCAN), value 70004 type 1 (EV_KEY), code 30 (KEY_A), value 1 -
/dev/input/event4: BFO-9000 BFO-9000 Consumer Control:
type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e9 type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 1
Notice the 70000 / c0000 - "USB HID Usage Page" 7 - keyboard (normal keys), c - consumer device (multimedia keys)
value 0 - released key. value 1 - pressed key. value 2 - key repeat
-
-
HID Usage Pages:
https://github.com/torvalds/linux/blob/075b74841bd0065a3bda3440873c747938e69b68/include/linux/hid.h
#define HID_UP_KEYBOARD 0x00070000
#define HID_UP_CONSUMER 0x000c0000
Mapping of USB HID Input to kernel keycode is done via: https://github.com/torvalds/linux/blob/master/drivers/hid/hid-input.c
# line 27
static const unsigned char hid_keyboard[256] = {
0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, # 0x0004 USB == KEY_A 30
50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3,
4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, # 0x0029 USB == KEY_ESC 1
27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
115,114,unk,unk,unk,121,unk, 89, 93,124, 92, 94, 95,unk,unk,unk,
122,123, 90, 91, 85,unk,unk,unk,unk,unk,unk,unk,111,unk,unk,unk,
unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,
unk,unk,unk,unk,unk,unk,179,180,unk,unk,unk,unk,unk,unk,unk,unk,
unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,
unk,unk,unk,unk,unk,unk,unk,unk,111,unk,unk,unk,unk,unk,unk,unk,
29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
150,158,159,128,136,177,178,176,142,152,173,140,unk,unk,unk,unk
};
# line 772
case HID_UP_KEYBOARD:
set_bit(EV_REP, input->evbit);
if ((usage->hid & HID_USAGE) < 256) {
if (!hid_keyboard[usage->hid & HID_USAGE]) goto ignore;
map_key_clear(hid_keyboard[usage->hid & HID_USAGE]);
} else
map_key(KEY_UNKNOWN);
break;
# line 1116
case HID_UP_CONSUMER: /* USB HUT v1.12, pages 75-84 */
switch (usage->hid & HID_USAGE) {
case 0x000: goto ignore;
case 0x030: map_key_clear(KEY_POWER); break;
case 0x031: map_key_clear(KEY_RESTART); break;
...
case 0x22e: map_key_clear(KEY_ZOOMOUT); break;
case 0x22f: map_key_clear(KEY_ZOOMRESET); break;
case 0x232: map_key_clear(KEY_FULL_SCREEN); break;
So you get: The USB Page + hidinput code mapped to a kernel keycode and kernel key
- for
<256 lookup-table is used (hid_keyboard) >=256KEY_constants (from kernel key - kernel keycode map - see below)
kernel key - kernel keycode map:
https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
or
/usr/include/linux/input.h
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
2 libinput-stage (wayland, input Kernel Keycode - output evdev scancode)
https://en.wikipedia.org/wiki/Wayland_(protocol)#libinput
This is the userspace library used by your compositor (Wayland) to talk to evdev devices. So if you set your input devices in sway et. here is the interface.
Small History Lesson
-
Bits 0-7 in X11 are reserved. See: https://tronche.com/gui/x/xlib/input/keyboard-encoding.html
-
In the X11 Protocol Specification, a keycode was defined as a single 8-bit unsigned byte (CARD8), giving a possible range of 0 to 255.
-
First bit (0) means Anykey which hints the GrabKey() function to grab any key. 1-7 are undefined - no explanation.
#define AnyKey 0LKeycode 0 was hardcoded as a wildcard value for functions like XGrabKey(). If a program tells the X server, "Grab Keycode 0 with the Alt modifier," it means "Grab ALL keys as long as Alt is held."
So using 0 a Program can grab all keys and using 8-255 grabs a specific one. This being the 80s i assume they planned on allowing lists of keys (grab all f-keys, modifiers, etc.) or allow special function keys - but never actually agreed on these lists or functions. Look no further fearless traveller as this is the end.
-
A decade later, Linux created its own input subsystem called evdev.
The Linux kernel developers didn't care about X11 protocol rules; they just wanted a clean array starting at the beginning of memory. So Linux assigned its keycodes starting at 1:
KEY_RESERVED = 0 KEY_ESC = 1
-
-
Thus evdev adds 8
-
XKB receives this scancode, "keycodes" translate that to <keycodes>
-
The display system (e.g., X11, Wayland) receives a keymap from libxkbcommon to translate the key into a character.
- This is what we build manually
-
The application uses the character however it sees fit.
!! SKIP this to keep sane and go to KcCGST !!
How xkb builds this map from a magic cookbook called RMLVO
!! SKIP this to keep sane and go to KcCGST !!
2 Layers of maps:
- RMLVO (rules, models, layouts, variants, options)
- KcCGST (keycodes, compat, geometry, symbols, types)
translation of RMLVO to KcCGST:
setxkbmap -layout us -variant -print
RMLVO
Rules define how to compile maps. So this is the first entry - and usually the evdev-ruleset
-
use your own rules/evdev to extend that:
// Include the system (%S is /usr/share/X11/xkb/rules) `evdev` file. ! include %S/evdev // Configure custom symbols. This is configured after including the system // `evdev` file so the changes aren't overridden by the system configuration. // however this won't matter when you use your own identifiers // map ! MLVO identifier to KcCGST // MLVO can be multiple like: ! model layout = symbols // multiple matches combine to + // See https://www.charvolant.org/doug/xkb/html/node5.html ! option = symbols bfo9000:function_keys = +bfo9000(function_keys)
3 XKBs KcCGST (input evdev scancode, output keysym)
keycodes, compat, (depr. geometry), symbols, types
Build your own
Take a template:
xkbcli compile-keymap --layout us > full_us_layout.xkb
[xkb_keycodes]
- A translation of the hardware/evdev **scancodes** from the keyboard into XKB symbolic **keycodes**.
- <KeyC> = 30;
- keycode aliases
- <AliC> = <KeyC>
- Indicator names
[xkb_types]
- Define what a keycode-type (1, 2, or 4 symbols) behaves like when a modifier is used
[xkb_compat]
- implicit modifier definitions and actions (preferred)
- Indicator rules
[xkb_symbols]
- explicit modifier definitions and actions
- A translation of symbolic key codes into actual symbols and actions.
XKB Keycodes
Easy, just record the scancode with evtest (add 8) or wev. You can name XKB keycodes however you like (need be 4 chars though)!
xkb_keycodes "yourcodes" {
// X11 compatible, Wayland can do more - i didnt need to.
minimum = 8;
maximum = 255;
// <keycode> = scancode;
<LatA> = 38;
<LatB> = 56;
<LatC> = 54;
<LatD> = 40;
.
.
.
// all fine with ,.+= etc as chars
<KP.> = 91;
<KP,> = 129;
<KP/> = 106;
<KP*> = 63;
<KP-> = 82;
<KP+> = 86;
<KP=> = 125;
<KPRT> = 104;
};
XKB Types
Each keycode will be mapped to one or more keysym.
xkb_symbols "(unnamed)" {
...
// four levels, FOUR_LEVEL_SEMIALPHABETIC
key <LatE> { [ e , E, EuroSign, cent ] };
...
};
But how?
XKB autoguesses rules:
- 1 keysym:
ONE_LEVEL - 2 keysyms:
- if the two keysyms are letter and the first is lower case and the other
upper case, then
ALPHABETIC; - if one of the keysyms is numpad, then
KEYPADelseTWO_LEVEL.
- if the two keysyms are letter and the first is lower case and the other
upper case, then
- 3 or 4 keysyms (a missing 4th keysym is set to
NoSymbol):- if the first two keysyms are letters and the first is lower case and the
other upper case:
- if the last two keysyms are letters and the first is lower case and the
other upper case then
FOUR_LEVEL_ALPHABETIC; - else
FOUR_LEVEL_SEMIALPHABETIC.
- if the last two keysyms are letters and the first is lower case and the
other upper case then
- if one of the first two keysyms is numpad, then
FOUR_LEVEL_KEYPAD; - else
FOUR_LEVEL.
- if the first two keysyms are letters and the first is lower case and the
other upper case:
Means if you skip Numpad and use key <KP1> { [ KP_1 ] }; etc. you can stay with only 3-5 types.
You can also set types yourselt and assign them to a key (never needed that)
Modifiers
These alter what "level" keysym you get when pressing a key.
Real Modifiers
these are sent to the OS as 8-bit bitmask
$ xmodmap -pm
xmodmap: up to 4 keys per modifier, (keycodes in parentheses):
shift Shift_L (0x32), Shift_R (0x3e)
lock Caps_Lock (0x42)
control Control_L (0x25), Control_R (0x69)
mod1 Alt_L (0x40), Alt_L (0xcc), Meta_L (0xcd)
mod2 Num_Lock (0x4d)
mod3 ISO_Level5_Shift (0xcb) <LVL5>
mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf)
mod5 ISO_Level3_Shift (0x5c)
Virtual Modifiers
These stay in XKB.
https://xkbcommon.org/doc/current/keymap-text-format-v1-v2.html#modifiers-declaration-and-binding
I recommend to start with only real modifiers - super easy:
Modifiers super easy
xkb_compatibility "(unnamed)" {
// These are all keysyms we map below from keycodes
interpret Shift_L { action = SetMods(modifiers=Shift); };
interpret Shift_R { action = SetMods(modifiers=Shift); };
interpret Caps_Lock { action = LockMods(modifiers=Lock); };
interpret Control_L { action = SetMods(modifiers=Control); };
interpret Control_R { action = SetMods(modifiers=Control); };
interpret Alt_L { action = SetMods(modifiers=Mod1); };
interpret Num_Lock { action = LockMods(modifiers=Mod2); };
interpret Scroll_Lock { action = LockMods(modifiers=Mod3); };
interpret Super_L { action = SetMods(modifiers=Mod4); };
interpret Super_R { action = SetMods(modifiers=Mod4); };
interpret ISO_Level3_Shift { action = SetMods(modifiers=Mod5); };
}
xkb_symbols "(unnamed)" {
// "Real" modifiers only, available systemwide
modifier_map Shift { <LFSH>, <RTSH> };
modifier_map Lock { <CAPS> };
modifier_map Control { <LCTL>, <RCTL> };
modifier_map Mod1 { <LALT> };
modifier_map Mod2 { <NMLK> };
modifier_map Mod3 { <SCLK> };
modifier_map Mod4 { <LWIN>, <RWIN> };
modifier_map Mod5 { <RALT> };
.
.
.
// Modifiers keysym //
key <LFSH> { [ Shift_L ] };
key <RTSH> { [ Shift_R ] };
// not on my BFO
key <CAPS> { [ Caps_Lock ] };
key <LCTL> { [ Control_L ] };
key <RCTL> { [ Control_R ] };
// mod1
key <LALT> { [ Alt_L ] };
// mod5 This symbol does nothing in the OS
key <RALT> { [ ISO_Level3_Shift ] };
// mod2
key <NMLK> { [ Num_Lock ] };
// mod3
key <SCLK> { [ Scroll_Lock ] };
// mod4
key <LWIN> { [ Super_L ] };
key <RWIN> { [ Super_R ] };
}
And thats it! Most keysyms get their type detected automagically:
key <LatD> { [ d , D ] }; // TWO_LEVEL
key <LatE> { [ e , E, EuroSign, cent ] }; // FOUR_LEVEL_ALPHABETIC
Now define their behaviour:
xkb_types "(unnamed)" {
type "ONE_LEVEL" {
modifiers= none;
level_name[1]= "Any";
};
type "TWO_LEVEL" {
modifiers= Shift;
map[Shift]= 2;
level_name[1]= "Base";
level_name[2]= "Shift";
};
.
.
.
type "FOUR_LEVEL_ALPHABETIC" {
modifiers= Shift+Lock+Mod5;
map[Shift]= 2;
map[Lock]= 2;
map[Mod5]= 3;
map[Shift+Mod5]= 4;
map[Lock+Mod5]= 4;
map[Shift+Lock+Mod5]= 3; // <-- we all remember this one! /s
level_name[1]= "Base";
level_name[2]= "Shift";
level_name[3]= "AltGr";
level_name[4]= "Shift AltGr";
};
.
.
.
};
Actions
easier to not get wrong when using interpret in compat
https://xkbcommon.org/doc/current/keymap-text-format-v1-v2.html#modifiers-bindings
Symbols
https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h
Test your map
xkbcli compile-keymap --from-xkb < bfo.xkb
My Keymap
Im still playing with groups. Note a switch to Group 2 still maps keys from Group 1 if not defined so experiments are not leaving you with an unusable keyboard.
xkb_keymap {
xkb_keycodes "ortholinear" {
minimum = 8;
maximum = 255;
// Indicators //
indicator 1 = "Caps Lock";
indicator 2 = "Num Lock";
indicator 3 = "Scroll Lock";
indicator 4 = "Compose";
indicator 5 = "Kana";
indicator 6 = "Sleep";
indicator 7 = "Suspend";
indicator 8 = "Mute";
indicator 9 = "Misc";
indicator 10 = "Mail";
indicator 11 = "Charging";
indicator 12 = "Shift Lock";
indicator 13 = "Group 2";
indicator 14 = "Mouse Keys";
// Keycodes //
// Modifiers
// shift
<LFSH> = 50;
<RTSH> = 62;
// lock
<CAPS> = 66;
// control
<LCTL> = 37;
<RCTL> = 105;
// mod1
<LALT> = 64;
// mod5
<RALT> = 108;
// mod2
<NMLK> = 77;
// mod3
<SCLK> = 78;
// mod4
<LWIN> = 133;
<RWIN> = 134;
// not a modN - Context-Menu-key
<COMP> = 135;
// Function Keys
<FK01> = 67;
<FK02> = 68;
<FK03> = 69;
<FK04> = 70;
<FK05> = 71;
<FK06> = 72;
<FK07> = 73;
<FK08> = 74;
<FK09> = 75;
<FK10> = 76;
<FK11> = 95;
<FK12> = 96;
<FK13> = 191;
<FK14> = 192;
<FK15> = 193;
<FK16> = 194;
<FK17> = 195;
<FK18> = 196;
<FK19> = 197;
<FK20> = 198;
<FK21> = 199;
<FK22> = 200;
<FK23> = 201;
<FK24> = 202;
// Numbers
<Ara1> = 10;
<Ara2> = 11;
<Ara3> = 12;
<Ara4> = 13;
<Ara5> = 14;
<Ara6> = 15;
<Ara7> = 16;
<Ara8> = 17;
<Ara9> = 18;
<Ara0> = 19;
// Latin
<LatA> = 38;
<LatB> = 56;
<LatC> = 54;
<LatD> = 40;
<LatE> = 26;
<LatF> = 41;
<LatG> = 42;
<LatH> = 43;
<LatI> = 31;
<LatJ> = 44;
<LatK> = 45;
<LatL> = 46;
<LatM> = 58;
<LatN> = 57;
<LatO> = 32;
<LatP> = 33;
<LatQ> = 24;
<LatR> = 27;
<LatS> = 39;
<LatT> = 28;
<LatU> = 30;
<LatV> = 55;
<LatW> = 25;
<LatX> = 53;
<LatY> = 29;
<LatZ> = 52;
// Stuff
<ESC> = 9;
<BKSP> = 22;
<TAB> = 23;
<RTRN> = 36;
<SPCE> = 65;
// ,./\;
<COMM> = 59;
<PERI> = 60;
<SLSH> = 61;
<BSLS> = 51;
<SEMI> = 47;
// []-=`'
<BRAL> = 34;
<BRAR> = 35;
<MINU> = 20;
<EQUA> = 21;
<GRAV> = 49;
<APOS> = 48;
// Arrows and so
<PRSC> = 107;
<PAUS> = 127;
<HOME> = 110;
<END> = 115;
<PGUP> = 112;
<PGDN> = 117;
<INS> = 118;
<DELE> = 119;
<UP> = 111;
<LEFT> = 113;
<RGHT> = 114;
<DOWN> = 116;
// Numpad
<KP1> = 87;
<KP2> = 88;
<KP3> = 89;
<KP4> = 83;
<KP5> = 84;
<KP6> = 85;
<KP7> = 79;
<KP8> = 80;
<KP9> = 81;
<KP0> = 90;
<KP.> = 91;
<KP,> = 129;
<KP/> = 106;
<KP*> = 63;
<KP-> = 82;
<KP+> = 86;
<KP=> = 125;
<KPRT> = 104;
// Multimedia
<VOL-> = 122;
<VOL+> = 123;
<BRI-> = 232;
<BRI+> = 233;
<PLPA> = 172;
// Aliases //
// Row / Column Aliases Lefthand
alias <LH24> = <LatQ>;
alias <LH25> = <LatW>;
alias <LH26> = <LatF>;
alias <LH27> = <LatP>;
alias <LH28> = <LatB>;
alias <LH29> = <GRAV>;
alias <LH34> = <LatA>;
alias <LH35> = <LatR>;
alias <LH36> = <LatS>;
alias <LH37> = <LatT>;
alias <LH38> = <LatG>;
alias <LH39> = <MINU>;
alias <LH44> = <LatZ>;
alias <LH45> = <LatX>;
alias <LH46> = <LatC>;
alias <LH47> = <LatD>;
alias <LH48> = <LatV>;
alias <LH49> = <BRAL>;
// Row / Column Aliases Righthand
alias <RH22> = <LatJ>;
alias <RH23> = <LatL>;
alias <RH24> = <LatU>;
alias <RH25> = <LatY>;
alias <RH26> = <SEMI>;
alias <RH27> = <BKSP>;
alias <RH32> = <LatM>;
alias <RH33> = <LatN>;
alias <RH34> = <LatE>;
alias <RH35> = <LatI>;
alias <RH36> = <LatO>;
alias <RH37> = <RTRN>;
alias <RH42> = <LatK>;
alias <RH43> = <LatH>;
alias <RH44> = <COMM>;
alias <RH45> = <PERI>;
alias <RH46> = <SLSH>;
alias <RH47> = <BSLS>;
};
xkb_types "(unnamed)" {
type "ONE_LEVEL" {
modifiers= none;
level_name[1]= "Any";
};
type "TWO_LEVEL" {
modifiers= Shift;
map[Shift]= 2;
level_name[1]= "Base";
level_name[2]= "Shift";
};
type "ALPHABETIC" {
modifiers= Shift+Lock;
map[Shift]= 2;
map[Lock]= 2;
level_name[1]= "Base";
level_name[2]= "Caps";
};
type "FOUR_LEVEL" {
modifiers= Shift+Mod5;
map[Shift]= 2;
map[Mod5]= 3;
map[Shift+Mod5]= 4;
level_name[1]= "Base";
level_name[2]= "Shift";
level_name[3]= "AltGr";
level_name[4]= "Shift AltGr";
};
type "FOUR_LEVEL_ALPHABETIC" {
modifiers= Shift+Lock+Mod5;
map[Shift]= 2;
map[Lock]= 2;
map[Mod5]= 3;
map[Shift+Mod5]= 4;
map[Lock+Mod5]= 4;
map[Shift+Lock+Mod5]= 3;
level_name[1]= "Base";
level_name[2]= "Shift";
level_name[3]= "AltGr";
level_name[4]= "Shift AltGr";
};
type "FOUR_LEVEL_SEMIALPHABETIC" {
modifiers= Shift+Lock+Mod5;
map[Shift]= 2;
map[Lock]= 2;
map[Mod5]= 3;
map[Shift+Mod5]= 4;
map[Lock+Mod5]= 3;
preserve[Lock+Mod5]= Lock;
map[Shift+Lock+Mod5]= 4;
preserve[Shift+Lock+Mod5]= Lock;
level_name[1]= "Base";
level_name[2]= "Shift";
level_name[3]= "AltGr";
level_name[4]= "Shift AltGr";
};
type "FOUR_LEVEL_KEYPAD" {
modifiers= Shift+Mod2+Mod5;
map[Shift]= 2;
map[Mod2]= 2;
map[Mod5]= 3;
map[Shift+Mod5]= 4;
map[Mod2+Mod5]= 4;
map[Shift+Mod2+Mod5]= 3;
level_name[1]= "Base";
level_name[2]= "Shift/Numlock";
level_name[3]= "AltGr";
level_name[4]= "Shift/Numlock AltGr";
};
};
xkb_compatibility "(unnamed)" {
// (indirect) virtual Modifier Map //
// i only use indirect because else you need to use explicit actions - no interpret
virtual_modifiers ModSwitchGroup = 0x1000; // 32-bit
// Example to make it use Mod2+Mod3 so you can derive it from real-modifiers
// virtual_modifiers SwitchGroup = Mod2+Mod3;
// Modifier actions //
// interpret (a keysym) is easier not to get wrong
// Alternative on keysym:
// key <LALT> {
// // Explicit virtual modifier map
// virtualModifiers = Alt,
// ...
// };
interpret F17 {
// Bind the virtual modifier…
virtualModifier = ModSwitchGroup;
// … independently of the group and level (default)
useModMap = AnyLevel;
action = LockGroup(group=+1); // cycle groups while on any group - SAFE
// BEWARE this will lock the Group and then you cannot switch back:
// action[Group1] = LockGroup(group=2)
// instead if you want this still, use:
// action[Group1] = LockGroup(group=2)
// action[Group2] = LockGroup(group=1)
// SetGroup also possible (Shift-like)
// action = SetGroup(group=2);
};
// You could map these as explicit actions on the keysym like:
// key <RTSH> { [ Shift_R ], actions[Group1] = [ SetMods(modifiers=Shift) ] };
// but DontRepeatYourself
interpret Shift_L { action = SetMods(modifiers=Shift); };
interpret Shift_R { action = SetMods(modifiers=Shift); };
interpret Caps_Lock { action = LockMods(modifiers=Lock); };
interpret Control_L { action = SetMods(modifiers=Control); };
interpret Control_R { action = SetMods(modifiers=Control); };
interpret Alt_L { action = SetMods(modifiers=Mod1); };
interpret Num_Lock { action = LockMods(modifiers=Mod2); };
interpret Scroll_Lock { action = LockMods(modifiers=Mod3); };
interpret Super_L { action = SetMods(modifiers=Mod4); };
interpret Super_R { action = SetMods(modifiers=Mod4); };
interpret ISO_Level3_Shift { action = SetMods(modifiers=Mod5); };
// Indicators //
indicator "Shift Lock" {
whichModState= locked;
modifiers= Shift;
};
indicator "Caps Lock" {
whichModState= locked;
modifiers= Lock;
};
indicator "Num Lock" {
whichModState= locked;
modifiers= Mod2;
};
indicator "Scroll Lock" {
whichModState= locked;
modifiers= Mod3;
};
indicator "Group 2" {
groups= 0xfe;
};
indicator "Mouse Keys" {
controls= MouseKeys;
};
};
xkb_symbols "(unnamed)" {
name[Group1]="Colemak US Lvl 3 German Umlauts + Math";
name[Group1]="Mooore Math";
// Real Modifiers Map //
// always direct (never indirect in compat)
// i only use direct keycodes
modifier_map Shift { <LFSH>, <RTSH> };
modifier_map Lock { <CAPS> };
modifier_map Control { <LCTL>, <RCTL> };
modifier_map Mod1 { <LALT> };
modifier_map Mod2 { <NMLK> };
modifier_map Mod3 { <SCLK> };
modifier_map Mod4 { <LWIN>, <RWIN> };
modifier_map Mod5 { <RALT> };
// Example to bind indirectly to a keysym not directly to a keycode:
// BUT this involves special rules so i dont do that
// See "Setting the real modifier map" in https://xkbcommon.org/doc/current/keymap-text-format-v1-v2.html#modifiers-bindings
// modifier_map Shift { Shift_L, Shift_R };
// (direct) Virtual Modifiers Map //
// use this if you only want explicit actions
// virtual_modifiers ModSwitchGroup = 0x1000; // 32-bit
// Example to make it use Mod2+Mod3 so you can derive it from real-modifiers
// virtual_modifiers ModSwitchGroup = Mod2+Mod3;
// Modifiers keysym //
key <LFSH> { [ Shift_L ] };
key <RTSH> { [ Shift_R ] };
// not on my BFO
key <CAPS> { [ Caps_Lock ] };
key <LCTL> { [ Control_L ] };
key <RCTL> { [ Control_R ] };
// mod1
key <LALT> { [ Alt_L ] };
// mod5 This symbol does nothing in the OS
key <RALT> { [ ISO_Level3_Shift ] };
// mod2
key <NMLK> { [ Num_Lock ] };
// mod3
key <SCLK> { [ Scroll_Lock ] };
// mod4
key <LWIN> { [ Super_L ] };
key <RWIN> { [ Super_R ] };
key <COMP> { [ Menu ] };
// Symbols //
// Function Keys
// type FOUR_LEVEL
key <FK01> { [ F1, F1, F1, F1 ] };
key <FK02> { [ F2, F2, F2, F2 ] };
key <FK03> { [ F3, F3, F3, F3 ] };
key <FK04> { [ F4, F4, F4, F4 ] };
key <FK05> { [ F5, F5, F5, F5 ] };
key <FK06> { [ F6, F6, F6, F6 ] };
key <FK07> { [ F7, F7, F7, F7 ] };
key <FK08> { [ F8, F8, F8, F8 ] };
key <FK09> { [ F9, F9, F9, F9 ] };
key <FK10> { [ F10, F10, F10, F10 ] };
key <FK11> { [ F11, F11, F11, F11 ] };
key <FK12> { [ F12, F12, F12, F12 ] };
key <FK13> { [ F13, F13, F13, F13 ] };
key <FK14> { [ F14, F14, F14, F14 ] };
key <FK15> { [ F15, F15, F15, F15 ] };
key <FK16> { [ F16, F16, F16, F16 ] };
key <FK17> { [ F17, F17, F17, F17 ] };
// Example for explicit definition of a virtual modifier
// i only use implicit in _compat
// key <FK17> { virtualModifiers = ModSwitchGroup, [ F17, F17, F17, F17 ] };
key <FK18> { [ F18, F18, F18, F18 ] };
key <FK19> { [ F19, F19, F19, F19 ] };
key <FK20> { [ F20, F20, F20, F20 ] };
key <FK21> { [ F21, F21, F21, F21 ] };
key <FK22> { [ F22, F22, F22, F22 ] };
key <FK23> { [ F23, F23, F23, F23 ] };
key <FK24> { [ F24, F24, F24, F24 ] };
// Numbers
// type FOUR_LEVEL
key <Ara1> { [ 1, exclam, onesuperior, exclamdown ] };
key <Ara2> { [ 2, at, twosuperior, squareroot ] };
key <Ara3> { [ 3, numbersign, threesuperior, cuberoot ] };
key <Ara4> { [ 4, dollar, foursuperior, fourthroot ] };
// type TWO_LEVEL
key <Ara5> { [ 5, percent ] };
key <Ara6> { [ 6, asciicircum ] };
key <Ara7> { [ 7, ampersand ] };
key <Ara8> { [ 8, asterisk ] };
key <Ara9> { [ 9, parenleft ] };
key <Ara0> { [ 0, parenright ] };
// Latin
// type TWO_LEVEL - math gets extended via Row/Column LH/RH
key <LatA> { [ a , A, adiaeresis, Adiaeresis ], [ b, B ] };
key <LatB> { [ b , B ] };
key <LatC> { [ c , C ] };
key <LatD> { [ d , D ] };
key <LatE> { [ e , E, EuroSign, cent ] };
key <LatF> { [ f , F ] };
key <LatG> { [ g , G ] };
key <LatH> { [ h , H ] };
key <LatI> { [ i , I ] };
key <LatJ> { [ j , J ] };
key <LatK> { [ k , K ] };
key <LatL> { [ l , L ] };
key <LatM> { [ m , M ] };
key <LatN> { [ n , N ] };
key <LatO> { [ o , O, odiaeresis, Odiaeresis ] };
key <LatP> { [ p , P ] };
key <LatQ> { [ q , Q ] };
key <LatR> { [ r , R ] };
key <LatS> { [ s , S, ssharp ] };
key <LatT> { [ t , T ] };
key <LatU> { [ u , U, udiaeresis, Udiaeresis ] };
key <LatV> { [ v , V ] };
key <LatW> { [ w , W ] };
key <LatX> { [ x , X ] };
key <LatY> { [ y , Y ] };
key <LatZ> { [ z , Z ] };
// Type ONE_LEVEL
key <ESC> { [ Escape ] };
// maybe?
key <BKSP> { [ BackSpace, BackSpace ] };
key <TAB> { [ Tab, ISO_Left_Tab ] };
key <RTRN> { [ Return ] };
key <SPCE> { [ space ] };
// ,./\;
key <COMM> { [ comma, less ] };
key <PERI> { [ period, greater ] };
key <SLSH> { [ slash, question ] };
key <BSLS> { [ backslash, bar ] };
key <SEMI> { [ semicolon, colon ] };
// []-=`'
key <BRAL> { [ bracketleft, braceleft ] };
key <BRAR> { [ bracketright, braceright] };
key <MINU> { [ minus, underscore] };
key <EQUA> { [ equal, plus] };
key <GRAV> { [ grave, asciitilde] };
key <APOS> { [ apostrophe, quotedbl] };
// Arrows and so
key <PRSC> { [ Print, Sys_Req ] };
key <PAUS> { [ Pause, Break ] };
key <HOME> { [ Home ] };
key <END> { [ End ] };
key <PGUP> { [ Prior ] };
key <PGDN> { [ Next ] };
key <INS> { [ Insert ] };
key <DELE> { [ Delete ] };
key <UP> { [ Up ] };
key <LEFT> { [ Left ] };
key <RGHT> { [ Right ] };
key <DOWN> { [ Down ] };
// Numpad
key <KP1> { [ KP_1 ] };
key <KP2> { [ KP_2 ] };
key <KP3> { [ KP_3 ] };
key <KP4> { [ KP_4 ] };
key <KP5> { [ KP_5 ] };
key <KP6> { [ KP_6 ] };
key <KP7> { [ KP_7 ] };
key <KP8> { [ KP_8 ] };
key <KP9> { [ KP_9 ] };
key <KP0> { [ KP_0 ] };
key <KP.> { [ KP_Decimal ] };
key <KP,> { [ KP_Separator ] };
key <KP/> { [ KP_Divide ] };
key <KP*> { [ KP_Multiply ] };
key <KP-> { [ KP_Subtract ] };
key <KP+> { [ KP_Add ] };
key <KP=> { [ KP_Equal ] };
key <KPRT> { [ KP_Enter ] };
// Multimedia
key <VOL-> { [ XF86AudioLowerVolume ] };
key <VOL+> { [ XF86AudioRaiseVolume ] };
key <BRI-> { [ XF86MonBrightnessDown ] };
key <BRI+> { [ XF86MonBrightnessUp ] };
key <PLPA> { [ XF86AudioPlay, XF86AudioPause ] };
// Maths
};
};
Actually use it:
sway config, use output of swaymsg -t get_inputs
# Keebio BFO-9000
input "51984:4457:Keebio_BFO-9000" {
xkb_file ~/.config/xkb/bfo.xkb
repeat_delay 200
repeat_rate 60
}
input "51984:4457:Keebio_BFO-9000_Consumer_Control" {
xkb_file ~/.config/xkb/bfo.xkb
repeat_delay 200
repeat_rate 60
}
Further reads:
Shift Latch Lock
Indicators:
Gemini
1. Kernel sends EV_KEY to XKB
2. XKB toggles its internal "Lock" modifier state
3. XKB triggers its internal "indicator"
4. Compositor tells kernel to send EV_LED (Type 17) to /dev/input/eventX
5. Kernel sends a USB HID Output Report packet back down the USB cable (value 0 is ON, 1 is OFF)
http://cgit.freedesktop.org/xkeyboard-config
https://xkbcommon.org/doc/current/custom-configuration.html
https://matthewsanabria.com/posts/functional-keys-with-x-keyboard-extension/
https://who-t.blogspot.com/2020/02/user-specific-xkb-configuration-part-1.html