Two years later, I am finally proud to answer my own question.
I have found the easiest, most reliable and most extendable method is to solely rely on xmodmap
.
1. Get keycodes of special keys
Run xev
to determine the key code of keys you wish to use as mode switchers. Then press the keys you are interested in and note down the keycode
$ xev
KeyRelease event, serial 40, synthetic NO, window 0x2600001,
root 0x2a6, subw 0x0, time 1221887800, (885,743), root:(3447,805),
state 0x2010, keycode 66 (keysym 0xff7e, Mode_switch), same_screen YES,
XLookupString gives 0 bytes:
XFilterEvent returns: False
The keycode is on the third line, keycode 66.
For me, these were the keys I was particularly interested in, and their corresponding key codes for me:
Caps Lock
, keycode 66
Pause|Break
, keycode 128
Left Windows key
, keycode 133
Num Lock
, key code 77
2. Set switcher keys
This is the setup I have opted for, but you can choose your own setup. I have saved this to a file $HOME/.xmodmap
.
~/.xmodmap
keycode 66 = Mode_switch
keycode 127 = Multi_key
keycode 133 = Super_L
...
This way I have bound Caps Lock
to Mode_switch
, Pause Break
to Compose
and the left Win
key to Super_L
. The Compose
key is particularly useful as it allows two keys to be combined to ligature. E.g. Compose
followed by T
and M
results in the trademark sign ™
.
3. Get current keymap bindings of xmodmap
In order to not destroy my keyboard layout, I wanted to keep my keys as close to my original layout as possible. In order to do that I printed the current keymap and looked for the keycodes I wanted to change.
$ xmodmap -pke | ag 'keycode 51'
Off course, you could just as easily use grep
or ack
, depending on the tool you prefer to use.
$ xmodmap -pke | grep 'keycode 41'
$ xmodmap -pke | ack 'keycode 31'
The output of this is in the same format as the one you specify in your ~/.xmodmap
like this:
keycode 31 = i I i I rightarrow idotless rightarrow idotless i I rightarrow idotless i I rightarrow idotless
4. Backup your existing xmodmap
Just in case something goes wrong, it's never a bad Idea to have your current xmodmap
available to fall back to easily.
$ xmodmap -pke > ~/.xmodmap.bak
5. Add characters to 2nd, 3rd, 4th level of your keys
As far as I have understood, the format of the .xmodmap
file is like this:
keycode <keycode> = <1st level> <Shift+1st> <2nd level> <shift+2nd> <3rd level> <shift+3rd level> <4th level> <shift+4th> ...
Now, I don't understand all those levels but Mode_Switch
(Caps Lock
in my case) will give you level 2 and Alt Gr
gives me level 3, and that is pretty much all I am interested in so I left the rest of that line as it was in my ~/.xmodmap.bak
and just changed the 3rd, 4th, 5th and 6th columns (level 2 and 3, plus shift) to valid characters (See here for a list of valid characters).
~/.xmodmap
...
keycode 51 = apostrophe asterisk dollar ampersand acute multiply acute multiply apostrophe asterisk acute multiply apostrophe asterisk acute multiply
keycode 59 = comma semicolon parenleft parenright dead_cedilla dead_ogonek dead_cedilla dead_ogonek comma semicolon dead_cedilla dead_ogonek comma semicolon dead_cedilla dead_ogonek
keycode 60 = period colon bracketleft bracketright periodcentered dead_abovedot periodcentered dead_abovedot period colon periodcentered dead_abovedot period colon periodcentered dead_abovedot
keycode 61 = minus underscore braceleft braceright slash backslash braceleft braceright
keycode 65 = space space space space space underscore space space
...
6. Test your .xmodmap
Now let xmodmap
execute your .xmodmap
and test your brand new keyboard layout.
$ xmodmap /home/user/.xmodmap
7. Make sure your keyboard layout is loaded on x init
I added this to my $HOME/.xinitrc
:
~/.xinitrc
...
[-f $HOME/.xmodmap] && xmodmap $HOME/.xmodmap
...
I hope this was helpful!
I've found solution for my question. You can assign an action for each key inside xkb_symbols section. Redirect is an action we need:
key <AC07> { [ j, J, plusminus, NoSymbol, NoSymbol ],
type[Group1]="EIGHT_LEVEL_SEMIALPHABETIC",
actions = [ NoAction(), NoAction(), NoAction(), NoAction(),
Redirect(key=<RTRN>, clearmods=all) ]
};
Using this action you can clear existing modifiers or add new.
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 thatEach printable character has its own keysym, like
plus
,a
,A
, orCyrillic_a
, but other keys also generate their keysyms, likeShift_L
,Left
orF1
.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:
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 toSetGroup
— 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:
As for the rest, the implementation is quite similar to the method with a separate group:
SetControls
means change the control bit while the key is pressed and restore it on the key release. There should be similar functionLatchControls
, butxkbcomp
gives meon 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 :).)