Best Way to Remap Caps Lock to Esc with Wayland

Hello! I am using NixOS 23.11 with Gnome, and according to Gnome Settings I’m using Wayland. I also use the Colemak keyboard layout.

By default, the Capslock key with my layout backspaces. I would prefer that it act as an ESC key. I don’t see a way to do this with Gnome keyboard settings. I have also tried the following command:

  • setxkbmap -option caps:escape

But I get an error saying that I’m running setxkbmap against an Xwayland server. Also it simply doesn’t work.

I tried using keyd (couldn’t get it to start due to group errors) and kanata also, but these seem like overkill and have a very large learning curve. All I want to do is remap a single key.

Does anyone know how to do this on a system that isn’t using Xwindows?

2 Likes

I love xremap: GitHub - k0kubun/xremap: Key remapper for X11 and Wayland

> cat .config/xremap/config.yml
modmap:
  - name: CapsLock to RightCtrl/Esc
    remap:
      CapsLock:
        held: Ctrl_R
        alone: Esc
        alone_timeout: 500

keymap:
  - name: RightCtrl+hjkl to Arrows
    remap:
      Ctrl_R-h: Left
      Ctrl_R-l: Right
      Ctrl_R-j: Down
      Ctrl_R-k: Up
  - name: RightCtrl + Esc to "~ and `"
    remap:
      Ctrl_R-Esc: Grave
      Ctrl_R-S-Esc: S-Grave
      Ctrl_R-Grave: Grave
      Ctrl_R-S-Grave: S-Grave
1 Like

Kanata is cross platform, and can do this and much more stuff (in particular, most similar linux tools don’t support chording (mapping simultaneous keypresses — for instance, I have mapped qw to esc))

1 Like

In GNOME, you should use xkb-options setting from the org.gnome.desktop.input-sources GSettings schema.

See Strange xkbOptions behavior (GNOME) - #5 by jtojnar for an example how to set GSettings keys directly in a dconf database using NixOS option.

You can also set the value in the xserver config, since that is needed if you want to apply it to TTY with console.useXkbConfig, and then refer to it within the dconf definitions similarly to how it is done here Setting caps lock as ctrl not working - #5 by jtojnar

1 Like

Kanata does look very cool. I am definitely putting it on my list of things to dive into when I have some more time.

Slightly different, since this does escape on tap and control on chord, but you might like it. This should be layout-neutral.

services.interception-tools =
  let
    itools = pkgs.interception-tools;
    itools-caps = pkgs.interception-tools-plugins.caps2esc;
  in
  {
    enable = true;
    plugins = [ itools-caps ];
    # requires explicit paths: https://github.com/NixOS/nixpkgs/issues/126681
    udevmonConfig = pkgs.lib.mkDefault ''
      - JOB: "${itools}/bin/intercept -g $DEVNODE | ${itools-caps}/bin/caps2esc -m 1 | ${itools}/bin/uinput -d $DEVNODE"
        DEVICE:
          EVENTS:
            EV_KEY: [KEY_CAPSLOCK, KEY_ESC]
    '';
  };

I set -m 1 here which only remaps caps lock, but there are a few different modes..

3 Likes

I used tools like setxkbmap in the past. They’re clunky and not always work, as they alter X11 events. I recommend Kmonad which alters kernel input events. It is much more configurable, supporting afaict arbitrary configurations, and is cross platform.

1 Like

That’s exactly why I recommend kanata! It very much feels like a spiritual successor to kmonad, which lacks some important features I’m interested in.

I was able to get Kanata to work! Here’s how I did it.

First, I added the following to my configuration.nix file:

environment.systemPackages = with pkgs; [
  ...
  kanata
  ...
];
  services.kanata = {
    enable = true;
    keyboards = {
      "logi".config = ''
(defsrc
  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
  caps a    s    d    f    g    h    j    k    l    ;    '    ret
  lsft z    x    c    v    b    n    m    ,    .    /    rsft
  lctl lmet lalt           spc            ralt rmet rctl
)

(deflayer colemak
  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
  tab  q    w    f    p    g    j    l    u    y    ;    [    ]    \
  esc  a    r    s    t    d    h    n    e    i    o    '    ret
  lsft z    x    c    v    b    k    m    ,    .    /    rsft
  lctl lmet lalt           spc            ralt rmet rctl
)
  '';
    };
  };

Logi is just an arbitrary name that I’m using since I have a Logitech keyboard.

I then ran sudo nixos-rebuild switch and now I’m able to remap my caps lock key as esc!

Here’s a few notes:

  • The colemak deflayer isn’t a true colemak keyboard. The only difference is that I replaced the caps key with esc.
  • Of course this didn’t work the first time :slight_smile: . I had a typo and nothing seemed to change on my keyboard after running nixos-rebuild. The good news is that the log file (that I could see with sudo journalctl -fu kanata-logi.service) did a good job telling me where the typo was.
  • I had to use the gnome keyboard switcher to switch to a US QWERTY layout first to make this work.

Thanks again for the suggestions and the help!

5 Likes

Yeah, one feature I miss from kmonad is config checking before the unit was actually restarted.

I found two steps missing to get this working for me:

Add your user to the uinput group:

users.users.your_username.extraGroups = [ "uinput" ];

Enable uinput with the hardware option:

hardware.uinput.enable = true;

These ensure proper permissions and kernel module loading for uinput when using Kanata.

2 Likes

I use this udev hwdb rule so that it’s changed system-wide

# Remap CAPS lock to ESC
services.udev.extraHwdb = ''
  evdev:atkbd:*
    KEYBOARD_KEY_3a=esc
'';
3 Likes

FYI, if anyone’s having permissions issues with uinput like the one below, there’s an open Github issue

Jun 04 21:36:31 midgar kanata[2788826]: 2024-06-04T21:36:31.672701327+02:00 [INFO] process unmapped keys: false
Jun 04 21:36:31 midgar kanata[2788826]: 2024-06-04T21:36:31.672745135+02:00 [INFO] NOTE: kanata was compiled to never allow cmd
Jun 04 21:36:31 midgar kanata[2788826]: 2024-06-04T21:36:31.672861387+02:00 [INFO] config file is valid
Jun 04 21:36:31 midgar kanata[2788826]: 2024-06-04T21:36:31.672889007+02:00 [ERROR] Failed to open the output uinput device. Make sure you've added the user executing kanata to the `uinput` group
Jun 04 21:36:31 midgar kanata[2788826]: 2024-06-04T21:36:31.672901284+02:00 [ERROR] Permission denied (os error 13)
Jun 04 21:36:31 midgar kanata[2788826]: Press enter to exit
Jun 04 21:36:31 midgar kanata[2788826]: Error: Permission denied (os error 13)
Jun 04 21:36:31 midgar systemd[1]: kanata-default.service: Main process exited, code=exited, status=1/FAILURE
Jun 04 21:36:31 midgar systemd[1]: kanata-default.service: Failed with result 'exit-code'.
Jun 04 21:36:31 midgar systemd[1]: Failed to start kanata-default.service.
1 Like

This worked for me, let me know if its not nix way of doing this.

# /etc/nixos/configuration.nix
{ config, pkgs, ... }:

{
  # ... other configurations ...
  # Enable the X11 windowing system.                                        
  services.xserver = {                                                       
        enable = true;                                                          
        xkb.options = "caps:swapescape";
  };                                                                           

  # ... rest of your configuration ...
}

xkb.options is traditionally an Xorg concept, modern Wayland compositors (like GNOME’s Mutter) often interpret and apply these xkbOptions settings correctly by parsing the XKB configuration. So, caps:swapescape should work as expected in your GNOME Wayland session.