Installation

May 5, 2026 · View on GitHub

The installation depends on whether you are using NixOS or home-manager.

Regardless of the chosen approach, the installation consists of:

  1. Importing this flake's module
  2. Configuring xremap

Note that flakes are required. If there is a demand for non-flake-based modules – please feel free to submit an issue.

Runnable demos

The flake comes with a set of self-contained NixOS configurations that can be run directly:

nix run github:xremap/nix-flake?dir=.dev#demo-gnome-user
nix run github:xremap/nix-flake?dir=.dev#demo-kde-user
nix run github:xremap/nix-flake?dir=.dev#demo-niri-user
nix run github:xremap/nix-flake?dir=.dev#demo-wlroots-hyprland
nix run github:xremap/nix-flake?dir=.dev#demo-wlroots-sway-user
nix run github:xremap/nix-flake?dir=.dev#demo-x11-system
nix run github:xremap/nix-flake?dir=.dev#demo-x11-user

NixOS

There are two main ways of running xremap -- as a system service or as a user service. Not all combinations of mode x desktop environment are supported:

ModeNo featuresKDEGnomeX11WlrootsNiriCosmic
System:heavy_check_mark::heavy_multiplication_x::heavy_multiplication_x::heavy_check_mark::heavy_multiplication_x::question::question:
User:heavy_check_mark::heavy_check_mark::heavy_check_mark::heavy_check_mark::heavy_check_mark::heavy_check_mark::question:
  • :heavy_check_mark: – tested, works
  • :heavy_multiplication_x: – not implemented
  • :question: – implemented, not tested

For all examples in this section you can copy the code into a random flake.nix on your machine and run it in a VM as nix run <path_to_flake>#nixosConfigurations.nixos.config.system.build.vm.

System module, no desktop environment

A very simple configuration that globally maps CapsLock to Escape and Ctrl+U to Page Up can look like this:

# flake.nix
{
  inputs.xremap-flake.url = "github:xremap/nix-flake";
  outputs = inputs@{ nixpkgs, ... }: {
    nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        inputs.xremap-flake.nixosModules.default
        /* This is effectively an inline module */
        {
          users.users.root.password = "hunter2";
          system.stateVersion = "24.05";

          services.xremap.enable = true;

          # Modmap for single key rebinds
          services.xremap.config.modmap = [
            {
              name = "Global";
              remap = { "CapsLock" = "Esc"; }; # globally remap CapsLock to Esc
            }
          ];

          # Keymap for key combo rebinds
          services.xremap.config.keymap = [
            {
              name = "Example ctrl-u > pageup rebind";
              remap = { "C-u" = "PAGEUP"; };
              # NOTE: no application-specific remaps work without features (see configuration)
            }
          ];
        }
      ];
    };
  };
}

User module

# flake.nix
{
  inputs.xremap-flake.url = "github:xremap/nix-flake";
  outputs = inputs@{ nixpkgs, ... }: {
    nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        inputs.xremap-flake.nixosModules.default
        /* This is effectively an inline module */
        {
          users.users.root.password = "hunter2";
          users.users.alice = {
            password = "hunter2";
            isNormalUser = true;
          };

          system.stateVersion = "24.05";
          # This configures the service to only run for a specific user
          services.xremap = {
            enable = true;
            /* NOTE: since this sample configuration does not have any DE, xremap needs to be started manually by systemctl --user start xremap */
            serviceMode = "user";
            userName = "alice";
          };
          # Modmap for single key rebinds
          services.xremap.config.modmap = [
            {
              name = "Global";
              remap = { "CapsLock" = "Esc"; }; # globally remap CapsLock to Esc
            }
          ];

          # Keymap for key combo rebinds
          services.xremap.config.keymap = [
            {
              name = "Example ctrl-u > pageup rebind";
              remap = { "C-u" = "PAGEUP"; };
            }
          ];
        }
      ];
    };
  };
}

Systemwide example with X feature for application-specific remaps

# flake.nix
{
  inputs.xremap-flake.url = "github:xremap/nix-flake";
  outputs = inputs@{ nixpkgs, ... }: {
    nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        inputs.xremap-flake.nixosModules.default
        /* This is effectively an inline module */
        (
          { pkgs, ... }:
          {
            users.users.root.password = "hunter2";
            users.users.alice = {
              password = "hunter2";
              isNormalUser = true;
              extraGroups = [ "wheel" ];
            };

            system.stateVersion = "24.05";

            services.xserver = {
              enable = true;
              desktopManager.xfce.enable = true; # xfce is just an example
            };
            environment.systemPackages = [ pkgs.kitty ];

            /* Run a single one-shot service that allows root's services to access user's X session */
            systemd.user.services.set-xhost = {
              description = "Run a one-shot command upon user login";
              path = [ pkgs.xorg.xhost ];
              wantedBy = [ "default.target" ];
              script = "xhost +SI:localuser:root";
              environment.DISPLAY = ":0.0"; # NOTE: This is hardcoded for this flake
            };

            /* Enable X11 feature support */
            services.xremap.withX11 = true;
            services.xremap.enable = true;
            # Modmap for single key rebinds
            services.xremap.config.modmap = [
              {
                name = "Global";
                remap = { "CapsLock" = "Esc"; }; # globally remap CapsLock to Esc
              }
            ];

            # Keymap for key combo rebinds
            services.xremap.config.keymap = [
              {
                name = "Example ctrl-u > pageup rebind, only for specific application";
                remap = { "C-u" = "PAGEUP"; };
                application.only = [ "kitty" ];
              }
            ];
          }
        )
      ];
    };
  };
}

