{ config, lib, pkgs, ... }: let cfg = config.services.evremap; format = pkgs.formats.toml { }; key = lib.types.strMatching "KEY_[[:upper:]]+" // { description = "key ID prefixed with KEY_"; }; mkKeyOption = description: lib.mkOption { type = key; description = '' ${description} You can get a list of keys by running `evremap list-keys`. ''; }; mkKeySeqOption = description: (mkKeyOption description) // { type = lib.types.listOf key; }; dualRoleModule = lib.types.submodule { options = { input = mkKeyOption "The key that should be remapped."; hold = mkKeySeqOption "The key sequence that should be output when the input key is held."; tap = mkKeySeqOption "The key sequence that should be output when the input key is tapped."; }; }; remapModule = lib.types.submodule { options = { input = mkKeySeqOption "The key sequence that should be remapped."; output = mkKeySeqOption "The key sequence that should be output when the input sequence is entered."; }; }; in { options.services.evremap = { enable = lib.mkEnableOption "evremap, a keyboard input remapper for Linux/Wayland systems"; settings = lib.mkOption { type = lib.types.submodule { freeformType = format.type; options = { device_name = lib.mkOption { type = lib.types.str; example = "AT Translated Set 2 keyboard"; description = '' The name of the device that should be remapped. You can get a list of devices by running `evremap list-devices` with elevated permissions. ''; }; dual_role = lib.mkOption { type = lib.types.listOf dualRoleModule; default = [ ]; example = [ { input = "KEY_CAPSLOCK"; hold = [ "KEY_LEFTCTRL" ]; tap = [ "KEY_ESC" ]; } ]; description = '' List of dual-role remappings that output different key sequences based on whether the input key is held or tapped. ''; }; remap = lib.mkOption { type = lib.types.listOf remapModule; default = [ ]; example = [ { input = [ "KEY_LEFTALT" "KEY_UP" ]; output = [ "KEY_PAGEUP" ]; } ]; description = '' List of remappings. ''; }; }; }; description = '' Settings for evremap. See the [upstream documentation](https://github.com/wez/evremap/blob/master/README.md#configuration) for how to configure evremap. ''; default = { }; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [ pkgs.evremap ]; hardware.uinput.enable = true; systemd.services.evremap = { description = "evremap - keyboard input remapper"; wantedBy = [ "multi-user.target" ]; script = "${lib.getExe pkgs.evremap} remap ${format.generate "evremap.toml" cfg.settings}"; serviceConfig = { DynamicUser = true; User = "evremap"; SupplementaryGroups = [ config.users.groups.input.name config.users.groups.uinput.name ]; Restart = "on-failure"; RestartSec = 5; TimeoutSec = 20; # Hardening ProtectClock = true; ProtectKernelLogs = true; ProtectControlGroups = true; ProtectKernelModules = true; ProtectHostname = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProtectHome = true; ProcSubset = "pid"; PrivateTmp = true; PrivateNetwork = true; PrivateUsers = true; RestrictRealtime = true; RestrictNamespaces = true; RestrictAddressFamilies = "none"; MemoryDenyWriteExecute = true; LockPersonality = true; IPAddressDeny = "any"; AmbientCapabilities = ""; CapabilityBoundingSet = ""; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ]; UMask = "0027"; }; }; }; }