diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b6ffddc6f..4d49c32d1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -272,6 +272,9 @@ /modules/services/grobi.nix @mbrgm +/modules/services/gromit-mpx.nix @pjones +/tests/modules/services/gromit-mpx @pjones + /modules/services/hound.nix @adisbladis /modules/services/imapnotify.nix @nickhu diff --git a/modules/misc/news.nix b/modules/misc/news.nix index bd376ec38..15297915d 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -2304,6 +2304,14 @@ in A new module is available: 'programs.navi'. ''; } + + { + time = "2021-12-11T16:07:00+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'services.gromit-mpx'. + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index 64905b12d..6d7ea4a7a 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -176,6 +176,7 @@ let ./services/gnome-keyring.nix ./services/gpg-agent.nix ./services/grobi.nix + ./services/gromit-mpx.nix ./services/hound.nix ./services/imapnotify.nix ./services/kanshi.nix diff --git a/modules/services/gromit-mpx.nix b/modules/services/gromit-mpx.nix new file mode 100644 index 000000000..7a85cf764 --- /dev/null +++ b/modules/services/gromit-mpx.nix @@ -0,0 +1,234 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.gromit-mpx; + + # Select the appropriate hot key: + hotkey = if isInt cfg.hotKey then + "--keycode ${toString cfg.hotKey}" + else if cfg.hotKey != null then + "--key ${cfg.hotKey}" + else + "--key none"; + + # Select the appropriate undo key: + undokey = if isInt cfg.undoKey then + "--undo-keycode ${toString cfg.undoKey}" + else if cfg.undoKey != null then + "--undo-key ${cfg.undoKey}" + else + "--undo-key none"; + + # The command line to send to gromit-mpx: + commandArgs = concatStringsSep " " [ hotkey undokey ]; + + # Gromit reads and writes from this file to store it's run time + # state. That will break our config so we set it manually which, + # thanks to the read-only Nix store, prevents Gromit from writing to + # it. + keyFile = generators.toINI { } { + General.ShowIntroOnStartup = false; + Drawing.Opacity = cfg.opacity; + }; + + # Allowed modifiers: + modsAndButtons = [ "1" "2" "3" "4" "5" "SHIFT" "CONTROL" "ALT" "META" ]; + + # Create a string of tool attributes: + toolAttrs = tool: + concatStringsSep " " ([ "size=${toString tool.size}" ] + ++ optional (tool.type != "eraser") ''color="${tool.color}"'' + ++ optional (tool.arrowSize != null) + "arrowsize=${toString tool.arrowSize}"); + + # Optional tool modifier string: + toolMod = tool: + if tool.modifiers != [ ] then + "[" + concatStringsSep ", " tool.modifiers + "]" + else + ""; + + # A single tool configuration: + toolToCfg = n: tool: '' + "tool-${toString n}" = ${toUpper tool.type} (${toolAttrs tool}); + "${tool.device}"${toolMod tool} = "tool-${toString n}"; + ''; + + # Per-tool options: + toolOptions = { + options = { + device = mkOption { + type = types.str; + example = "default"; + description = '' + Use this tool with the given xinput device. The device with + the name default works with any input. + ''; + }; + + type = mkOption { + type = types.enum [ "pen" "eraser" "recolor" ]; + default = "pen"; + example = "eraser"; + description = "Which type of tool this is."; + }; + + color = mkOption { + type = types.str; + default = "red"; + example = "#ff00ff"; + description = "The stroke (or recolor) color of the tool."; + }; + + size = mkOption { + type = types.ints.positive; + default = 5; + example = 3; + description = "The tool size."; + }; + + arrowSize = mkOption { + type = types.nullOr types.ints.positive; + default = null; + example = 2; + description = '' + If not null, automatically draw an arrow + at the end of a stroke with the given size. + ''; + }; + + modifiers = mkOption { + type = types.listOf (types.enum modsAndButtons); + default = [ ]; + example = [ "SHIFT" ]; + description = '' + Only activate this tool if the given modifiers are also active. + ''; + }; + }; + }; + +in { + meta.maintainers = [ maintainers.pjones ]; + + options.services.gromit-mpx = { + enable = mkEnableOption "Gromit-MPX annotation tool"; + + package = mkOption { + type = types.package; + default = pkgs.gromit-mpx; + defaultText = "pkgs.gromit-mpx"; + description = "The gromit-mpx package to use."; + }; + + hotKey = mkOption { + type = with types; nullOr (either str ints.positive); + default = "F9"; + example = "Insert"; + description = '' + A keysym or raw keycode that toggles the activation state of + gromit-mpx. Set to null to disable the + hotkey in which case you'll have to activate gromit-mpx + manually using the command line. + ''; + }; + + undoKey = mkOption { + type = with types; nullOr (either str ints.positive); + default = "F10"; + description = '' + A keysym or raw keycode that causes gromit-mpx to undo the + last stroke. Use this key along with the shift key to redo an + undone stoke. Set to null to disable the + undo hotkey. + ''; + }; + + opacity = mkOption { + type = types.addCheck types.float (f: f >= 0.0 && f <= 1.0) // { + description = "float between 0.0 and 1.0 (inclusive)"; + }; + default = 0.75; + example = 1.0; + description = "Opacity of the drawing overlay."; + }; + + tools = mkOption { + type = types.listOf (types.submodule toolOptions); + default = [ + { + device = "default"; + type = "pen"; + size = 5; + color = "red"; + } + { + device = "default"; + type = "pen"; + size = 5; + color = "blue"; + modifiers = [ "SHIFT" ]; + } + { + device = "default"; + type = "pen"; + size = 5; + color = "yellow"; + modifiers = [ "CONTROL" ]; + } + { + device = "default"; + type = "pen"; + size = 6; + color = "green"; + arrowSize = 1; + modifiers = [ "2" ]; + } + { + device = "default"; + type = "eraser"; + size = 75; + modifiers = [ "3" ]; + } + ]; + description = '' + Tool definitions for gromit-mpx to use. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "services.gromit-mpx" pkgs + lib.platforms.linux) + ]; + + xdg.configFile."gromit-mpx.ini".text = keyFile; + xdg.configFile."gromit-mpx.cfg".text = + concatStringsSep "\n" (imap1 toolToCfg cfg.tools); + + home.packages = [ cfg.package ]; + + systemd.user.services.gromit-mpx = { + Unit = { + Description = "Gromit-MPX"; + After = [ "graphical-session-pre.target" ]; + PartOf = [ "graphical-session.target" ]; + X-Restart-Triggers = [ + "${config.xdg.configFile."gromit-mpx.cfg".source}" + "${config.xdg.configFile."gromit-mpx.ini".source}" + ]; + }; + + Service = { + Type = "simple"; + ExecStart = "${cfg.package}/bin/gromit-mpx ${commandArgs}"; + }; + + Install = { WantedBy = [ "graphical-session.target" ]; }; + }; + }; +} diff --git a/tests/default.nix b/tests/default.nix index b7a398586..f1445ba6c 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -132,6 +132,7 @@ import nmt { ./modules/services/fnott ./modules/services/git-sync ./modules/services/gpg-agent + ./modules/services/gromit-mpx ./modules/services/kanshi ./modules/services/lieer ./modules/services/pantalaimon diff --git a/tests/modules/services/gromit-mpx/basic-configuration.cfg b/tests/modules/services/gromit-mpx/basic-configuration.cfg new file mode 100644 index 000000000..d96e9b5ab --- /dev/null +++ b/tests/modules/services/gromit-mpx/basic-configuration.cfg @@ -0,0 +1,5 @@ +"tool-1" = PEN (size=5 color="red"); +"default" = "tool-1"; + +"tool-2" = ERASER (size=75); +"default"[3] = "tool-2"; diff --git a/tests/modules/services/gromit-mpx/basic-configuration.nix b/tests/modules/services/gromit-mpx/basic-configuration.nix new file mode 100644 index 000000000..380bcb5c3 --- /dev/null +++ b/tests/modules/services/gromit-mpx/basic-configuration.nix @@ -0,0 +1,23 @@ +{ config, pkgs, ... }: + +{ + services.gromit-mpx = { + enable = true; + package = config.lib.test.mkStubPackage { }; + tools = [ + { + device = "default"; + type = "pen"; + size = 5; + } + { + device = "default"; + type = "eraser"; + size = 75; + modifiers = [ "3" ]; + } + ]; + }; + + nmt.script = import ./nmt-script.nix ./basic-configuration.cfg; +} diff --git a/tests/modules/services/gromit-mpx/default-configuration.cfg b/tests/modules/services/gromit-mpx/default-configuration.cfg new file mode 100644 index 000000000..904c0c6a4 --- /dev/null +++ b/tests/modules/services/gromit-mpx/default-configuration.cfg @@ -0,0 +1,14 @@ +"tool-1" = PEN (size=5 color="red"); +"default" = "tool-1"; + +"tool-2" = PEN (size=5 color="blue"); +"default"[SHIFT] = "tool-2"; + +"tool-3" = PEN (size=5 color="yellow"); +"default"[CONTROL] = "tool-3"; + +"tool-4" = PEN (size=6 color="green" arrowsize=1); +"default"[2] = "tool-4"; + +"tool-5" = ERASER (size=75); +"default"[3] = "tool-5"; diff --git a/tests/modules/services/gromit-mpx/default-configuration.nix b/tests/modules/services/gromit-mpx/default-configuration.nix new file mode 100644 index 000000000..b2ca50569 --- /dev/null +++ b/tests/modules/services/gromit-mpx/default-configuration.nix @@ -0,0 +1,10 @@ +{ config, pkgs, ... }: + +{ + services.gromit-mpx = { + enable = true; + package = config.lib.test.mkStubPackage { }; + }; + + nmt.script = import ./nmt-script.nix ./default-configuration.cfg; +} diff --git a/tests/modules/services/gromit-mpx/default.nix b/tests/modules/services/gromit-mpx/default.nix new file mode 100644 index 000000000..d2a43841f --- /dev/null +++ b/tests/modules/services/gromit-mpx/default.nix @@ -0,0 +1,4 @@ +{ + gromit-mpx-default-configuration = ./default-configuration.nix; + gromit-mpx-basic-configuration = ./basic-configuration.nix; +} diff --git a/tests/modules/services/gromit-mpx/nmt-script.nix b/tests/modules/services/gromit-mpx/nmt-script.nix new file mode 100644 index 000000000..84dcb66f3 --- /dev/null +++ b/tests/modules/services/gromit-mpx/nmt-script.nix @@ -0,0 +1,14 @@ +golden_file: + +'' + serviceFile=home-files/.config/systemd/user/gromit-mpx.service + + assertFileExists $serviceFile + assertFileRegex $serviceFile 'X-Restart-Triggers=.*gromitmpx\.cfg' + assertFileRegex $serviceFile 'X-Restart-Triggers=.*gromitmpx\.ini' + assertFileRegex $serviceFile 'ExecStart=.*/bin/gromit-mpx' + + assertFileExists home-files/.config/gromit-mpx.ini + assertFileExists home-files/.config/gromit-mpx.cfg + assertFileContent home-files/.config/gromit-mpx.cfg ${golden_file} +''