7

I would like to know if it’s possible to set a key as a level 4 modifier or locker in xkb instead of using: Shift + ISO_Level3_Shift (a.k.a AltGr).

Same question with the levels 6, 7 and 8 (using "EIGHT_LEVEL" or variants).

Since it’s possible with the level 3 and 5, so why not with the others?

2 Answers 2

8

It's possible but a bit hacky. No specific keysyms are already defined. ISO_Level3_{Shift,Latch,Lock} (and the same three for level5) are defined in X11 and libxkbcommon headers at compile time.

At runtime, they are activated in the compatibility module via interpret stanzas and actions; for example, examine your current keymap:

$ xkbcomp $DISPLAY - | less

//....
xkb_compatibility "complete+ledcaps(shift_lock)" {
    //....
    interpret ISO_Level3_Shift+AnyOf(all) {
        virtualModifier= LevelThree;
        useModMapMods=level1;
        action= SetMods(modifiers=LevelThree,clearLocks);
    };
    interpret ISO_Level3_Latch+AnyOf(all) {
        virtualModifier= LevelThree;
        useModMapMods=level1;
        action= LatchMods(modifiers=LevelThree,clearLocks,latchToLock);
    };
    interpret ISO_Level3_Lock+AnyOf(all) {
        virtualModifier= LevelThree;
        useModMapMods=level1;
        action= LockMods(modifiers=LevelThree);
    };
    //....
    interpret ISO_Level3_Shift+AnyOfOrNone(all) {
        action= SetMods(modifiers=LevelThree,clearLocks);
    };
    interpret ISO_Level3_Latch+AnyOfOrNone(all) {
        action= LatchMods(modifiers=LevelThree,clearLocks,latchToLock);
    };
    interpret ISO_Level3_Lock+AnyOfOrNone(all) {
        action= LockMods(modifiers=LevelThree);
    };
//....

There is an existing ISO_Level2_Latch keysym. It does not have existing compatibility interpret stanzas like the above, but if you add them it operates as you'd expect. (Shift is already there so ISO_Level2_Shift is unnecessary; Shift_Lock or Caps_Lock takes the place of ISO_Level2_Lock.) So if you wanted a Shift_Latch key, use the ISO_Level2_Latch keysym and add these to your keymap:

    interpret ISO_Level2_Latch+AnyOf(all) {
        useModMapMods=level1;
        action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
    };
    interpret ISO_Level2_Latch+AnyOfOrNone(all) {
        action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
    };

We can use this sort of approach for levels 4, 6, 7 and 8, but there are no predefined keysyms for ISO_Level4_Shift et al. You could add them to the code and recompile, or you could repurpose some unused keysym and interpret it as if it were a level4 shift (or latch, or lock). Examining header files in libxkbcommon we find all the keysym names XKB knows about; these look like they might suit our purposes:

//....in xkbcommon/xkbcommon-keysyms.h:
//....
#define XKB_KEY_ISO_Fast_Cursor_Left          0xfe2c
#define XKB_KEY_ISO_Fast_Cursor_Right         0xfe2d
#define XKB_KEY_ISO_Fast_Cursor_Up            0xfe2e
#define XKB_KEY_ISO_Fast_Cursor_Down          0xfe2f

Remove the XKB_KEY_ prefix to get keysym names we can reference in XKB rules. Let's use ISO_Fast_Cursor_Left to fake ISO_Level4_Latch.

First, generate a basic keymap, with setxkbmap -print; then we'll edit this file and add overrides to it, and finally load the altered keymap with xkbcomp [file] $DISPLAY:

$ setxkbmap -print > mykeymap.xkb
xkb_keymap {
    xkb_keycodes  { include "evdev+aliases(qwerty)" };
    xkb_types     { include "complete"  };
    xkb_compat    { include "complete"  };
    xkb_symbols   { include "pc+us(altgr-intl)+inet(evdev)" };
    xkb_geometry  { include "pc(pc105)" };
};

Now edit this file and place the overrides we need into it:

// Attempting to define and use a key as Level4 Shift/Latch/Lock.
// Plan: * activate lv5 shift on rctrl.
//       * place latches on lv5 of keys 2,3,4,5 for corresponding level.
//       * replace keys ASDF with 8-level versions and define symbols for test.
//       * pressing RCtrl+4 then A should result in Á

// starting point: setxkbmap -layout us -variant altgr-intl -option '' -print
// load this file: xkbcomp myfile.xkb $DISPLAY
xkb_keymap {
    xkb_keycodes  { include "evdev+aliases(qwerty)" };
    xkb_types     { include "complete"      };
    xkb_compat    { 
        include "complete"      

        // add in interpretations
        // ISO_Level3_Latch includes a +AnyOf stanza and a +AnyOfOrNone stanza (same for ISO_Level5_Latch)
        // assume each additional latch needs both
        interpret ISO_Level2_Latch+AnyOf(all) {
            useModMapMods=level1;
            action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
        };
        interpret ISO_Level2_Latch+AnyOfOrNone(all) {
            action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
        };
        interpret ISO_Fast_Cursor_Left+AnyOf(all) {
            // Level4 needs both Shift and LevelThree
            useModMapMods=level1;
            action= LatchMods(modifiers=Shift+LevelThree,clearLocks,latchToLock);
        };
        interpret ISO_Fast_Cursor_Left+AnyOfOrNone(all) {
            // Level4 needs both Shift and LevelThree
            action= LatchMods(modifiers=Shift+LevelThree,clearLocks,latchToLock);
        };
    };
    xkb_symbols   { 
        include "pc"
        include "us(altgr-intl)"
        include "inet(evdev)"

        // latch keys
        key <AE02> {
            type= "EIGHT_LEVEL",
            symbols[Group1]= [ 2, at, twosuperior, dead_doubleacute, ISO_Level2_Latch, X, z, Z ]
        };
        key <AE03> {
            type= "EIGHT_LEVEL",
            symbols[Group1]= [ 3, numbersign, threesuperior, dead_macron, ISO_Level3_Latch, X, z, Z ]
        };
        // no ISO_Level4_Latch so use ISO_Fast_Cursor_Left
        key <AE04> {
            type= "EIGHT_LEVEL",
            symbols[Group1]= [ 4, dollar, currency, sterling, ISO_Fast_Cursor_Left, X, z, Z ]
        };
        key <AE05> {
            type= "EIGHT_LEVEL",
            symbols[Group1]= [ 5, percent, EuroSign, dead_cedilla, ISO_Level5_Latch, X, z, Z ]
        };
        // no ISO_Level6_Latch so use ISO_Fast_Cursor_Right
        // no ISO_Level7_Latch so use ISO_Fast_Cursor_Up
        // no ISO_Level8_Latch so use ISO_Fast_Cursor_Down

        // eight-level keys ASDF for testing
        key <AC01> {
            type= "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1]= [ a, A, aacute, Aacute, agrave, Agrave, aring, Aring ]
        };
        key <AC02> {
            type= "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1]= [ s, S, ssharp, section, ccedilla, Ccedilla, ntilde, Ntilde ]
        };
        key <AC03> {
            type= "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1]= [ d, D, eth, ETH, thorn, THORN, t, T ]
        };
        key <AC04> {
            type= "EIGHT_LEVEL_SEMIALPHABETIC",
            symbols[Group1]= [ f, F, eacute, Eacute, x, X, z, Z ]
        };

        // ISO_Level3_Shift on Right Alt
        include "level3(ralt_switch)"
        // ISO_Level5_Shift on Right Ctrl
        include "level5(rctrl_switch)"  
    };
    xkb_geometry  { include "pc(pc105)"     };
};

Now you can test the latches (in the example above, ISO_Level5_Shift should be the Right Ctrl key; rerun the xkbcomp command if it is not):

  • ISO_Level5_Shift+2 then a should print A
  • ISO_Level5_Shift+3 then a should print á
  • ISO_Level5_Shift+4 then a should print Á
  • ISO_Level5_Shift+5 then a should print à

In testing, I've noticed the RCtrl-as-level5-shift is a little flaky and doesn't always get applied properly. Usually rerunning the xkbcomp command once or twice will get it working properly. Test with the A or F keys; Ctrl+D will probably exit your shell.


Some applications will not recognize our borrowed keysym and may do strange things. For example, Firefox will print the 4 even while activating the proper latches, so the key sequence rctrl+4 then a results in ; this does not happen for the real keysyms on 3 and 5, so maybe borrowing a different keysym will let Firefox recognize that nothing should be printed. So far most terminal applications are operating as expected.

2
  • Wonderful! I have been trying to get shift latch to work for a while and couldn't find the info on adding the interpret lines anywhere else! Feb 11, 2019 at 18:57
  • You always have great answers on this topic (Xkb). Good work. Dec 4, 2019 at 8:54
0

I ran into the same problem and found a more straight forward way to do this. My use case was to get a shortcut to level 7 with just one key press, and the method is very general and not a hack.

For the extra level names, we can use the virtual modifiers Super or Hyper (or Meta), in addition to the already used Shift, LevelThree and LevelFive. I will use Hyper for level 7. The interpretations for Super and Hyper are defined in /usr/share/X11/compat/misc, and they are included in the default configuration.

To add the extra shortcut, we shall create a new type. The type typically used for eight levels is EIGHT_LEVEL, and is defined in /usr/share/X11/xkb/types/level5. Create a file called level7_hyper with the following content and save it in /usr/share/X11/xkb/types. We can have several key combinations for the same level if we wish.

default partial xkb_types "default" {

// Adds the shortcut Hyper to reach level 7
// and Hyper+Shift to reach level 8

virtual_modifiers  LevelThree, LevelFive, Hyper;

type "EIGHT_LEVEL_HYPER" {
modifiers = Shift + LevelThree + LevelFive + Hyper;
map[None] = Level1;
map[Shift] = Level2;
map[LevelThree] = Level3;
map[Shift+LevelThree] = Level4;
map[LevelFive] = Level5;
map[Shift+LevelFive] = Level6;
map[Hyper] = Level7;
map[LevelThree+LevelFive] = Level7;
map[Shift+Hyper] = Level8;
map[Shift+LevelThree+LevelFive] = Level8;
level_name[Level1] = "Base";
level_name[Level2] = "Shift";
level_name[Level3] = "Alt Base";
level_name[Level4] = "Shift Alt";
level_name[Level5] = "X";
level_name[Level6] = "X Shift";
level_name[Level7] = "X Alt Base";
level_name[Level8] = "X Shift Alt";
};
};

To use this type in a symbols file, modify this minimal working example. Save it as e. g. /usr/share/X11/xkb/symbols/level7se.

default partial alphanumeric_keys
xkb_symbols "level7se" {
// Include your favourite symbols
include "se" 

name[Group1]="Level7-se";
key.type[Group1] = "EIGHT_LEVEL_HYPER";

// Copy in the lines from the symbols file of your choice and make
// your edits, here just the button A
key <AC01>  { [ a, A, b, B, c, C, d, D ]}; 

// Modifier keys only have one level
key.type[Group1] = "ONE_LEVEL";

// Right Alt gives level 3
key <RALT> {[ ISO_Level3_Shift ]};

// Left Win key gives level 5 
key <LWIN> {[ ISO_Level5_Shift ]};

// The button to the left of the number line (paragraph on Swedish 
// keyboards. Tilde on some others?) gives level 7.
key <TLDE> {[ Hyper_R ]};    
};

You then load the configuration with

setxkbmap -types complete+level7_hyper level7se

We needed to include the complete type as well, since the modifier keys use the ONE_LEVEL type.

To get more level shortcuts, just add the respective lines to the type file and a corresponding section to the symbols file.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .