1 { config, lib, pkgs, utils, ... }:
6 cfg = config.services.kanata;
11 type = types.addCheck (types.listOf types.str)
12 (devices: (length devices) > 0);
13 example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
14 # TODO replace note with tip, which has not been implemented yet in
15 # nixos/lib/make-options-doc/mergeJSON.py
16 description = mdDoc ''
17 Paths to keyboard devices.
20 To avoid unnecessary triggers of the service unit, unplug devices in
21 the order of the list.
29 grv 1 2 3 4 5 6 7 8 9 0 - = bspc
30 tab q w e r t y u i o p [ ] \
31 caps a s d f g h j k l ; ' ret
32 lsft z x c v b n m , . / rsft
33 lctl lmet lalt spc ralt rmet rctl)
36 grv 1 2 3 4 5 6 7 8 9 0 - = bspc
37 tab q w e r t y u i o p [ ] \
38 @cap a s d f g h j k l ; ' ret
39 lsft z x c v b n m , . / rsft
40 lctl lmet lalt spc ralt rmet rctl)
43 ;; tap within 100ms for capslk, hold more than 100ms for lctl
44 cap (tap-hold 100 100 caps lctl))
46 description = mdDoc ''
47 Configuration other than `defcfg`. See [example config
48 files](https://github.com/jtroo/kanata) for more information.
51 extraDefCfg = mkOption {
54 example = "danger-enable-cmd yes";
55 description = mdDoc ''
56 Configuration of `defcfg` other than `linux-dev`. See [example
57 config files](https://github.com/jtroo/kanata) for more information.
60 extraArgs = mkOption {
61 type = types.listOf types.str;
63 description = mdDoc "Extra command line arguments passed to kanata.";
66 type = types.nullOr types.port;
69 description = mdDoc ''
70 Port to run the notification server on. `null` will not run the
77 mkName = name: "kanata-${name}";
79 mkDevices = devices: concatStringsSep ":" devices;
81 mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
83 ${keyboard.extraDefCfg}
84 linux-dev ${mkDevices keyboard.devices})
89 mkService = name: keyboard: nameValuePair (mkName name) {
90 description = "kanata for ${mkDevices keyboard.devices}";
92 # Because path units are used to activate service units, which
93 # will start the old stopped services during "nixos-rebuild
94 # switch", stopIfChanged here is a workaround to make sure new
95 # services are running after "nixos-rebuild switch".
96 stopIfChanged = false;
100 ${cfg.package}/bin/kanata \
101 --cfg ${mkConfig name keyboard} \
102 --symlink-path ''${RUNTIME_DIRECTORY}/${name} \
103 ${optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
104 ${utils.escapeSystemdExecArgs keyboard.extraArgs}
108 RuntimeDirectory = mkName name;
109 SupplementaryGroups = with config.users.groups; [
119 CapabilityBoundingSet = [ "" ];
120 DevicePolicy = "closed";
121 IPAddressAllow = optional (keyboard.port != null) "localhost";
122 IPAddressDeny = [ "any" ];
123 LockPersonality = true;
124 MemoryDenyWriteExecute = true;
125 PrivateNetwork = keyboard.port == null;
129 ProtectControlGroups = true;
131 ProtectHostname = true;
132 ProtectKernelLogs = true;
133 ProtectKernelModules = true;
134 ProtectKernelTunables = true;
135 ProtectProc = "invisible";
136 RestrictAddressFamilies =
137 if (keyboard.port == null) then "none" else [ "AF_INET" ];
138 RestrictNamespaces = true;
139 RestrictRealtime = true;
140 SystemCallArchitectures = [ "native" ];
150 mkPathName = i: name: "${mkName name}-${toString i}";
152 mkPath = name: n: i: device:
153 nameValuePair (mkPathName i name) {
155 "${toString (i+1)}/${toString n} kanata trigger for ${name}, watching ${device}";
156 wantedBy = optional (i == 0) "multi-user.target";
159 # (ab)use systemd.path to construct a trigger chain so that the
160 # service unit is only started when all paths exist
161 # however, manual of systemd.path says Unit's suffix is not ".path"
164 then "${mkName name}.service"
165 else "${mkPathName (i + 1) name}.path";
167 unitConfig.StopPropagatedFrom = optional (i > 0) "${mkName name}.service";
170 mkPaths = name: keyboard:
172 n = length keyboard.devices;
174 imap0 (mkPath name n) keyboard.devices
178 options.services.kanata = {
179 enable = mkEnableOption (lib.mdDoc "kanata");
181 type = types.package;
182 default = pkgs.kanata;
183 defaultText = literalExpression "pkgs.kanata";
184 example = literalExpression "pkgs.kanata-with-cmd";
185 description = mdDoc ''
186 The kanata package to use.
189 If `danger-enable-cmd` is enabled in any of the keyboards, the
190 `kanata-with-cmd` package should be used.
194 keyboards = mkOption {
195 type = types.attrsOf (types.submodule keyboard);
197 description = mdDoc "Keyboard configurations.";
201 config = mkIf cfg.enable {
202 hardware.uinput.enable = true;
205 paths = trivial.pipe cfg.keyboards [
206 (mapAttrsToList mkPaths)
210 services = mapAttrs' mkService cfg.keyboards;
214 meta.maintainers = with maintainers; [ linj ];