diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index be3adc4d3be..79f856a4093 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -133,6 +133,14 @@
services.infnoise.
+
+
+ kanata,
+ a tool to improve keyboard comfort and usability with advanced
+ customization. Available as
+ services.kanata.
+
+
persistent-evdev,
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 3f9afe13f1d..607da187b89 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -58,6 +58,10 @@ In addition to numerous new and upgraded packages, this release has the followin
- [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle.
Available as [services.infnoise](options.html#opt-services.infnoise.enable).
+
+- [kanata](https://github.com/jtroo/kanata), a tool to improve keyboard comfort and usability with advanced customization.
+ Available as [services.kanata](options.html#opt-services.kanata.enable).
+
- [persistent-evdev](https://github.com/aiberia/persistent-evdev), a daemon to add virtual proxy devices that mirror a physical input device but persist even if the underlying hardware is hot-plugged. Available as [services.persistent-evdev](#opt-services.persistent-evdev.enable).
- [schleuder](https://schleuder.org/), a mailing list manager with PGP support. Enable using [services.schleuder](#opt-services.schleuder.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 3010a213705..d961e2f683b 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -447,6 +447,7 @@
./services/hardware/interception-tools.nix
./services/hardware/irqbalance.nix
./services/hardware/joycond.nix
+ ./services/hardware/kanata.nix
./services/hardware/lcd.nix
./services/hardware/lirc.nix
./services/hardware/nvidia-optimus.nix
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
new file mode 100644
index 00000000000..f8250afa4a0
--- /dev/null
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.kanata;
+
+ keyboard = {
+ options = {
+ device = mkOption {
+ type = types.str;
+ example = "/dev/input/by-id/usb-0000_0000-event-kbd";
+ description = "Path to the keyboard device.";
+ };
+ config = mkOption {
+ type = types.lines;
+ example = ''
+ (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 qwerty
+ grv 1 2 3 4 5 6 7 8 9 0 - = bspc
+ tab q w e r t y u i o p [ ] \
+ @cap 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)
+
+ (defalias
+ ;; tap within 100ms for capslk, hold more than 100ms for lctl
+ cap (tap-hold 100 100 caps lctl))
+ '';
+ description = ''
+ Configuration other than defcfg.
+ See for more information.
+ '';
+ };
+ extraDefCfg = mkOption {
+ type = types.lines;
+ default = "";
+ example = "danger-enable-cmd yes";
+ description = ''
+ Configuration of defcfg other than linux-dev.
+ See for more information.
+ '';
+ };
+ };
+ };
+
+ mkName = name: "kanata-${name}";
+
+ mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
+ (defcfg
+ ${keyboard.extraDefCfg}
+ linux-dev ${keyboard.device})
+
+ ${keyboard.config}
+ '';
+
+ mkService = name: keyboard: nameValuePair (mkName name) {
+ description = "kanata for ${keyboard.device}";
+
+ # Because path units are used to activate service units, which
+ # will start the old stopped services during "nixos-rebuild
+ # switch", stopIfChanged here is a workaround to make sure new
+ # services are running after "nixos-rebuild switch".
+ stopIfChanged = false;
+
+ serviceConfig = {
+ ExecStart = ''
+ ${cfg.package}/bin/kanata \
+ --cfg ${mkConfig name keyboard}
+ '';
+
+ DynamicUser = true;
+ SupplementaryGroups = with config.users.groups; [
+ input.name
+ uinput.name
+ ];
+
+ # hardening
+ DeviceAllow = [
+ "/dev/uinput w"
+ "char-input r"
+ ];
+ CapabilityBoundingSet = "";
+ DevicePolicy = "closed";
+ IPAddressDeny = "any";
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ PrivateNetwork = true;
+ PrivateUsers = true;
+ ProcSubset = "pid";
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectProc = "invisible";
+ RestrictAddressFamilies = "none";
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ SystemCallArchitectures = "native";
+ SystemCallFilter = [
+ "@system-service"
+ "~@privileged"
+ "~@resources"
+ ];
+ UMask = "0077";
+ };
+ };
+
+ mkPath = name: keyboard: nameValuePair (mkName name) {
+ description = "kanata trigger for ${keyboard.device}";
+ wantedBy = [ "multi-user.target" ];
+ pathConfig = {
+ PathExists = keyboard.device;
+ };
+ };
+in
+{
+ options.services.kanata = {
+ enable = mkEnableOption "kanata";
+ package = mkOption {
+ type = types.package;
+ default = pkgs.kanata;
+ defaultText = lib.literalExpression "pkgs.kanata";
+ example = lib.literalExpression "pkgs.kanata-with-cmd";
+ description = ''
+ kanata package to use.
+ If you enable danger-enable-cmd, pkgs.kanata-with-cmd should be used.
+ '';
+ };
+ keyboards = mkOption {
+ type = types.attrsOf (types.submodule keyboard);
+ default = { };
+ description = "Keyboard configurations.";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ hardware.uinput.enable = true;
+
+ systemd = {
+ paths = mapAttrs' mkPath cfg.keyboards;
+ services = mapAttrs' mkService cfg.keyboards;
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ linj ];
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 9dab8dd99ab..00d41f48949 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -1231,6 +1231,8 @@ with pkgs;
kanata = callPackage ../tools/system/kanata { };
+ kanata-with-cmd = callPackage ../tools/system/kanata { withCmd = true; };
+
ksnip = libsForQt5.callPackage ../tools/misc/ksnip { };
kubevirt = callPackage ../tools/virtualization/kubevirt { };