Non-NixOS and home-manager

Since on non-NixOS configurations, the environment outside the user cannot be controlled (e.g. home-manager cannot add the user to uinput), additional steps may be needed. See upstream for more information.

Example configuration

{
  description = "Home-manager flake for xremap";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-24.11";

    home-manager.url = "github:nix-community/home-manager/release-24.11";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";

    xremap-flake.url = "github:xremap/nix-flake";
  };

  outputs = inputs@{ self, nixpkgs, home-manager, ... }:
    let
      lib = nixpkgs.lib;
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      ...
      homeConfigurations = {
        user = home-manager.lib.homeManagerConfiguration {
          inherit pkgs;
          modules = [
            inputs.xremap-flake.homeManagerModules.default
            {
              services.xremap = {
                enable = true;
                # Modmap for single key rebinds
                config.modmap = [{
                  name = "Global";
                  remap = { "CapsLock" = "Esc"; };
                }];

                # Keymap for key combo rebinds
                config.keymap = [{
                  name = "Example ctrl-u > pageup rebind";
                  remap = { "C-u" = "PAGEUP"; };
                }];
              };
            }
          ];
        };
      };
    };
}

Xremap service configuration

There are three categories of options:

  1. enable option; false by default. Set it to true to enable the service.

  2. Options that enable package features (support for X/Wayland) or service configuration.

    Feature flags are:

    • withWlroots, bool – whether to enable wlroots-based compositor support (Sway, Hyprland, etc.)
    • withGnome, bool – whether to enable Gnome support
    • withX11, bool – whether to enable X11 support
    • withKDE, bool – whether to enable KDE wayland support
    • withHypr, bool – whether to enable non-wlroots based Hyprland support
    • withNiri, bool – whether to enable Niri support
    • withCosmic, bool – whether to enable Cosmic support

    All of them are false by default, which means no application-specific remaps work as xremap does not know which application is being used.

    • serviceMode, str – whether to run as user ("user") or system ("system", default)
    • userName, str – Name of user logging into graphical session (not set by default)
    • userId, int – user under which IPC socket runs (1000 by default)
    • watch, bool – whether to watch for new devices (false by default)
    • mouse, bool – whether to watch for mice (false by default)
    • deviceNames, list of str, – list of devices to monitor (empty by default)
    • extraArgs, list of str, – list of arguments to provide for xremap (empty by default)
    • package – which package for xremap to use. Useful if you want to somehow override the flake-provided package.
    • debug – enables debug logging for xremap (off by default)
  3. Options that define xremap's config. See upstream for more options.

    They are defined in either services.xremap.config as a Nix attrset or in services.xremap.yamlConfig as raw YAML text.

Troubleshooting

Cannot launch applications

If there is a binding to launch an application that looks like this:

{
  name = "apps";
  remap.alt-f.launch = [ "pavucontrol" ];
}

But nothing happens, and you see this in the logs:

Jan 01 19:30:46 nix xremap[595413]: [2024-01-01T16:30:46Z DEBUG xremap::action_dispatcher] Running command: ["pavucontrol"]
Jan 01 19:30:46 nix xremap[650912]: [2024-01-01T16:30:46Z ERROR xremap::action_dispatcher] Error running command: Os { code: 2, kind: NotFound, message: "No such file or directory" }

This happens because of a discrepancy in $PATH variable between your environment and xremap service.

Three approaches to solve this:

  1. Use binds to something like ${lib.getExe pkgs.pavucontrol}
  2. (not applicable to all DEs) use the bind to call DE-specific utility, e.g. hyprctl exec <binary>. This way whatever UI customizations are present on the DE level will get propagated to launched binary.
  3. Add the relevant path to the xremap service. For example, if which pavucontrol returns /run/current-system/sw/bin/pavucontrol add systemd.services.xremap.path = [ "/run/current-system/sw" ]; (or systemd.user.services.xremap.path = [ "/run/current-system/sw" ]; if serviceMode = user) without /bin to your config. This approach also works for non-NixOS systems where you may have binaries in arbitrary locations. The downside is that if the binary were to disappear from the path, trying to launch it would fail.

(specific to X11) xremap starts but app-specific shortcuts are not working

If you are using X11 and have enabled withX11, but the application-specific keybinds do not work, check the logs (journalctl -u xremap).

Should the logs contain:

Failed to connect to X11: X11 setup failed: 'Authorization required, but no authorization protocol specified

You would need to run xhost +SI:localuser:root. One approach to do this is described in this example config.

Xremap service is not started automatically

Happens if serviceMode == "user" or when using home-manager module; typically because graphical-session.target is not launched automatically.

If you are using a desktop environment (Gnome, KDE, something wlroots-based, etc.) – make sure that the target is started.

If you intend to use xremap without a DE, change systemd.user.services.xremap.wantedBy to some target that starts after you log in or manually start systemctl --user start xremap

Xremap does not restart on config change

Not (yet?) implemented, see #49.

Something else

Feel free to submit an issue or reach out to the main contributor of the repo via any other means.