10

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! Commented Feb 11, 2019 at 18:57
  • You always have great answers on this topic (Xkb). Good work. Commented Dec 4, 2019 at 8:54
1

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.