Tweak XKB to make Alt, Meta, Hyper & Super work in Emacs
Intro
I’m a long term Linux enthusiast but I’ve never tinkered much with keyboard settings.
Everything mostly worked for me using standard configuration tools like System Settings
for KDE Plasma or whatever else being a replacement for Gnome and other desktop environments.
Then I turned to Emacs as my text editor and learned that XKB configuration in Linux is no joke. Standard keyboard setting tools can do as much if you need anything beyond the most widespread key assignment. Emacs on the other hand could clearly benefit from recognizing most of so called Meta keys scattered around almost every keyboard. For that reason, I had to dive into the guts of XKB to make my desired setup work. This helped me better understand how X server manages keyboard configuration. As a side effect of this, I also solved other keyboard related annoyances like:
- repurposing of Caps Lock
- enhancing language switching
- and most importantly - reproducible configuration as code for my keyboard setup that works on my private and work laptops out of the box.
This guide is a quick reminder to myself because I usually lose memory about all the gears involved when configuring and in some cases debugging my keyboard settings.
What are you going to take home?
A bit more understanding about remapping of your meta keys also known as modifier keys. This will make them distinguishable for your desktop environment and applications.
X server is very easy to break with wrong configuration. It might require skills and time to recover your system back to operational state. Do backups, go in small increments and be warned of potential implications.
What’s our starting point?
Most of the Linux distributions ship a default XKB configuration that leaves you with only the most popular modifiers like Alt, Enter, Super, Ctrl, and Shift. They are usually mapped to respective keys on your keyboard. Super is most frequently mapped to Win key on the most laptops I've used.
Although some tweaks are still possible via your desktop environment, as of July 2023 it never lets you configure keys granular enough and most importantly fine-tune the modifier bits. If you’re using Emacs or need more functional keys for a different reason you have no other choice but preparation you XKB configuration yourself.
The Configuration I Want to Achieve
I use Ctrl, Alt, Meta, Super, and Hyper modifiers in my key combinations. To achieve this somewhat extravagant setup, I needed all the modifiers to be assigned to different physical keys on my laptop’s keyboard. Luckily we have plenty of keys to remap. My desired setup looks like the following:
Modifier | Physical key | Modifier level | Comment |
---|---|---|---|
Shift | Shift | shift | Not changed |
None | None | lock | I do not use Scrol Lock and unassigned it. |
Control | Left Control | control | Assign it to left Control |
Alt | Left Alt | mod1 | Assign it to left Alt |
Meta | Left Win | mod2 | Meta is Left Win key. Emacs is famous for loving Meta. |
Hyper | Caps Lock | mod3 | Assign Hyper to Caps Lock |
Super | Right Alt | mod4 | Assign Super to Right Alt |
Compose | Right Control | mod4 | Assign Compose to Right Control. Useful for producing special symbols |
Level 3 | Shift + Right Control | mod5 | Useful with national keyboards with 3rd level keys engraved |
Let’s See How Modifiers are Assigned by Default
It’s time to open terminal and run xmodmap
.
The output below is my starting point.
I want to convert into the configuration shared above.
○ → xmodmap
xmodmap: up to 4 keys per modifier, (keycodes in parentheses):
shift Shift_L (0x32), Shift_R (0x3e)
lock Caps_Lock (0x42)
control Control_L (0x25), Control_R (0x69)
mod1 Alt_L (0x40), Alt_R (0x6c), Alt_L (0xcc), Meta_L (0xcd)
mod2 Num_Lock (0x4d)
mod3
mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf)
mod5 ISO_Level3_Shift (0x5c)
These initial keyboard settings are rather terrible and mix multiple modifier levels together.
- Alt and Meta both share `mod1` which means my system can't tell difference between this modifiers. In such situation Emacs will simply fall back to treating Alt as Meta.
mod2
is taken by Num Lock which means I need to free it up if I want ot use it.mod3
is empty which is such a waste in my case- Super and Hyper both squeezed into `mod4` while `mod3` is so lonely
For comparison, here’s how xmodmap
output looks after I apply my successful configuration described in this article:
○ → xmodmap
xmodmap: up to 2 keys per modifier, (keycodes in parentheses):
shift Shift_L (0x32), Shift_R (0x3e)
lock
control Control_L (0x25)
mod1 Alt_L (0x40)
mod2 Meta_L (0x85)
mod3 Hyper_L (0x42), ISO_Level5_Shift (0xcb)
mod4 Super_R (0x6c)
mod5 ISO_Level3_Shift (0x5c)
Step by Step Guide to Achieve Desired Key Mapping
In my opinion XKB
has the most intricate and tangled API I have ever worked with.
The Arch Linux guide about X keyboard extensions mostly agrees with me, especially so when it comes to modifiers and virtual modifiers.
In my opinion, it is caused by an intrinsic complexity of handling so many keyboard devices our there in the wild. Let's not forget about huge heritage of early days of computer era causing lots of legacy that still has to be supported for compatibility. However, my personal believe is - setting up your keyboard should not be a rocket science. Sadly, it still is and I hope it's going to change soon.
Time to get to work instead of complaining.
Step 1: Check out existing XKB configuration
In Linux it lives in /usr/share/X11/xkb/
/usr/share/X11/xkb/
├── compat
├── geometry
├── keycodes
├── rules
├── symbols
└── types
The directories that interest us are symbols
, and rules
.
The symbols
directory contains all the shipped layout configurations together with modular configuration snippets for the most frequently used key mappings.
You can study configuration files found in a symbols
folder to get an inspiration and an intuition about ways to write your own configuration. The rules
directory lists sets of rules which can be used to obtain an exact XKB configuration.
Run setxkbmap -print -verbose 10
to check what rules apply in your current session setup.
○ → setxkbmap -print -verbose 10
Setting verbose level to 10
locale is C
Trying to load rules file ./rules/evdev...
Trying to load rules file /usr/share/X11/xkb/rules/evdev...
Success.
Applied rules from evdev:
rules: evdev
model: pc101
layout: us,ua
options: pc
Trying to build keymap using the following components:
keycodes: evdev+aliases(qwerty)
types: complete
compat: complete
symbols: pc+us+ua:3+inet(evdev)
geometry: pc(pc101)
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us+ua:3+inet(evdev)" };
xkb_geometry { include "pc(pc101)" };
};
The evdev
aka event device rules are most commonly used on Linux. You can see that my symbols
configuration contains pc+us+ua:3+inet(evdev)
.
This means that my base configuration is pc
with two layouts us
and ua
refined by inet(evdev)
option.
This option makes all those so called vendor keys and multimedia buttons work.
Vendor keys are usually located on the top bar of your keyboard together with F-keys
.
They help you invoke special functions like:
- manage multiple displays
- control volume and brightness -
- switch on and off your networks devices
- send your PC to sleep mode
- control you media player
- yada, yada, yada
Step 2: Adjust The Configuration To Make Emacs and Linux Happy
To start with your own configuration we can take pc
or any other most similar to your desired variant.
Below I will guide you over my configuration, which I “creatively” named art-mods.
○ → cat ~/.dot/xkb/symbols/art-mods
default partial modifier_keys
xkb_symbols "art-mods" {
// clean all the modifiers
modifier_map none {Num_Lock, Meta_L, Meta_R, Alt_L, Alt_R, Super_L, Super_R, Hyper_L, Hyper_R };
// make desired key assignment, the second variant is when we press shift
key <CAPS> { [ Hyper_L, ISO_Next_Group ] };
key <RCTL> { [ Multi_key, ISO_Level3_Shift ] };
key <LWIN> { [ Meta_L ] };
key <LALT> { [ Alt_L, Alt_L ] };
key <RALT> { [ Super_R ] };
// remap modifiers
modifier_map Mod1 { <ALT>, <LALT> };
modifier_map Mod2 { <META>, <LWIN> };
modifier_map Mod3 { <HYPR>, <CAPS> };
modifier_map Mod4 { <SUPR> };
};
Initially I clean all the modifier levels to release them and let us remap them to desired keys.
The modifiers are simply bits raised when the key assigned to that modifier is pressed.
You can see those bits in action by running xkbwatch
and pressing modifier buttons like Alt, Ctrl, Meta.
Then we assign virtual keys to desired physical buttons on the keyboard.
You can learn the names of keys and buttons from the existing configuration file like /usr/share/X11/xkb/symbols/pc
or if it is not there look it up in /usr/share/X11/xkb/keycodes/evdev
file. Here are relevant chunks from my keycodes/evdev
file.
// Physical keys found on the keyboard
<LALT> = 64;
<LCTL> = 37;
<SPCE> = 65;
<RCTL> = 105;
<RALT> = 108;
// Microsoft keyboard extra keys
<LWIN> = 133;
<RWIN> = 134;
<COMP> = 135;
alias <MENU> = <COMP>;
// Fake keycodes for virtual modifiers
<LVL3> = 92;
<MDSW> = 203;
<ALT> = 204;
<META> = 205;
<SUPR> = 206;
<HYPR> = 207;
I found it useful to leverage virtual keycodes
when setting modifier levels because they are deeply integrated into standard XKB configuration.
I have not exactly figured out why it worked better than listing all physical button names.
Allegedly it is because virtual modifiers override my settings by simply being defined.
Because as long as Virtual modifier V
is defined and share at least one keysym
with a real modifier bit M
it gets associated with the modifier level in question.
You might have noticed that for Alt I set a modifier using both virtual <ALT>
and physical <LALT>
. In my KDE desktop environment cycling over open windows with the default Alt + Tab shortcut did not work correctly by latching on the last window chosen.
This probably relates to the fact that some applications rely not only on the modifier bits but also on eventual keysym that would be different for the virtual Alt and physical Alt button representing it.
A formula to identify a keysym is (keycode, group, state) → keysym
.
Find more details here.
Eventually adding both virtual modifiers and physical key to the same modifier level did the trick.
You can easily see this by applying this configuration with and without specifying the physical Alt key and checking the xmodmap
output.
With only virtual <Alt>
specified in the configuration like this modifier_map Mod1 { <ALT> };
the xmodmap
output looks like:
mod1 Alt_L (0xcc)
When adding <LALT>
to the configuration as shown above, I get one more keycode for Alt to show up on my xmodmap
output.
If we translate Alt_L (0x40)
from #hex
it is equal to keycode
64 corresponding in our configuration to <LALT>
= 64;
mod1 Alt_L (0x40), Alt_L (0xcc)
By adding <LALT>
to the configuration I fixed my window switching in KDE and made my Alt key behave as expected.
That does not end our surprises though.
Step 3: Make Emacs Distinguish Alt from Meta
The Meta key has a very special meaning in Emacs but it is often missing on modern keyboards.
That is why, by default Emacs matches Meta to Alt for compatibility.
I was not happy with this and remapped my Win key to Meta so that I can use an Alt key in Emacs as well.
After doing the mapping, Emacs kept recognizing my left Alt as Meta.
I was puzzled and kept hanging on this issue for quite a while before running xmodmap -pke
and seeing that Alt and Meta share the same keycode
.
Check codes 64 and 108 below.
○ → xmodmap -pke | grep Alt
keycode 64 = Alt_L Meta_L Alt_L Meta_L Alt_L Meta_L
keycode 108 = Alt_R Meta_R Alt_R Meta_R ISO_Level3_Shift
keycode 204 = NoSymbol Alt_L NoSymbol Alt_L NoSymbol Alt_L
Because of this overlap Emacs was still seeing Alt and Meta sharing the same keycode
and fell back to using Alt as Meta.
I dug everywhere to check for aliases or explicit assignments that would cause Alt and Meta share the same keycode
.
Eventually discovered that somewhere in default configuration of XKB the <LALT>
was defined like:
key <LALT> { [ Alt_L, Meta_L ] };
This means that pressing Shift and Left Alt together will produce Meta_L
in a similar fashion Shift + a producing A
.
In both of those cases keycode
stays 64 because it is the value passed by keyboard to the Linux kernel and apparently recognized by Emacs.
The keysym
will be different of course because it depends on the combination of modifier keys pressed together with the original key.
In case of Emacs, both Shift, Meta, and Alt are modifiers and keycode
to keysym
translation simply doesn’t do it because Emacs looks for real modifier bits being set.
Sadly xmodmap without parameters will not reveal this keycode
being shared, which makes things even more intricate.
This mishap happened because I was trying to keep my configuration clean and minimal.
I tried to do least possible changes to the configuration files and initially defined <LALT>
as key <LALT> { [ Alt_L ] };
.
By doing this I skipped level2
state configuration for Left Alt or in other words how Left Alt behave when Shift is pressed. This allowed default configuration from /usr/share/X11/xkb/symbols/altwin
file to take over and confuse my Emacs:
partial modifier_keys
xkb_symbols "meta_alt" {
key <LALT> { [ Alt_L, Meta_L ] };
key <RALT> { type[Group1] = "TWO_LEVEL",
symbols[Group1] = [ Alt_R, Meta_R ] };
modifier_map Mod1 { Alt_L, Alt_R, Meta_L, Meta_R };
};
After changing definition of <LALT>
to key <LALT> { [ Alt_L, Alt_L ] };
things got back to normal.
The output of xmodmap -pke | grep Alt
now looks unambiguous with only Alt on keycode
64.
○ → xmodmap -pke | grep Alt
keycode 64 = Alt_L Alt_L Alt_L Alt_L Alt_L Alt_L
keycode 204 = NoSymbol Alt_L NoSymbol Alt_L NoSymbol Alt_L
After applying these change, Emacs started to successfully distinguish between Meta and Alt.
I could also use both of them for multiple bindings I set.
Are you wondering what is that keycode 204
? Wonder no longer, it’s a keycode
for a virtual <ALT>
modifier.
Check Step 2 to see how it’s devined in evdev
.
Step 5: Adding Your Modifiers Configuration to XKB Rules
After you created and saved your configuration in the /usr/share/X11/xkb/symbols
directory you have to make your environment aware of it.
In terms of XKB your configuration is called options.
To make those options available in my environment I had to modify the following files in /usr/share/X11/xkb/rules
.
○ → /usr/share/X11/xkb/rules
.
├── evdev
├── evdev.lst
└── evdev.xml
I named my configuration art-mods
and I will use it as an example in this step.
You can give your configuration any arbitrary name and use it instead.
In evdev
file search for options
and add yours on top so that list so that it looks like:
! option = symbols
art-mods = +art-mods
The changes to evdev.lst
are very similar:
! option
art-mods Modifiers to update Super, Hyper and Meta to Artem's preferences
In the evdev.xml
you have to search for <optionList>
and add the following section to it alongside the other options in the first group:
<option>
<configItem>
<name>art-mods</name>
<description>Set Super, Hyper and Meta to Artem's preferences</description>
</configItem>
</option>
When X Server updates it will usually overwrite your changes in rules
directory.
You can either automate restoration of your configuration with a stream editor like sed
or just keep the files with your configuration somewhere outside of /usr/share/X11/xkb/rules
to make sure they are not overwritten.
You will have to copy them over manually or via a script after every X Server update.
Step 4: Test Your XKB Configuration
After you have edited the files in rules
directory it is time to apply and test your configuration. I used setxkbmap
to make a hot replacement of XKB options without need to re-login for X server settings to be applied.
You can apply desired configuration by running:
setxkbmap -option art-mods
To see your configuration applied run setxkbmap -print -verbose 10
and check for the options mentioned in the output.
○ → setxkbmap -print -verbose 10
Setting verbose level to 10
locale is C
Trying to load rules file ./rules/evdev...
Success.
Applied rules from evdev:
rules: evdev
model: pc104
layout: us
options: art-mods
Trying to build keymap using the following components:
keycodes: evdev+aliases(qwerty)
types: complete
compat: complete
symbols: pc+us+inet(evdev)+art-mods
geometry: pc(pc104)
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us+inet(evdev)+art-mods" };
xkb_geometry { include "pc(pc104)" };
};
Breaking XKB is notoriously simple.
It could render you keyboard useless.
Try to move in little steps and test your configuration continuously.
If things still went out of hand, try to clean up by loading a very generic configuration to make things working again.
Use something like: setxkbmap -model pc104 -layout us -option ""
.
It will reset you keyboard to the rescue setup featuring pc104
as a very common generic model with us
layout and empty options to discard the changes you just did.
Here is how the output will look on my machine after applying the “rescue” configuration:
Setting verbose level to 10
locale is C
Trying to load rules file ./rules/evdev...
Success.
Applied rules from evdev:
rules: evdev
model: pc104
layout: us
Trying to build keymap using the following components:
keycodes: evdev+aliases(qwerty)
types: complete
compat: complete
symbols: pc+us+inet(evdev)
geometry: pc(pc104)
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us+inet(evdev)" };
xkb_geometry { include "pc(pc104)" };
};
As you might guess, Emacs immediately stops recognizing my Alt key after applying this rescue configuration.
Persist Your XKB Configuration Between Login Session
Different
KDE
kxkbrc
Now when I have everything as desired it would be nice to avoid loosing this every time X server updates.
Well, you can not avoid it but minimizing the amount of steps needed to get your system back to your preferred setup is absolutely possible.
This can be easily scripted but I have never had time to commit to it and simply preserved the files with my configuration changes.
The file with options which is /usr/share/X11/xkb/symbols/art-mods
in my case is usually kept after updates unless you completely reinstall the system.
On the other hand file in /usr/share/X11/xkb/rules
are always overwritten which makes you system loose awareness of your options.
In my case I simply copy my variants back from my git versioned directory and it always worked well for me.
You can then run setxkbmap -option <you-options>
to apply them in the current session or simply re-login to have them applied on X Server restart.
XKBCOMP
Preserve configuration with Check XKB configuration: setxkbmap -print -verbose 10 https://wiki.archlinux.org/title/Xorg/Keyboard_configuration#Viewing_keyboard_settings i
XKBCOMP
xkbcomp $DISPLAY output.xkb xkbcomp input.xkb $DISPLAY