diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b6ffddc6..4d49c32d 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 bd376ec3..15297915 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 64905b12..6d7ea4a7 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 00000000..7a85cf76
--- /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 b7a39858..f1445ba6 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 00000000..d96e9b5a
--- /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 00000000..380bcb5c
--- /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 00000000..904c0c6a
--- /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 00000000..b2ca5056
--- /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 00000000..d2a43841
--- /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 00000000..84dcb66f
--- /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}
+''