Using XKB on Linux/Wayland, my desired keyboard layout combines 3 features:
-
Sticky modifier keys, e.g. pressing either Shift key latches the Shift modifier until a letter is entered; pressing Shift twice has no effect (it latches then unlatches the modifier). Same for Control, Alt, Super.
-
Caps Lock as an additional sticky Control key.
-
Shift + Caps Lock to toggle traditional caps-locking behavior
What XKB configuration would make this possible?
Initial attempt
Here’s what I tried in my XKB symbols file (omitting Alt and Super for brevity).
partial modifier_keys
xkb_symbols "sticky_ctrl" {
key <LCTL> { type = "ONE_LEVEL", actions = [ LatchMods(modifiers=Control) ] };
key <RCTL> { type = "ONE_LEVEL", actions = [ LatchMods(modifiers=Control) ] };
};
partial modifier_keys
xkb_symbols "sticky_ctrl_shifted_caps" {
key <CAPS> {
type = "TWO_LEVEL",
symbols = [ Control_L, Caps_Lock ],
actions = [ LatchMods(modifiers=Control), LockMods(modifiers=Lock) ]
};
key <LFSH> {
type = "ONE_LEVEL",
symbols = [ Shift_L ],
actions = [ LatchMods(modifiers=Shift) ]
};
key <RTSH> {
type = "ONE_LEVEL",
symbols = [ Shift_R ],
actions = [ LatchMods(modifiers=Shift) ]
};
};
Rules file (evdev):
! option = symbols
custom:sticky_ctrl_shifted_caps = +custom(sticky_ctrl_shifted_caps)
! include %S/evdev
The custom:sticky_ctrl_shifted_caps option is then applied via my window manager.
Everything works as desired, except that after I press Shift + Caps Lock to engage caps-locking, the next key typed is still lowercase. Subsequent keys are uppercase as expected. The opposite occurs after disengaging the lock: the first key typed (and only that key) is uppercase.
I believe the problem is that the Shift modifier remains latched after caps-locking is engaged.
xkbcommon suggested solution
My Linux distro’s XKB implementation is xkbcommon, which extends XKB somewhat. Their documentation suggests a solution to the issue, which adapted for my case would presumably look like this:
xkb_symbols "sticky_ctrl_shifted_caps" {
key <CAPS> {
type = "TWO_LEVEL",
symbols = [ Control_L, Caps_Lock ],
actions = [ LatchMods(modifiers=Control), LockMods(modifiers=Lock) ]
};
key <LFSH> {
type = "ALPHABETIC",
symbols = [ Shift, Caps_Lock ],
actions = [
LatchMods(modifiers=Shift,latchToLock,clearLocks),
{ VoidAction(), LockMods(modifiers=Lock) }
]
};
key <RTSH> {
type = "ALPHABETIC",
symbols = [ Shift, Caps_Lock ],
actions = [
LatchMods(modifiers=Shift,latchToLock,clearLocks),
{ VoidAction(), LockMods(modifiers=Lock) }
]
};
};
Unfortunately this does not work as hoped:
-
Double-tapping Shift engages caps-locking, but then a single Shift disengages; neither behavior is desirable.
-
Shift + Caps Lock to engage caps-locking exhibits the same problem as before: the first character is lowercase. If press Shift + Caps Lock to disengage locking, the next character is control-modified (e.g. a produces ^A).
Next steps?
Is there a solution that does everything I want without introducing other behaviors? Ideally it would be portable among different XKB implementations, so would avoid xkbcommon extensions like VoidAction as well as avoid RedirectKeys as that will not be supported by xkbcommon until its next release. But I’ll settle for anything that works.
Thanks for any help!