{ config, lib, pkgs, ... }: let cfg = config.services.keyd; keyboardOptions = { ... }: { options = { ids = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "*" ]; example = [ "*" "-0123:0456" ]; description = '' Device identifiers, as shown by {manpage}`keyd(1)`. ''; }; settings = lib.mkOption { type = (pkgs.formats.ini { }).type; default = { }; example = { main = { capslock = "overload(control, esc)"; rightalt = "layer(rightalt)"; }; rightalt = { j = "down"; k = "up"; h = "left"; l = "right"; }; }; description = '' Configuration, except `ids` section, that is written to {file}`/etc/keyd/.conf`. Appropriate names can be used to write non-alpha keys, for example "equal" instead of "=" sign (see ). See how to configure. ''; }; extraConfig = lib.mkOption { type = lib.types.lines; default = ""; example = '' [control+shift] h = left ''; description = '' Extra configuration that is appended to the end of the file. **Do not** write `ids` section here, use a separate option for it. You can use this option to define compound layers that must always be defined after the layer they are comprised. ''; }; }; }; in { imports = [ (lib.mkRemovedOptionModule [ "services" "keyd" "ids" ] ''Use keyboards..ids instead. If you don't need a multi-file configuration, just add keyboards.default before the ids. See https://github.com/NixOS/nixpkgs/pull/243271.'') (lib.mkRemovedOptionModule [ "services" "keyd" "settings" ] ''Use keyboards..settings instead. If you don't need a multi-file configuration, just add keyboards.default before the settings. See https://github.com/NixOS/nixpkgs/pull/243271.'') ]; options.services.keyd = { enable = lib.mkEnableOption "keyd, a key remapping daemon"; keyboards = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule keyboardOptions); default = { }; example = lib.literalExpression '' { default = { ids = [ "*" ]; settings = { main = { capslock = "overload(control, esc)"; }; }; }; externalKeyboard = { ids = [ "1ea7:0907" ]; settings = { main = { esc = capslock; }; }; }; } ''; description = '' Configuration for one or more device IDs. Corresponding files in the /etc/keyd/ directory are created according to the name of the keys (like `default` or `externalKeyboard`). ''; }; }; config = lib.mkIf cfg.enable { # Creates separate files in the `/etc/keyd/` directory for each key in the dictionary environment.etc = lib.mapAttrs' (name: options: lib.nameValuePair "keyd/${name}.conf" { text = '' [ids] ${lib.concatStringsSep "\n" options.ids} ${lib.generators.toINI {} options.settings} ${options.extraConfig} ''; }) cfg.keyboards; hardware.uinput.enable = lib.mkDefault true; systemd.services.keyd = { description = "Keyd remapping daemon"; documentation = [ "man:keyd(1)" ]; wantedBy = [ "multi-user.target" ]; restartTriggers = lib.mapAttrsToList (name: options: config.environment.etc."keyd/${name}.conf".source ) cfg.keyboards; # this is configurable in 2.4.2, later versions seem to remove this option. # post-2.4.2 may need to set makeFlags in the derivation: # # makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ]; environment.KEYD_SOCKET = "/run/keyd/keyd.sock"; serviceConfig = { ExecStart = "${pkgs.keyd}/bin/keyd"; Restart = "always"; # TODO investigate why it doesn't work propeprly with DynamicUser # See issue: https://github.com/NixOS/nixpkgs/issues/226346 # DynamicUser = true; SupplementaryGroups = [ config.users.groups.input.name config.users.groups.uinput.name ]; RuntimeDirectory = "keyd"; # Hardening CapabilityBoundingSet = [ "CAP_SYS_NICE" ]; DeviceAllow = [ "char-input rw" "/dev/uinput rw" ]; ProtectClock = true; PrivateNetwork = true; ProtectHome = true; ProtectHostname = true; PrivateUsers = false; PrivateMounts = true; PrivateTmp = true; RestrictNamespaces = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; MemoryDenyWriteExecute = true; RestrictRealtime = true; LockPersonality = true; ProtectProc = "invisible"; SystemCallFilter = [ "nice" "@system-service" "~@privileged" ]; RestrictAddressFamilies = [ "AF_UNIX" ]; RestrictSUIDSGID = true; IPAddressDeny = [ "any" ]; NoNewPrivileges = true; ProtectSystem = "strict"; ProcSubset = "pid"; UMask = "0077"; }; }; }; }