Pretty early into using Emacs, I switched my Caps Lock key to a Control key. I used my Super key (a.k.a. the “Windows” key) to generally control my window manager. But I didn’t really use my left control key anymore. So now I’ve repurposed that as a Hyper key.
NixOS settings
In NixOS, I set up a Hyper key by setting the following (important setting highlighted):
services.xserver.xkbOptions = "grp:switch,ctrl:swapcaps_hyper,compose:rctrl";
What ctrl:swapcaps_hyper
does is change your Caps Lock to a Control key and your Left Ctrl to a Hyper key.
To set this immediately under X11, you can do something like:
setxkbmap -option "grp:alt_space_toggle,ctrl:swapcaps_hyper,compose:rctrl"
Under Wayland, though, your keyboard settings are controlled by your compositor.
If you’re using GNOME, you can modify your preferences on the fly by editing the dconf
setting for /org/gnome/desktop/input-sources/xkb-options
.
Normally you’d access these settings through gnome-tweaks
and clicking some preferences under the Keyboard section.
But you can also set this manually in a GUI with dconf-editor
or through a command line with:
dconf write /org/gnome/desktop/input-sources/xkb-options "['grp:switch', 'ctrl:swapcaps_hyper', 'compose:rctrl']"
Hyper key issue NixOS 23.11 Gnome 45
TL;DR: As of 2023-12-11 I solved this by patching xkeyboard-config
to add an option where Hyper is bound to Mod3.
Issues were caused by a change in xkeyboard-config
where the semantics of the new ctrl:hyper_capscontrol
option weren’t the same as the previous ctrl:swapcaps_hyper
option.
Previously it mapped Hyper to Mod3, but it was changed to Mod4 to be more consistent with other settings.
This change seems good, but probably broke a tiny subset of people, and that included me.
My settings above worked for a while, but there seems to be some issue with it when I upgraded to NixOS 23.11, running GNOME 45. When I press Left Control right now, it seems to act like a Super key rather than a Hyper key. For example, pressing Left Control+A brings up GNOME Activites, just like Super+A would. I’m currently encountering this on 2023-12-10. So I’m going to try to pinpoint the problem.
ctrl:swapcaps_hyper
definitely does something
Switching to ctrl:swapcaps
by using dconf write /org/gnome/desktop/input-sources/xkb-options "['grp:switch', 'ctrl:swapcaps', 'compose:rctrl']"
, I check what happens under xev
.
When pressing Left_Control I see:
KeyPress event, serial 40, synthetic NO, window 0x600001, root 0x248, subw 0x0, time 51029838, (162,-12), root:(589,213), state 0x2002, keycode 37 (keysym 0xffe5, Caps_Lock), same_screen YES, XLookupString gives 0 bytes: XmbLookupString gives 0 bytes: XFilterEvent returns: False
Then when I switch to ctrl:swapcaps_hyper
and press Left_Control under xev
I see:
KeyPress event, serial 38, synthetic NO, window 0x600001, root 0x248, subw 0x0, time 51295901, (160,-16), root:(540,347), state 0x2000, keycode 37 (keysym 0xffed, Hyper_L), same_screen YES, XLookupString gives 0 bytes: XmbLookupString gives 0 bytes: XFilterEvent returns: False
Which is good. The setting is being changed. And yet when I press Left_Control+A, I still get Activites popping up. And when I press Left_Control+Z (where Super+Z is unbound) in Emacs, I see:
s-z is undefined
Which means Emacs sees this as a Super key.
But I’m also running emacs-pgtk
.
I wonder if Emacs under X11 (or Xwayland at least) would register this differently.
Running emacs -nw
under Alacritty (Wayland client) and pressing Left_Control+Z, Emacs doesn’t even show any modifier key is being pressed.
The same thing happens with emacs -nw
under xterm
(X11 client).
I wonder if GTK or GDK is somehow filtering Hyper or Mod4.
It might be that “Hyper” is recognized, but it isn’t recognized as “Mod4”.
Looking at X keyboard extension - ArchWiki and trying more xev -event keyboard
, I notice the following when I press different modifiers with Z:
- Caps_Lock+Z
-
state 0x2004, keycode 52 (keysym 0x7a, z), same_screen YES,
- Left_Alt+Z
-
state 0x2008, keycode 52 (keysym 0x7a, z), same_screen YES,
- “Windows”/Super+L
-
state 0x2040, keycode 52 (keysym 0x7a, z), same_screen YES,
- Left_Control+Z
-
state 0x2040, keycode 52 (keysym 0x7a, z), same_screen YES,
It looks like my Hyper is being mapped to the same modifier as Super.
Setting Hyper to be a different modifier
Yep, looking at the output of xmodmap -pm
, I see:
xmodmap: up to 5 keys per modifier, (keycodes in parentheses): shift Shift_L (0x32), Shift_R (0x3e) lock control Control_L (0x42) mod1 Alt_L (0x40), Alt_L (0xcc), Meta_L (0xcd) mod2 Num_Lock (0x4d) mod3 ISO_Level5_Shift (0xcb) mod4 Hyper_L (0x25), Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf) mod5 ISO_Level3_Shift (0x5c)
So Hyper and Super are mapped to the same modifier.
I try setting this with xmodmap
:
xmodmap -e "remove mod4 = Hyper_L" -e "add mod3 = Hyper_L"
And now I see
xmodmap: up to 3 keys per modifier, (keycodes in parentheses): shift Shift_L (0x32), Shift_R (0x3e) lock control Control_L (0x42) mod1 Alt_L (0x40), Alt_L (0xcc), Meta_L (0xcd) mod2 Num_Lock (0x4d) mod3 Hyper_L (0x25), ISO_Level5_Shift (0xcb), Hyper_L (0xcf) mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce) mod5 ISO_Level3_Shift (0x5c)
And yet that doesn’t seem to actually change anything with xev
or other keys.
Trying out wev
(for Wayland events), I see:
[14: wl_keyboard] key: serial: 50399; time: 55028622; key: 37; state: 1 (pressed) sym: Hyper_L (65517), utf8: '' [14: wl_keyboard] modifiers: serial: 1; group: 0 depressed: 00002040: Mod4 Super latched: 00000000 locked: 00000000
One thing I noticed while looking at localectl list-x11-keymap-options
is that it seems like the “ctrl:swapcaps_hyper
” might have been renamed to “ctrl:hyper_capscontrol
”.
The meaning of ctrl:swapcaps_hyper
changed
Aha.
I found a libera.chat IRC log that pointed to xkeyboard-config issue #344, where they talk about Hyper inconsistently being Mod3 for ctrl:swapcaps_hyper
where in other places it’s Mod4.
Commit 7124dd51 changes hyper_capscontrol
to be Mod4.
Unfortunately this doesn’t work for me.
But helpfully, the first comment of issue #344 points these few users like me to set a custom layout.
So that I shall do.
Custom layout
NixOS has a services.xserver.xkb.extraLayouts
setting that allows an override.
I try to make my own custom configuration:
services.xserver.xkb.extraLayouts = {
jmmcustom = {
description = "JMM’s custom xkb layout for the hyper key.";
languages = [ "eng" ];
symbolsFile = ./files/jmm_xkb_custom;
};
};
But the first build fails since there’s a conflict with my services.xserver.exportConfiguration = true;
setting.
So I comment that out.
Then my build fails with
mesonConfigurePhase flags: --prefix=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40 --libdir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/lib --libexecdir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/libexec --bindir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/bin --sbindir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/sbin --includedir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/include --mandir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/share/man --infodir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/share/info --localedir=/nix/store/0sw5ma2syx2xmz5y92haay0dy3sabp8z-xkeyboard-config-2.40/share/locale -Dauto_features=enabled -Dwrap_mode=nodownload --buildtype=plain -Dxorg-rules-symlinks=true /nix/store/qrmvf49dbs7yqshymnqdhaiig0mcljlz-meson-1.2.3/nix-support/setup-hook: line 3: 41 Bus error (core dumped) meson setup build "${flagsArray[@]}"
Maybe this is related to NIX_HARDENING_ENABLE
?
Or it’s related to:
"mesonFlags": "-Dxorg-rules-symlinks=true",
It’s not that mesonFlags
setting. I overrode it and it still failed to build.
It’s something with my “meson
”, which is just getting a Bus error.
$ nix-store --verify-path $(nix-store -qR /nix/store/qrmvf49dbs7yqshymnqdhaiig0mcljlz-meson-1.2.3/bin/meson) path '/nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6' was modified! expected hash 'sha256:1x4h04h2q757fqy786v78x1jbk6a7dcrbysa6zn00zzpzi1mz7yr', got 'sha256:0rb84gyvfpjhb2rgz2pzdnhj57ikqs8j9fk8k9gll0gd6yvwhwi8'
Trying to repair.
$ nix-store --repair-path /nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6 error: operation 'repairPath' is not supported by store 'daemon' $ sudo nix-store --repair-path /nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6 copying path '/nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6' from 'https://cache.nixos.org'... warning: removing corrupted link '/nix/store/.links/1gzxz7nqambfscpsyixp7kqkn5z0h6k8a9z9vxpz5fwimdjrdy9s' warning: There may be more corrupted paths. You should run `nix-store --verify --check-contents --repair` to fix them all
Building now works.
Patch overlay
Okay, rather than making my own layout (which didn’t work well), I just decided to add my own xkb option that’s a modified version of ctrl:hyper_capscontrol
with Mod3 instead of Mod4.
See xkeyboard-config-hyper.patch for a patch I made that defines an option ctrl:jmm_hyper_capscontrol
.
I then have an overlay with a patched xkeyboardconfig
:
self: super:
{
jmm_xkb_patched = self.pkgs.xorg.xkeyboardconfig.overrideAttrs (old: {
patches = [ ./xkeyboard-config-hyper.patch ];
});
}
Then I set the following two configuration settings in my configuration.nix
:
services.xserver.xkb.dir = "${pkgs.jmm_xkb_patched}/etc/X11/xkb";
environment.sessionVariables.XKB_CONFIG_ROOT = config.services.xserver.xkb.dir;
I had to restart display-manager.service
, but after that everything worked as before.
Emacs now recognizes Left_Control as Hyper, and it no longer triggers GNOME Activities.