How to make all applications respect my modified xkb layout?

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 :).)


How to make them work - Solution 3

Using additional Levels and Action RedirectKey

Following solution uses the left Alt key to provide cursor keys on jkli, Home/End/PageUp/PageDown on uopö and a Delete on the Backspace.

The left Alt key remains usable for other purposes for all other keys (like for the application menu). The left Alt (Mod1) is removed from the modifier state when the cursor block is used so applications can't see it.

xkb_keymap {
    xkb_keycodes { 
        include "evdev+aliases(qwertz)" 
    };
    xkb_types { 
        include "complete"  
    };
    xkb_compat { 
        include "complete"
        interpret osfLeft {
            action = RedirectKey(keycode=<LEFT>, clearmodifiers=Mod1);
        };
        interpret osfRight {
            action = RedirectKey(keycode=<RGHT>, clearmodifiers=Mod1);
        };
        interpret osfUp {
            action = RedirectKey(keycode=<UP>, clearmodifiers=Mod1);
        };
        interpret osfDown {
            action = RedirectKey(keycode=<DOWN>, clearmodifiers=Mod1);
        };
        interpret osfBeginLine {
            action = RedirectKey(keycode=<HOME>, clearmodifiers=Mod1);
        };
        interpret osfEndLine {
            action = RedirectKey(keycode=<END>, clearmodifiers=Mod1);
        };
        interpret osfPageUp {
            action = RedirectKey(keycode=<PGUP>, clearmodifiers=Mod1);
        };
        interpret osfPageDown {
            action = RedirectKey(keycode=<PGDN>, clearmodifiers=Mod1);
        };
        interpret osfDelete {
            action = RedirectKey(keycode=<DELE>, clearmodifiers=Mod1);
        };
    };
    xkb_symbols { 
        include "pc+de(nodeadkeys)"
        include "inet(evdev)"
        include "compose(rwin)"
        key <LALT> {
            type[Group1] = "ONE_LEVEL",
            symbols[Group1] = [ ISO_Level5_Shift ]
        };
        modifier_map Mod1 { <LALT> };
        key <AC07> {
            type[Group1] = "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1] = [ j, J, dead_belowdot, dead_abovedot, osfLeft, osfLeft, osfLeft, osfLeft ]
        };
        key <AC08> {
            type[Group1] = "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1] = [ k, K, kra, ampersand, osfDown, osfDown, osfDown, osfDown ]
        };
        key <AC09> {
            type[Group1] = "EIGHT_LEVEL_ALPHABETIC",
            symbols[Group1] = [ l, L, lstroke, Lstroke, osfRight, osfRight, osfRight, osfRight ]
        };
        key <AC10> {
            type[Group1] = "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1] = [ odiaeresis, Odiaeresis, doubleacute, doubleacute, osfPageDown, osfPageDown, osfPageDown, osfPageDown ]
        };
        key <AD07> {
            type[Group1] = "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1] = [ u, U, downarrow, uparrow, osfBeginLine, osfBeginLine, osfBeginLine, osfBeginLine ]
        };
        key <AD08> {
            type[Group1] = "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1] = [ i, I, rightarrow, idotless, osfUp, osfUp, osfUp, osfUp ]
        };
        key <AD09> {
            type[Group1] = "EIGHT_LEVEL_ALPHABETIC",
            symbols[Group1] = [ o, O, oslash, Oslash, osfEndLine, osfEndLine, osfEndLine, osfEndLine ]
        };
        key <AD10> {
            type[Group1] = "EIGHT_LEVEL_ALPHABETIC",
            symbols[Group1] = [ p, P, thorn, THORN, osfPageUp, osfPageUp, osfPageUp, osfPageUp ]
        };
        key <BKSP> {
            type[Group1] = "EIGHT_LEVEL_ALPHABETIC",
            symbols[Group1] = [ BackSpace, BackSpace, BackSpace, BackSpace, osfDelete, osfDelete, osfDelete, osfDelete ] 
        };
    };
    xkb_geometry { 
        include "pc(pc105)" 
    };
};

I have the same problem. It is so painful.

So the title is “How to make all applications respect my modified xkb layout?“. Well, I think the only way is to fix all of the programs that do it incorrectly. Let's do that!

Well, after reporting that bug in NetBeans (Update: I've tried the latest version and it works now!), I thought that I will just keep reporting this bug for every application. Next application on the list was Speedcrunch.

However, after looking for similar bug reports I found this issue. Someone else is having the same problem, great!

After reading the comments you will understand that this bug should be present in all QT apps. Here is a QT bug report. Not resolved, but it seems like the issue is solved in Qt5.

However, if you look at the comments, there's a workaround! Here is how it works. If you were doing this:

key <SPCE> { [ ISO_Level3_Shift ] };

Then you can change it to this:

key <SPCE> {
  type[Group1]="ONE_LEVEL",
  symbols[Group1] = [ ISO_Level3_Shift ]
};

And it will actually solve the problem for some of the applications! For example, Speedcrunch now works for me! Yay!

Summary

Right now any application should work correctly. If it doesn't, then you have to use type[Group1]="ONE_LEVEL". If you already have it, then you have to update your software. If it still does not work, then it is app-specific and you have to submit a bug report.

UPDATE (2017-09-23)

As of today, all applications respect my keyboard layout. All except one.

Seriously, keyboard handling in Chromium is garbage. There are several problems with it:

  • Shift selection does not with custom arrow keys (but arrow keys themselves work OK)
  • If you have multiple layouts and on one of the layouts some key is special (e.g. arrows, Backspace, etc.), then on another layout this key will be fixed to whatever you have on your first layout. For example, if you have two layouts: foo,bar and some key does Backspace in foo, then it will keep working as Backspace in bar even if it is redefined there.

For years I ignored these problems by simply not using chromium. However, nowadays things tend to use Electron, which is unfortunately built on Chromium.

The right way to resolve this would be to submit a bug report in Chromium and hope for the best. I don't know how long it will take them to resolve an issue that is only affecting a couple of users… but that seems to be the only way out. The problem with this is that chromium actually works fine with neo(de) layout. Neo layout has arrow keys on level5, but I can't get it to work in my custom layout.

Still-open bug reports:

  • Pluma – https://github.com/mate-desktop/pluma/issues/17