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?

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

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))

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

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..

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.

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!

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.

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
'';

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.

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.

I’m quite new to nix and NixOS, but your answer seems alright to me. At first I did it this way. But Minecraft takes keyboard input from a lower level API, and it doesn’t work correctly. So I used @zogstrip ‘s answer:

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

So, if you have a similar problem, this is a good answer too.

I mapped capslock to esc, and I was consulting ChatGPT while configuring my new machine, and suggested to cover generic keyboards, leaving it in my config as:

    services.udev.extraHwdb = ''
        evdev:atkbd:*
            KEYBOARD_KEY_3a=esc
            KEYBOARD_KEY_01=capslock

        evdev:input:b*v*p*:
            KEYBOARD_KEY_3a=esc
            KEYBOARD_KEY_01=capslock
    '';

I guess there’s a tradeoff here. using services.xserver.xkb is easier to configure, and you don’t need to worry about targeting the hardware specifically, but with services.udev you can rest assured that any program will be compatible, no matter if it’s X11 or Wayland. In my case, since I don’t expect to switch keyboards that often or have any weird ones, udev works perfectly.

I recently tried this and struggled a bit trying to avoid going deeper into a rabbit hole from additional tool layers, and I found this to work:

  programs.dconf = {
    enable = true;
    profiles.user.databases = [{
      settings."org/gnome/desktop/input-sources".xkb-options = [ "ctrl:nocaps" ];
    }];
  };

This is in the system configuration.nix, nixos 26.05

This is an old thread it seems, but did the keyd service already exist as a config option at the time? cause it does look like the service should have created the group, but if there wasn’t a service for it at the time then you probably could have tried creating a group manually. I use Guix and they don’t have a service module for keyd, but i just configured it manually by adding the group and declaratively adding the config to /etc, and creating a service to start the daemon. I personally really like it and was pretty simple to set up from my experience. I also use it for just a single key. I remap capslock to control because of Emacs :smiling_face_with_horns:

As many others on GNOME have hinted on dconf:

{
  # this is a home-manager option
  dconf.settings = {

    # map caps to escape
    "org/gnome/desktop/input-sources" = {
      xkb-options = [ "caps:escape" ];
    };
  };
}

Only works on GNOME though, and I’m sure there is a better way to do it in a more portable way, but this is essentially what the setting does in gnome-tweaks.