My Quest to Map <Win>+<Left> to <Home> on Wayland
The only arrow key configuration for laptop keyboard that makes sense is when:
- Fn+Left gives you the “Home” key
- Fn + Right gives you the “End” key
- Fn + Up gives you the “Page Up” key
- Fn + Down gives you the “Page Down” key
I have no idea why many laptop manufacturers don’t adopt that convention and put the Home/End/PgUp/PgDown keys in either easily accidentally clicked location or a location impossible to accurately access without carefully perusing the keyboard with your eyes.
While it’s impossible to remap the Fn key since it’s handled at firmware level, I got quite fond of Chromium OS’ convention of Mapping Win+Arrow Key to Home/End/PgUp/PgDown which made quite a lot of sense. I wanted to reproduce that in Linux. Given that Linux is ultra-customizable, I thought that it would be a simple job, but it turns out to be very complicated.
Naive Attempt
Linux’s method of customizing key layout is using XKB, which is a very cryptic config file. (Hey, it’s Linux-style!) First, I’ve tried editing /usr/share/X11/xkb/symbols/pc to add the arrow keys setup.
key <LEFT> {
type[Group1]= "PC_SUPER_LEVEL2",
symbols[Group1]= [ Left, Home ]
};
key <RGHT> {
type= "PC_SUPER_LEVEL2",
symbols[Group1]= [ Right, End ]
};
key <UP> {
type= "PC_SUPER_LEVEL2",
symbols[Group1]= [ Up, Prior ]
};
key <DOWN> {
type= "PC_SUPER_LEVEL2",
symbols[Group1]= [ Down, Next ]
};
After disabling Super-related shortcuts in GNOME, that config worked reasonably well in GTK-native applications, but it did not work in Chrome! That is because Chrome sees the “Super” modifier and decide to think that it’s a different shortcut. We must make Chrome not see “Super” somehow. (Note:
“Super” is a generic name for the “Windows” key.)
RedirectKey
If I were using X11, I could’ve used the “RedirectKey” action in XKB to remove the “Super” modifier when the shortcut is executed. Unfortunately, this option is no longer supported in Wayland.
Second Attempt
I tried mapping the “Windows” key to “Mod5” instead of “Super”, which Chrome hopefully will not care about.
key <LWIN> { [ISO_Level3_Shift ] };
modifier_map Mod5 { ISO_Level3_Shift }; key <LEFT> {
type[Group1]= "THREE_LEVEL",
symbols[Group1]= [ Left, Left, Home ]
};
key <RGHT> {
type= "THREE_LEVEL",
symbols[Group1]= [ Right, Right, End ]
};
key <UP> {
type= "THREE_LEVEL",
symbols[Group1]= [ Up, Up, Prior ]
};
key <DOWN> {
type= "THREE_LEVEL",
symbols[Group1]= [ Down, Down, Next ]
};
Now my setup works in Chrome. There is a disadvantage that shortcuts relying on “Super” no longer works in GNOME, but it’s possible to remap them
gsettings set org.gnome.mutter overlay-key 'ISO_Level3_Shift'
gsettings set org.gnome.settings-daemon.plugins.media-keys screensaver '<Mod5>l'
I thought I got everything working, but unfortunately not. I later found out that Shift+Super+Left doesn’t map to Shift+Home in Chrome. The use case is to highlight text until the beginning or end of the line.
Patching libX11
I have thought about many ways to solve this problem, including patching evdev in the Kernel and patching the xkbcommon library. Ultimately, I’ve found somewhere I can patch, the libX11 library.
This problem only occurs in Chrome and IntelliJ which are X11 applications running on XWayland and uses the libX11 library to get events, so if I hack the library to strip “Super” modifier flag, then everything should work. Thus, I came up with this patch.
diff --git a/src/XlibInt.c b/src/XlibInt.c
index 4e45e62b..afe382cb 100644
--- a/src/XlibInt.c
+++ b/src/XlibInt.c
@@ -915,6 +915,11 @@ _XWireToEvent(
ev->state = event->u.keyButtonPointer.state;
ev->same_screen = event->u.keyButtonPointer.sameScreen;
ev->keycode = event->u.u.detail;
+#define REMAP(from, to) if (ev->keycode == from && ev->state & 0x40) {ev->state = ev->state & ~0x40;ev->keycode=to;}
+ REMAP(114, 115);
+ REMAP(113, 110);
+ REMAP(111, 112);
+ REMAP(116, 117);
}
break;
case ButtonPress:
The gist of this patch is that if it sees an arrow key with the “Super” modifier from the X Server, it converts it to the respective Home/End/PgUp/PgDown key and strip the “Super” modifier. Now everything works!