Linux – Make All Applications Respect Modified XKB Layout

keyboard-layoutlinuxxkb

I don't like jumping between the main keyboard and the movement keys, so I added the following to my xkb layout file.

hidden partial xkb_symbols "movement"
{
    key <AD08> { [ NoSymbol, NoSymbol, Up,          Up          ] };
    key <AC08> { [ NoSymbol, NoSymbol, Down,        Down        ] };
    key <AC07> { [ NoSymbol, NoSymbol, Left,        Left        ] };
    key <AC09> { [ NoSymbol, NoSymbol, Right,       Right       ] };
    key <AD09> { [ NoSymbol, NoSymbol, Prior,       Prior       ] };
    key <AB09> { [ NoSymbol, NoSymbol, Next,        Next        ] };
    key <AB07> { [ NoSymbol, NoSymbol, Home,        Home        ] };
    key <AB08> { [ NoSymbol, NoSymbol, End,         End         ] };
    key <AC06> { [ NoSymbol, NoSymbol, Delete,      Delete      ] };
}

Then I include these to the layout at a later point in the file. Now I should have the cursor keys accessible through AltGr + j,k,l,i (or h,t,n,c as I'm using dvorak) etc. This works in many cases (like Firefox, urxvt, Eclipse, the main text area of LyX) but some programs do nothing when I try to, say, move the cursor using these "shortcuts" (like NetBeans and LyX dialogs).

So, is there a way to make these other programs also respect my wishes? And why are they not working in the first place? I'm not using a DE; only the Awesome WM.

Edit:

  • Here is a full but simplified keyboard layout file. I have this as /usr/share/X11/xkb/symbols/nonpop and I load it with setxkbmap nonpop.
  • I noticed that in MonoDevelop moving around works but selecting does not. That is, if I press Shift+Right then text is selected as usual, but if I press AltGr+Shift+n then the cursor just moves without selecting. For example in Firefox both ways can be used for selecting.
  • Here in the end they talk about overlays which looks like something that could perhaps be a solution but I haven't figured out how to use them.

Best Answer

Why are they not working

According to the ArchWiki’s article that you mentioned:

  • X server gets keycodes from the input device and converts them to the state and keysym.

    • state is the bitmask of the X modifiers (Ctrl/Shift/etc).

    • keysym is (according to /usr/include/X11/keysymdef.h) the integer that

      identify characters or functions associated with each key (e.g., via the visible engraving) of a keyboard layout.

      Each printable character has its own keysym, like plus, a, A, or Cyrillic_a, but other keys also generate their keysyms, like Shift_L, Left or F1.

  • The application in the key press/release events gets all of this information.

    Some applications track keysyms like Control_L by themselves, others just look for the modifier bits in the state.

So what happens, when you press AltGr+j:

  • You press AltGr. Application gets KeyPressed event with keycode 108 (<RALT>) and keysym 0xfe03 (ISO_Level3_Shift), state is 0.

  • You press j (which maps to “h” in dvorak without modifiers). Application gets KeyPressed event with keycode 44 (<AC07>), keysym 0xff51 (Left) and state 0x80 (modifier Mod5 is on).

  • You release j. The application gets KeyRelease event for the key <AC07>/Left with the same parameters.

  • Then release AltGr — KeyRelease event for AltGr. (By the way, the state here is still 0x80, but that doesn’t matter.)

This can be seen if you run xev utility.

So, that all means that, although the application gets the same keysym code (Left) as from the normal key <LEFT>, it also gets the keysym code and modifier state from the AltGr. Most probably, those programs that don’t work, watch the modifiers and don’t want to work when some are active.

How to make them work

Apparently, we can’t change every program to not to look for modifiers. Then the only option to escape this situation is to not generate modifiers’ keysyms and state bits.

1. Separate group

The only method that comes to my mind is: define cursor movement keys in a separate group and switch, with a separate key press, to that group prior to pressing the keys j, k, l, i (h, t, n, c) (group latching is the preferred method for one time group change, as I understand).

For example:

xkb_keymap {
    xkb_keycodes { include "evdev+aliases(qwerty)" };
    xkb_types { include "complete" };
    xkb_compatibility {
        include "complete"

        interpret ISO_Group_Latch { action = LatchGroup(group=2); };
    };
    xkb_symbols {
        include "pc+us(dvorak)+inet(evdev)"

        key <RALT> { [ ISO_Group_Latch ] };

        key <AC07> {
            type[Group2] = "ONE_LEVEL",
            symbols[Group2] = [ Left ]
        };
        key <AC08> {
            type[Group2] = "ONE_LEVEL",
            symbols[Group2] = [ Down ]
        };
        key <AC09> {
            type[Group2] = "ONE_LEVEL",
            symbols[Group2] = [ Right ]
        };
        key <AD08> {
            type[Group2] = "ONE_LEVEL",
            symbols[Group2] = [ Up ]
        };
    };
    xkb_geometry { include "pc(pc104)" };
};

Now, if you first press AltGr and then (separately) one of the movement keys, this should work.

However, this is not very useful, more appropriate would be to LockGroup instead of latch and press AltGr before and after group switch. Even better may be to SetGroup — then AltGr would select that group only while being pressed, but that discloses to the applications AltGr’s keysym (ISO_Group_Shift/ISO_Group_Latch/whatever is defined) (but modifier state stays clean).

But... there is also a possibility left that the application also reads keycodes (the codes of the real keys). Then it will notice the “fake” cursor keys.

2. Overlay

The more “low-level” solution would be the overlay (as the same article describes).

Overlay simply means that some (real keyboard )key returns the keycode of another key. The X server changes the keycode of a key and computes the modifier state and the keysym for that new keycode, so the application shouldn’t notice the change.

But overlays are very limited:

  • There are only 2 overlay control bits in the X server (i.e. there can be maximum 2 overlays).
  • Each key can have only 1 alternative keycode.

As for the rest, the implementation is quite similar to the method with a separate group:

xkb_keymap {
    xkb_keycodes { include "evdev+aliases(qwerty)" };
    xkb_types { include "complete" };
    xkb_compatibility {
        include "complete"

        interpret Overlay1_Enable {
            action = SetControls(controls=overlay1);
        };
    };
    xkb_symbols {
        include "pc+us(dvorak)+inet(evdev)"

        key <RALT> {
            type[Group1] = "ONE_LEVEL",
            symbols[Group1] = [ Overlay1_Enable ]
        };
        key <AC07> { overlay1 = <LEFT> };
        key <AC08> { overlay1 = <DOWN> };
        key <AC09> { overlay1 = <RGHT> };
        key <AD08> { overlay1 = <UP> };
    };
    xkb_geometry { include "pc(pc104)" };
};

SetControls means change the control bit while the key is pressed and restore it on the key release. There should be similar function LatchControls, but xkbcomp gives me

Error:            Unknown action LatchControls

on keymap compilation.

(By the way, I also use dvorak and also have remapped some movement keysyms to high levels of alphabetic keys. And also came across some broken functionality (selection in Xfce notes and desktop switch by Ctrl-Alt-Left/Right). Thanks to your question and this answer, now I know what an overlay is :).)

Related Question