From 4c225e2cf2e2d2039d2f15547e916176e23901c1 Mon Sep 17 00:00:00 2001 From: Nickiel12 Date: Sat, 8 Oct 2022 16:25:40 -0700 Subject: [PATCH] added kmonad module --- configuration.nix | 7 +- flake.lock | 33 +++++--- kmonad-module.nix | 186 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 kmonad-module.nix diff --git a/configuration.nix b/configuration.nix index d5e0124..461a2ce 100644 --- a/configuration.nix +++ b/configuration.nix @@ -8,8 +8,11 @@ imports = [ # Include the results of the hardware scan. ./hardware-configuration.nix + ./kmonad-module.nix ]; + # nixpkgs.config.allowBroken = true; + boot.loader.systemd-boot.configurationLimit = 5; boot.loader = { efi = { @@ -51,6 +54,7 @@ # Configure keymap in X11 services.xserver.layout = "us"; services.xserver.xkbOptions = "caps:super"; + services.kmonad.enable = true; nixpkgs.config.allowUnfree = true; @@ -90,7 +94,6 @@ # List packages installed in system profile. To search, run: # $ nix search wget environment.systemPackages = with pkgs; [ - haskellPackages.kmonad vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. # wget ]; @@ -105,7 +108,7 @@ }; nix = { - package = pkgs.nixFlakes; + package = pkgs.nixVersions.stable; extraOptions = "experimental-features = nix-command flakes"; }; diff --git a/flake.lock b/flake.lock index c6284c3..6a8dfd7 100644 --- a/flake.lock +++ b/flake.lock @@ -4,35 +4,35 @@ "inputs": { "nixpkgs": [ "nixpkgs" - ] + ], + "utils": "utils" }, "locked": { - "lastModified": 1656169755, - "narHash": "sha256-Nlnm4jeQWEGjYrE6hxi/7HYHjBSZ/E0RtjCYifnNsWk=", + "lastModified": 1665259915, + "narHash": "sha256-hHfint8/thFWQWk5P1erLM1y8Gwsk2COc3aYQaF1Evs=", "owner": "nix-community", "repo": "home-manager", - "rev": "4a3d01fb53f52ac83194081272795aa4612c2381", + "rev": "bd87a34bb487a655e1282528a70d0d6b10585a37", "type": "github" }, "original": { "owner": "nix-community", - "ref": "release-22.05", "repo": "home-manager", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1653936696, - "narHash": "sha256-M6bJShji9AIDZ7Kh7CPwPBPb/T7RiVev2PAcOi4fxDQ=", + "lastModified": 1665081174, + "narHash": "sha256-6hsmzdhdy8Kbvl5e0xZNE83pW3fKQvNiobJkM6KQrgA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ce6aa13369b667ac2542593170993504932eb836", + "rev": "598f83ebeb2235435189cf84d844b8b73e858e0f", "type": "github" }, "original": { "owner": "nixos", - "ref": "22.05", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -42,6 +42,21 @@ "home-manager": "home-manager", "nixpkgs": "nixpkgs" } + }, + "utils": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } } }, "root": "root", diff --git a/kmonad-module.nix b/kmonad-module.nix new file mode 100644 index 0000000..643ff94 --- /dev/null +++ b/kmonad-module.nix @@ -0,0 +1,186 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.kmonad; + + # Per-keyboard options: + keyboard = { name, ... }: { + options = { + name = lib.mkOption { + type = lib.types.str; + example = "laptop-internal"; + description = "Keyboard name."; + }; + + device = lib.mkOption { + type = lib.types.path; + example = "/dev/input/by-id/some-dev"; + description = "Path to the keyboard's device file."; + }; + + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "openrazer" ]; + description = '' + Extra permission groups to attach to the KMonad instance for + this keyboard. + + Since KMonad runs as an unprivileged user, it may sometimes + need extra permissions in order to read the keyboard device + file. If your keyboard's device file isn't in the input + group you'll need to list its group in this option. + ''; + }; + + defcfg = { + enable = lib.mkEnableOption '' + Automatically generate the defcfg block. + + When this is option is set to true the config option for + this keyboard should not include a defcfg block. + ''; + + compose = { + key = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "ralt"; + description = "The (optional) compose key to use."; + }; + + delay = lib.mkOption { + type = lib.types.int; + default = 5; + description = "The delay (in milliseconds) between compose key sequences."; + }; + }; + + fallthrough = lib.mkEnableOption "Reemit unhandled key events."; + + allowCommands = lib.mkEnableOption "Allow keys to run shell commands."; + }; + + config = lib.mkOption { + type = lib.types.lines; + description = "Keyboard configuration."; + }; + }; + + config = { + name = lib.mkDefault name; + }; + }; + + # Create a complete KMonad configuration file: + mkCfg = keyboard: + let defcfg = '' + (defcfg + input (device-file "${keyboard.device}") + output (uinput-sink "kmonad-${keyboard.name}") + '' + + lib.optionalString (keyboard.defcfg.compose.key != null) '' + cmp-seq ${keyboard.defcfg.compose.key} + cmp-seq-delay ${toString keyboard.defcfg.compose.delay} + '' + '' + fallthrough ${lib.boolToString keyboard.defcfg.fallthrough} + allow-cmd ${lib.boolToString keyboard.defcfg.allowCommands} + ) + ''; + in + pkgs.writeTextFile { + name = "kmonad-${keyboard.name}.cfg"; + text = lib.optionalString keyboard.defcfg.enable (defcfg + "\n") + keyboard.config; + checkPhase = "${cfg.package}/bin/kmonad -d $out"; + }; + + # Build a systemd path config that starts the service below when a + # keyboard device appears: + mkPath = keyboard: rec { + name = "kmonad-${keyboard.name}"; + value = { + description = "KMonad trigger for ${keyboard.device}"; + wantedBy = [ "default.target" ]; + pathConfig.Unit = "${name}.service"; + pathConfig.PathExists = keyboard.device; + }; + }; + + # Build a systemd service that starts KMonad: + mkService = keyboard: + let + cmd = [ + "${cfg.package}/bin/kmonad" + "--input" + ''device-file "${keyboard.device}"'' + ] ++ cfg.extraArgs ++ [ + "${mkCfg keyboard}" + ]; + + groups = [ + "input" + "uinput" + ] ++ keyboard.extraGroups; + in + { + name = "kmonad-${keyboard.name}"; + value = { + description = "KMonad for ${keyboard.device}"; + script = lib.escapeShellArgs cmd; + serviceConfig.Restart = "no"; + serviceConfig.User = "kmonad"; + serviceConfig.SupplementaryGroups = groups; + serviceConfig.Nice = -20; + }; + }; +in +{ + options.services.kmonad = { + enable = lib.mkEnableOption "KMonad: An advanced keyboard manager."; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.kmonad; + example = "pkgs.haskellPackages.kmonad"; + description = "The KMonad package to use."; + }; + + keyboards = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule keyboard); + default = { }; + description = "Keyboard configuration."; + }; + + extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "--log-level" "debug" ]; + description = "Extra arguments to pass to KMonad."; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + users.groups.uinput = { }; + users.groups.kmonad = { }; + + users.users.kmonad = { + description = "KMonad system user."; + group = "kmonad"; + isSystemUser = true; + }; + + services.udev.extraRules = '' + # KMonad user access to /dev/uinput + KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput" + ''; + + systemd.paths = + builtins.listToAttrs + (map mkPath (builtins.attrValues cfg.keyboards)); + + systemd.services = + builtins.listToAttrs + (map mkService (builtins.attrValues cfg.keyboards)); + }; +}