From 90b46307b5d71b36f28eed78dfcdd2b2e6b57972 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 5 Feb 2023 00:59:47 +0100
Subject: [PATCH 01/21] htgen-imgur: 1.0.0 -> 1.2.0

Generate deletehash without dashes, otherwise an unpatched flameshot
will fail to delete uploaded images, as it uses dashes to pack files
into its history.
---
 krebs/5pkgs/simple/htgen-imgur/default.nix     | 2 +-
 krebs/5pkgs/simple/htgen-imgur/src/htgen-imgur | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/krebs/5pkgs/simple/htgen-imgur/default.nix b/krebs/5pkgs/simple/htgen-imgur/default.nix
index e6b60be49..379150a85 100644
--- a/krebs/5pkgs/simple/htgen-imgur/default.nix
+++ b/krebs/5pkgs/simple/htgen-imgur/default.nix
@@ -1,7 +1,7 @@
 { attr, coreutils, exiv2, findutils, gnugrep, jq, nix, stockholm, util-linux, stdenv }:
 stdenv.mkDerivation rec {
   pname = "htgen-imgur";
-  version = "1.0.0";
+  version = "1.2.0";
 
   src = ./src;
 
diff --git a/krebs/5pkgs/simple/htgen-imgur/src/htgen-imgur b/krebs/5pkgs/simple/htgen-imgur/src/htgen-imgur
index af092d007..696d1c00d 100644
--- a/krebs/5pkgs/simple/htgen-imgur/src/htgen-imgur
+++ b/krebs/5pkgs/simple/htgen-imgur/src/htgen-imgur
@@ -99,7 +99,7 @@ case "$Method $path" in
 
     if item=$(find_item $base32short); then
 
-      deletehash=$(uuidgen)
+      deletehash=$(uuidgen | tr -d -)
 
       info=$(
         exiv2 print "$item" |

From 7b8f46c398b459bdbb9650465d5912100dd5f3fe Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Fri, 3 Feb 2023 18:10:41 +0100
Subject: [PATCH 02/21] lib.types.boundedInt: init

---
 lib/types.nix | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lib/types.nix b/lib/types.nix
index 32b4541ae..cda338130 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -287,6 +287,12 @@ rec {
     };
   });
 
+  boundedInt = min: max: mkOptionType {
+    name = "bounded integer";
+    check = x: isInt x && min <= x && x <= max;
+    merge = mergeOneOption;
+  };
+
   positive = mkOptionType {
     name = "positive integer";
     check = x: isInt x && x > 0;

From 3b04273d5cf816897ba6f0b1879bea259726703b Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Fri, 3 Feb 2023 18:10:50 +0100
Subject: [PATCH 03/21] lib.types.lowerBoundedInt: init

---
 lib/types.nix | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lib/types.nix b/lib/types.nix
index cda338130..f7c491645 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -293,15 +293,21 @@ rec {
     merge = mergeOneOption;
   };
 
+  lowerBoundedInt = min: mkOptionType {
+    name = "lower bounded integer";
+    check = x: isInt x && min <= x;
+    merge = mergeOneOption;
+  };
+
   positive = mkOptionType {
+    inherit (lowerBoundedInt 1) check;
     name = "positive integer";
-    check = x: isInt x && x > 0;
     merge = mergeOneOption;
   };
 
   uint = mkOptionType {
+    inherit (lowerBoundedInt 0) check;
     name = "unsigned integer";
-    check = x: isInt x && x >= 0;
     merge = mergeOneOption;
   };
 

From 377840226c448fd44f616b77e6057cd414f12480 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 5 Feb 2023 00:41:47 +0100
Subject: [PATCH 04/21] lib.types.svg.color-keyword: init

---
 lib/svg-colors.json | 149 ++++++++++++++++++++++++++++++++++++++++++++
 lib/types.nix       |  17 ++++-
 2 files changed, 164 insertions(+), 2 deletions(-)
 create mode 100644 lib/svg-colors.json

diff --git a/lib/svg-colors.json b/lib/svg-colors.json
new file mode 100644
index 000000000..834bf14f4
--- /dev/null
+++ b/lib/svg-colors.json
@@ -0,0 +1,149 @@
+[
+  "aliceblue",
+  "antiquewhite",
+  "aqua",
+  "aquamarine",
+  "azure",
+  "beige",
+  "bisque",
+  "black",
+  "blanchedalmond",
+  "blue",
+  "blueviolet",
+  "brown",
+  "burlywood",
+  "cadetblue",
+  "chartreuse",
+  "chocolate",
+  "coral",
+  "cornflowerblue",
+  "cornsilk",
+  "crimson",
+  "cyan",
+  "darkblue",
+  "darkcyan",
+  "darkgoldenrod",
+  "darkgray",
+  "darkgreen",
+  "darkgrey",
+  "darkkhaki",
+  "darkmagenta",
+  "darkolivegreen",
+  "darkorange",
+  "darkorchid",
+  "darkred",
+  "darksalmon",
+  "darkseagreen",
+  "darkslateblue",
+  "darkslategray",
+  "darkslategrey",
+  "darkturquoise",
+  "darkviolet",
+  "deeppink",
+  "deepskyblue",
+  "dimgray",
+  "dimgrey",
+  "dodgerblue",
+  "firebrick",
+  "floralwhite",
+  "forestgreen",
+  "fuchsia",
+  "gainsboro",
+  "ghostwhite",
+  "gold",
+  "goldenrod",
+  "gray",
+  "green",
+  "greenyellow",
+  "grey",
+  "honeydew",
+  "hotpink",
+  "indianred",
+  "indigo",
+  "ivory",
+  "khaki",
+  "lavender",
+  "lavenderblush",
+  "lawngreen",
+  "lemonchiffon",
+  "lightblue",
+  "lightcoral",
+  "lightcyan",
+  "lightgoldenrodyellow",
+  "lightgray",
+  "lightgreen",
+  "lightgrey",
+  "lightpink",
+  "lightsalmon",
+  "lightseagreen",
+  "lightskyblue",
+  "lightslategray",
+  "lightslategrey",
+  "lightsteelblue",
+  "lightyellow",
+  "lime",
+  "limegreen",
+  "linen",
+  "magenta",
+  "maroon",
+  "mediumaquamarine",
+  "mediumblue",
+  "mediumorchid",
+  "mediumpurple",
+  "mediumseagreen",
+  "mediumslateblue",
+  "mediumspringgreen",
+  "mediumturquoise",
+  "mediumvioletred",
+  "midnightblue",
+  "mintcream",
+  "mistyrose",
+  "moccasin",
+  "navajowhite",
+  "navy",
+  "oldlace",
+  "olive",
+  "olivedrab",
+  "orange",
+  "orangered",
+  "orchid",
+  "palegoldenrod",
+  "palegreen",
+  "paleturquoise",
+  "palevioletred",
+  "papayawhip",
+  "peachpuff",
+  "peru",
+  "pink",
+  "plum",
+  "powderblue",
+  "purple",
+  "red",
+  "rosybrown",
+  "royalblue",
+  "saddlebrown",
+  "salmon",
+  "sandybrown",
+  "seagreen",
+  "seashell",
+  "sienna",
+  "silver",
+  "skyblue",
+  "slateblue",
+  "slategray",
+  "slategrey",
+  "snow",
+  "springgreen",
+  "steelblue",
+  "tan",
+  "teal",
+  "thistle",
+  "tomato",
+  "turquoise",
+  "violet",
+  "wheat",
+  "white",
+  "whitesmoke",
+  "yellow",
+  "yellowgreen"
+]
diff --git a/lib/types.nix b/lib/types.nix
index f7c491645..4bb8c173f 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -3,8 +3,8 @@
 let
   inherit (lib)
     all any attrNames concatMapStringsSep concatStringsSep const filter flip
-    genid_uint31 hasSuffix head isInt isString length mergeOneOption mkOption
-    mkOptionType optional optionalAttrs optionals range splitString
+    genid_uint31 hasSuffix head importJSON isInt isString length mergeOneOption
+    mkOption mkOptionType optional optionalAttrs optionals range splitString
     stringLength substring test testString typeOf;
   inherit (lib.types)
     attrsOf bool either enum int lines listOf nullOr path str submodule;
@@ -613,6 +613,19 @@ rec {
     merge = mergeOneOption;
   };
 
+  # SVG 1.1, 4.4 Recognized color keyword names
+  #
+  # svg-colors.json has been generated with:
+  #   curl -sS https://www.w3.org/TR/SVG11/types.html#ColorKeywords |
+  #   fq -d html '[
+  #     grep_by(.["@class"]=="color-keywords") |
+  #     grep_by(.["@class"]=="prop-value"and.["#text"]!="").["#text"]
+  #   ] | sort'
+  #
+  svg.color-keyword = enum (importJSON ./svg-colors.json) // {
+    name = "SVG 1.1 recognized color keyword";
+  };
+
   systemd.unit-name = mkOptionType {
     name = "systemd unit name";
     check = x:

From f802eaf7847eb7bd1b8a735dac5166fa8999ea06 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 5 Feb 2023 00:47:55 +0100
Subject: [PATCH 05/21] lib.hexchars: init

---
 lib/default.nix | 2 ++
 lib/genid.nix   | 3 +--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/lib/default.nix b/lib/default.nix
index 280f04299..187514a30 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -45,6 +45,8 @@ let
     genid_uint31 = x: ((lib.genid_uint32 x) + 16777216) / 2;
     genid_uint32 = import ./genid.nix { inherit lib; };
 
+    hexchars = stringToCharacters "0123456789abcdef";
+
     lpad = n: c: s:
       if lib.stringLength s < n
         then lib.lpad n c (c + s)
diff --git a/lib/genid.nix b/lib/genid.nix
index 0aed1d351..bfa4a9a03 100644
--- a/lib/genid.nix
+++ b/lib/genid.nix
@@ -32,6 +32,5 @@ let out = genid;
   hexint = x: hexvals.${toLower x};
 
   # :: attrset char uint4
-  hexvals = listToAttrs (imap (i: c: { name = c; value = i - 1; })
-                        (stringToCharacters "0123456789abcdef"));
+  hexvals = listToAttrs (imap (i: c: { name = c; value = i - 1; }) hexchars);
 in out

From 23b63bfdfb5ed2700441f13429cbd1cd0c2ea870 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sat, 4 Feb 2023 21:52:14 +0100
Subject: [PATCH 06/21] flameshot-once: reinit with flameshot 12.1.0-pre

---
 krebs/5pkgs/simple/flameshot-once/config.nix  | 408 ++++++++++++++++++
 krebs/5pkgs/simple/flameshot-once/default.nix |  71 +++
 .../flameshot-once/flameshot/default.nix      |  15 +
 .../flameshot/flameshot-12.imgur.patch        |  43 ++
 lib/types.nix                                 |   5 +-
 tv/5pkgs/haskell/flameshot-once.nix           |  20 -
 tv/5pkgs/override/flameshot/default.nix       |  15 -
 .../flameshot/flameshot_imgur_0.10.2.patch    |  35 --
 tv/5pkgs/simple/flameshot-once/default.nix    |  28 --
 tv/5pkgs/simple/flameshot-once/profile.nix    | 235 ----------
 10 files changed, 541 insertions(+), 334 deletions(-)
 create mode 100644 krebs/5pkgs/simple/flameshot-once/config.nix
 create mode 100644 krebs/5pkgs/simple/flameshot-once/default.nix
 create mode 100644 krebs/5pkgs/simple/flameshot-once/flameshot/default.nix
 create mode 100644 krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.imgur.patch
 delete mode 100644 tv/5pkgs/haskell/flameshot-once.nix
 delete mode 100644 tv/5pkgs/override/flameshot/default.nix
 delete mode 100644 tv/5pkgs/override/flameshot/flameshot_imgur_0.10.2.patch
 delete mode 100644 tv/5pkgs/simple/flameshot-once/default.nix
 delete mode 100644 tv/5pkgs/simple/flameshot-once/profile.nix

diff --git a/krebs/5pkgs/simple/flameshot-once/config.nix b/krebs/5pkgs/simple/flameshot-once/config.nix
new file mode 100644
index 000000000..817c700a1
--- /dev/null
+++ b/krebs/5pkgs/simple/flameshot-once/config.nix
@@ -0,0 +1,408 @@
+{ config, pkgs, ... }:
+with pkgs.stockholm.lib;
+
+let
+  # Encode integer to C-escaped string of bytes, little endian / LSB 0
+  le = rec {
+    x1 = i: let
+      i0 = mod i 16;
+      i1 = i / 16;
+    in
+      if i == 0 then
+        "\\0"
+      else if i < 16 then
+        "\\x${elemAt hexchars i0}"
+      else
+        "\\x${elemAt hexchars i1}${elemAt hexchars i0}";
+
+    x2 = i: let
+      i0 = mod i 256;
+      i1 = i / 256;
+    in
+      "${x1 i1}${x1 i0}";
+
+    x4 = i: let
+      i0 = mod i 65536;
+      i1 = i / 65536;
+    in
+      "${x2 i1}${x2 i0}";
+  };
+
+  toQList = t: xs:
+    assert t == "int";
+    "QList<${t}>${le.x4 0}${le.x1 (length xs)}${concatMapStrings le.x4 xs}";
+in
+
+{
+  options = {
+    imgur = mkOption {
+      default = {};
+      type = types.submodule {
+        options = {
+          enable = mkEnableOption "imgur";
+          createUrl = mkOption {
+            example = "http://p.r/image";
+            type = types.str;
+          };
+          deleteUrl = mkOption {
+            example = "http://p.r/image/delete/%1";
+            type = types.str;
+          };
+          xdg-open = mkOption {
+            default = {};
+            type = types.submodule {
+              options = {
+                enable = mkEnableOption "imgur.xdg-open" // {
+                  default = true;
+                };
+                browser = mkOption {
+                  default = "${pkgs.coreutils}/bin/false";
+                  type = types.str;
+                };
+                createPrefix = mkOption {
+                  default = config.imgur.createUrl;
+                  type = types.str;
+                };
+                deletePrefix = mkOption {
+                  default = removeSuffix "/%1" config.imgur.deleteUrl;
+                  type = types.str;
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+    package = mkOption {
+      type = types.package;
+      default = import ./flameshot { inherit pkgs; };
+    };
+    settings = {
+      # Options without a description are not documented in flameshot's README.
+      General = mapAttrs (_: recursiveUpdate { default = null; }) {
+        allowMultipleGuiInstances = mkOption {
+          description = ''
+            Allow multiple instances of `flameshot gui` to run at the same time
+          '';
+          type = with types; nullOr bool;
+        };
+        antialiasingPinZoom = mkOption {
+          description = ''
+            Anti-aliasing image when zoom the pinned image
+          '';
+          type = with types; nullOr bool;
+        };
+        autoCloseIdleDaemon = mkOption {
+          description = ''
+            Automatically close daemon when it's not needed
+          '';
+          type = with types; nullOr bool;
+        };
+        buttons = let
+          buttonTypes = {
+            TYPE_PENCIL = 0;
+            TYPE_DRAWER = 1;
+            TYPE_ARROW = 2;
+            TYPE_SELECTION = 3;
+            TYPE_RECTANGLE = 4;
+            TYPE_CIRCLE = 5;
+            TYPE_MARKER = 6;
+            TYPE_SELECTIONINDICATOR = 7;
+            TYPE_MOVESELECTION = 8;
+            TYPE_UNDO = 9;
+            TYPE_COPY = 10;
+            TYPE_SAVE = 11;
+            TYPE_EXIT = 12;
+            TYPE_IMAGEUPLOADER = 13;
+            TYPE_OPEN_APP = 14;
+            TYPE_PIXELATE = 15;
+            TYPE_REDO = 16;
+            TYPE_PIN = 17;
+            TYPE_TEXT = 18;
+            TYPE_CIRCLECOUNT = 19;
+            TYPE_SIZEINCREASE = 20;
+            TYPE_SIZEDECREASE = 21;
+            TYPE_INVERT = 22;
+            TYPE_ACCEPT = 23;
+          };
+          iterableButtonTypes = [
+            "TYPE_ACCEPT"
+            "TYPE_ARROW"
+            "TYPE_CIRCLE"
+            "TYPE_CIRCLECOUNT"
+            "TYPE_COPY"
+            "TYPE_DRAWER"
+            "TYPE_EXIT"
+            "TYPE_IMAGEUPLOADER"
+            "TYPE_MARKER"
+            "TYPE_MOVESELECTION"
+            "TYPE_OPEN_APP"
+            "TYPE_PENCIL"
+            "TYPE_PIN"
+            "TYPE_PIXELATE"
+            "TYPE_RECTANGLE"
+            "TYPE_REDO"
+            "TYPE_SAVE"
+            "TYPE_SELECTION"
+            "TYPE_SIZEDECREASE"
+            "TYPE_SIZEINCREASE"
+            "TYPE_TEXT"
+            "TYPE_UNDO"
+          ];
+        in mkOption {
+          apply = names:
+            if names != null then let
+              values = map (name: buttonTypes.${name}) names;
+            in
+              ''@Variant(\0\0\0\x7f\0\0\0\v${toQList "int" values})''
+            else
+              null;
+          description = ''
+            Configure which buttons to show after drawing a selection
+          '';
+          type = with types; nullOr (listOf (enum iterableButtonTypes));
+        };
+        checkForUpdates = mkOption {
+          type = with types; nullOr bool;
+        };
+        contrastOpacity = mkOption {
+          description = ''
+            Opacity of area outside selection
+          '';
+          type = with types; nullOr (boundedInt 0 255);
+        };
+        contrastUiColor = mkOption {
+          description = ''
+            Contrast UI color
+          '';
+          type = with types; nullOr flameshot.color;
+        };
+        copyAndCloseAfterUpload = mkOption {
+          type = with types; nullOr bool;
+        };
+        copyOnDoubleClick = mkOption {
+          type = with types; nullOr bool;
+        };
+        copyPathAfterSave = mkOption {
+          description = ''
+            Copy path to image after save
+          '';
+          type = with types; nullOr bool;
+        };
+        copyURLAfterUpload = mkOption {
+          description = ''
+            On successful upload, close the dialog and copy URL to clipboard
+          '';
+          type = with types; nullOr bool;
+        };
+        disabledTrayIcon = mkOption {
+          description = ''
+            Whether the tray icon is disabled
+          '';
+          type = with types; nullOr bool;
+        };
+        drawColor = mkOption {
+          description = ''
+            Last used color
+          '';
+          type = with types; nullOr flameshot.color;
+        };
+        drawFontSize = mkOption {
+          type = with types; nullOr positive;
+        };
+        drawThickness = mkOption {
+          description = ''
+            Last used tool thickness
+          '';
+          type = with types; nullOr positive;
+        };
+        filenamePattern = mkOption {
+          description = ''
+            Filename pattern using C++ strftime formatting
+          '';
+          type =
+            # This is types.filename extended by [%:][%:+]*
+            with types;
+            nullOr (addCheck str (test "[%:0-9A-Za-z._][%:+0-9A-Za-z._-]*"));
+        };
+        fontFamily = mkOption {
+          type = with types; nullOr str;
+        };
+        historyConfirmationToDelete = mkOption {
+          type = with types; nullOr bool;
+        };
+        ignoreUpdateToVersion = mkOption {
+          description = ''
+            Ignore updates to versions less than this value
+          '';
+          type = with types; nullOr str;
+        };
+        keepOpenAppLauncher = mkOption {
+          description = ''
+            Keep the App Launcher open after selecting an app
+          '';
+          type = with types; nullOr bool;
+        };
+        predefinedColorPaletteLarge = mkOption {
+          description = ''
+            Use larger color palette as the default one
+          '';
+          type = with types; nullOr bool;
+        };
+        saveAfterCopy = mkOption {
+          description = ''
+            Save image after copy
+          '';
+          type = with types; nullOr bool;
+        };
+        saveAsFileExtension = mkOption {
+          description = ''
+            Default file extension for screenshots
+          '';
+          type = with types; nullOr (addCheck filename (hasPrefix "."));
+        };
+        safeLastRegion = mkOption {
+          type = with types; nullOr bool;
+        };
+        savePath = mkOption {
+          description = ''
+            Image Save Path
+          '';
+          type = with types; nullOr absolute-pathname;
+        };
+        savePathFixed = mkOption {
+          description = ''
+            Whether the savePath is a fixed path
+          '';
+          type = with types; nullOr bool;
+        };
+        showDesktopNotification = mkOption {
+          description = ''
+            Show desktop notifications
+          '';
+          type = with types; nullOr bool;
+        };
+        showHelp = mkOption {
+          description = ''
+            Show the help screen on startup
+          '';
+          type = with types; nullOr bool;
+        };
+        showMagnifier = mkOption {
+          type = with types; nullOr bool;
+        };
+        showSelectionGeometry = mkOption {
+          type = with types; nullOr (boundedInt 0 5);
+        };
+        showSelectionGeometryHideTime = mkOption {
+          type = with types; nullOr uint;
+        };
+        showSidePanelButton = mkOption {
+          description = ''
+            Show the side panel button
+          '';
+          type = with types; nullOr bool;
+        };
+        showStartupLaunchMessage = mkOption {
+          type = with types; nullOr bool;
+        };
+        squareMagnifier = mkOption {
+          type = with types; nullOr bool;
+        };
+        startupLaunch = mkOption {
+          description = ''
+            Launch at startup
+          '';
+          type = with types; nullOr bool;
+        };
+        uiColor = mkOption {
+          description = ''
+            Main UI color
+          '';
+          type = with types; nullOr flameshot.color;
+        };
+        undoLimit = mkOption {
+          type = with types; nullOr (boundedInt 0 999);
+        };
+        uploadClientSecret = mkOption {
+          type = with types; nullOr str;
+        };
+        uploadHistoryMax = mkOption {
+          type = with types; nullOr uint;
+        };
+        uploadWithoutConfirmation = mkOption {
+          description = ''
+            Upload to imgur without confirmation
+          '';
+          type = with types; nullOr bool;
+        };
+        useJpgForClipboard = mkOption {
+          description = ''
+            Use JPG format instead of PNG
+          '';
+          type = with types; nullOr bool;
+        };
+        userColors = mkOption {
+          apply = value:
+            if value != null then
+              concatStringsSep ", " value
+            else
+              null;
+          description = ''
+            List of colors for color picker
+            The colors are arranged counter-clockwise with the first being set
+            to the right of the cursor.  "picker" adds a custom color picker.
+          '';
+          type =
+            with types;
+            nullOr (listOf (either flameshot.color (enum ["picker"])));
+        };
+      };
+      Shortcuts = genAttrs [
+        "TYPE_ACCEPT"
+        "TYPE_ARROW"
+        "TYPE_CIRCLE"
+        "TYPE_CIRCLECOUNT"
+        "TYPE_COMMIT_CURRENT_TOOL"
+        "TYPE_COPY"
+        "TYPE_DELETE_CURRENT_TOOL"
+        "TYPE_DRAWER"
+        "TYPE_EXIT"
+        "TYPE_IMAGEUPLOADER"
+        "TYPE_INVERT"
+        "TYPE_MARKER"
+        "TYPE_MOVESELECTION"
+        "TYPE_MOVE_DOWN"
+        "TYPE_MOVE_LEFT"
+        "TYPE_MOVE_RIGHT"
+        "TYPE_MOVE_UP"
+        "TYPE_OPEN_APP"
+        "TYPE_PENCIL"
+        "TYPE_PIN"
+        "TYPE_PIXELATE"
+        "TYPE_RECTANGLE"
+        "TYPE_REDO"
+        "TYPE_RESIZE_DOWN"
+        "TYPE_RESIZE_LEFT"
+        "TYPE_RESIZE_RIGHT"
+        "TYPE_RESIZE_UP"
+        "TYPE_SAVE"
+        "TYPE_SELECTION"
+        "TYPE_SELECTIONINDICATOR"
+        "TYPE_SELECT_ALL"
+        "TYPE_SIZEDECREASE"
+        "TYPE_SIZEINCREASE"
+        "TYPE_SYM_RESIZE_DOWN"
+        "TYPE_SYM_RESIZE_LEFT"
+        "TYPE_SYM_RESIZE_RIGHT"
+        "TYPE_SYM_RESIZE_UP"
+        "TYPE_TEXT"
+        "TYPE_TOGGLE_PANEL"
+        "TYPE_UNDO"
+      ] (name: mkOption {
+        default = null;
+        type = with types; nullOr str;
+      });
+    };
+  };
+}
diff --git a/krebs/5pkgs/simple/flameshot-once/default.nix b/krebs/5pkgs/simple/flameshot-once/default.nix
new file mode 100644
index 000000000..92dd5311f
--- /dev/null
+++ b/krebs/5pkgs/simple/flameshot-once/default.nix
@@ -0,0 +1,71 @@
+{ name ? "flameshot-once", pkgs, ... }@args:
+with pkgs.stockholm.lib;
+
+let
+  # config cannot be declared in the input attribute set because that would
+  # cause callPackage to inject the wrong config.  Instead, get it from ...
+  # via args.
+  config = args.config or {};
+
+  cfg = evalModulesConfig (singleton {
+    _file = toString ./default.nix;
+    _module.args.pkgs = pkgs;
+    imports = [
+      config
+      ./config.nix
+    ];
+  });
+in
+
+pkgs.symlinkJoin {
+  inherit name;
+  paths = [
+    (pkgs.write "flameshot-once" {
+      "/bin/flameshot-once" = {
+        executable = true;
+        text = /* sh */ ''
+          #! ${pkgs.dash}/bin/dash
+          export PATH=${makeBinPath [
+            pkgs.qt5.qtbase
+          ]}:''${PATH+:$PATH}
+          ${optionalString (config != null) /* sh */ ''
+            export XDG_CONFIG_HOME=${placeholder "out"}/etc
+            ${optionalString cfg.imgur.enable /* sh */ ''
+              export IMGUR_CREATE_URL=${shell.escape cfg.imgur.createUrl}
+              export IMGUR_DELETE_URL=${shell.escape cfg.imgur.deleteUrl}
+              ${optionalString cfg.imgur.xdg-open.enable /* sh */ ''
+                export PATH=${placeholder "out"}/lib/imgur/bin''${PATH+:$PATH}
+              ''}
+            ''}
+          ''}
+          ${cfg.package}/bin/flameshot &
+          exec ${cfg.package}/bin/flameshot gui
+        '';
+      };
+      "/etc/flameshot/flameshot.ini".text =
+        lib.generators.toINI {} (stripAttr cfg.settings);
+      ${if cfg.imgur.enable then "/lib/imgur/bin/xdg-open" else null} = {
+        executable = true;
+        text = /* sh */ ''
+          #! ${pkgs.dash}/bin/dash
+          set -efu
+          uri=$1
+          prefix=$(${pkgs.coreutils}/bin/dirname "$uri")
+          case $prefix in
+            (${shell.escape cfg.imgur.xdg-open.createPrefix})
+              echo "opening image in browser: $uri" >&2
+              exec ${config.imgur.xdg-open.browser} "$uri"
+              ;;
+            (${shell.escape cfg.imgur.xdg-open.deletePrefix})
+              echo "deleting image: $uri" >&2
+              exec ${pkgs.curl}/bin/curl -fsS -X DELETE "$uri"
+              ;;
+            (*)
+              echo "don't know how to open URI: $uri" >&2
+              exit 1
+          esac
+        '';
+      };
+    })
+  ];
+}
diff --git a/krebs/5pkgs/simple/flameshot-once/flameshot/default.nix b/krebs/5pkgs/simple/flameshot-once/flameshot/default.nix
new file mode 100644
index 000000000..b129270eb
--- /dev/null
+++ b/krebs/5pkgs/simple/flameshot-once/flameshot/default.nix
@@ -0,0 +1,15 @@
+{ pkgs }:
+
+pkgs.flameshot.overrideAttrs (old: rec {
+  name = "flameshot-${version}";
+  version = "12.1.0-pre";
+  src = pkgs.fetchFromGitHub {
+    owner = "flameshot-org";
+    repo = "flameshot";
+    rev = "f7e41f4d708e50eeaec892408069da25a28e04a2";
+    hash = "sha256-fZquXY0xSaN1hJgCh16MocIlvxHe1c2Nt+fGF2NIOVw=";
+  };
+  patches = old.patches or [] ++ [
+    ./flameshot-12.imgur.patch
+  ];
+})
diff --git a/krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.imgur.patch b/krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.imgur.patch
new file mode 100644
index 000000000..b6c3f497a
--- /dev/null
+++ b/krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.imgur.patch
@@ -0,0 +1,43 @@
+diff --git a/src/tools/imgupload/storages/imgur/imguruploader.cpp b/src/tools/imgupload/storages/imgur/imguruploader.cpp
+index d6748b5a..5bb8d7de 100644
+--- a/src/tools/imgupload/storages/imgur/imguruploader.cpp
++++ b/src/tools/imgupload/storages/imgur/imguruploader.cpp
+@@ -16,6 +16,7 @@
+ #include <QNetworkRequest>
+ #include <QShortcut>
+ #include <QUrlQuery>
++#include <stdlib.h>
+ 
+ ImgurUploader::ImgurUploader(const QPixmap& capture, QWidget* parent)
+   : ImgUploaderBase(capture, parent)
+@@ -70,7 +71,13 @@ void ImgurUploader::upload()
+     QString description = FileNameHandler().parsedPattern();
+     urlQuery.addQueryItem(QStringLiteral("description"), description);
+ 
+-    QUrl url(QStringLiteral("https://api.imgur.com/3/image"));
++    const char *IMGUR_CREATE_URL = secure_getenv("IMGUR_CREATE_URL");
++    QString createUrlPattern =
++      IMGUR_CREATE_URL != NULL
++        ? QString::fromUtf8(IMGUR_CREATE_URL)
++        : QStringLiteral("https://api.imgur.com/3/image")
++        ;
++    QUrl url(createUrlPattern);
+     url.setQuery(urlQuery);
+     QNetworkRequest request(url);
+     request.setHeader(QNetworkRequest::ContentTypeHeader,
+@@ -87,8 +94,14 @@ void ImgurUploader::deleteImage(const QString& fileName,
+                                 const QString& deleteToken)
+ {
+     Q_UNUSED(fileName)
++    const char *IMGUR_DELETE_URL = secure_getenv("IMGUR_DELETE_URL");
++    QString deleteImageURLPattern =
++      IMGUR_DELETE_URL != NULL
++        ? QString::fromUtf8(IMGUR_DELETE_URL)
++        : QStringLiteral("https://imgur.com/delete/%1")
++        ;
+     bool successful = QDesktopServices::openUrl(
+-      QUrl(QStringLiteral("https://imgur.com/delete/%1").arg(deleteToken)));
++      QUrl(deleteImageURLPattern.arg(deleteToken)));
+     if (!successful) {
+         notification()->showMessage(tr("Unable to open the URL."));
+     }
diff --git a/lib/types.nix b/lib/types.nix
index 4bb8c173f..5f01ccb52 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -7,7 +7,7 @@ let
     mkOption mkOptionType optional optionalAttrs optionals range splitString
     stringLength substring test testString typeOf;
   inherit (lib.types)
-    attrsOf bool either enum int lines listOf nullOr path str submodule;
+    addCheck attrsOf bool either enum int lines listOf nullOr path str submodule;
 in
 
 rec {
@@ -595,6 +595,9 @@ rec {
     };
   };
 
+  flameshot.color =
+    either (addCheck str (test "#[0-9A-Fa-f]{6}")) svg.color-keyword;
+
   file-mode = mkOptionType {
     name = "file mode";
     check = test "[0-7]{4}";
diff --git a/tv/5pkgs/haskell/flameshot-once.nix b/tv/5pkgs/haskell/flameshot-once.nix
deleted file mode 100644
index c8007ce9e..000000000
--- a/tv/5pkgs/haskell/flameshot-once.nix
+++ /dev/null
@@ -1,20 +0,0 @@
-{ mkDerivation, async, base, blessings, bytestring, dbus, fetchgit
-, iso8601-time, lib, process, random, text, time, unagi-chan, unix
-}:
-mkDerivation {
-  pname = "flameshot-once";
-  version = "1.4.0";
-  src = fetchgit {
-    url = "https://cgit.krebsco.de/flameshot-once";
-    sha256 = "13szgsiwn29aixm5xvs1m7128y5km5xss0ry5ii5y068rc2vysw8";
-    rev = "4475893c2081b3d9db4b7a54d0ce38d0914a17bf";
-    fetchSubmodules = true;
-  };
-  isLibrary = false;
-  isExecutable = true;
-  executableHaskellDepends = [
-    async base blessings bytestring dbus iso8601-time process random
-    text time unagi-chan unix
-  ];
-  license = lib.licenses.mit;
-}
diff --git a/tv/5pkgs/override/flameshot/default.nix b/tv/5pkgs/override/flameshot/default.nix
deleted file mode 100644
index 10154cc44..000000000
--- a/tv/5pkgs/override/flameshot/default.nix
+++ /dev/null
@@ -1,15 +0,0 @@
-self: super:
-
-super.flameshot.overrideAttrs (old: rec {
-  name = "flameshot-${version}";
-  version = "0.10.2";
-  src = self.fetchFromGitHub {
-    owner = "flameshot-org";
-    repo = "flameshot";
-    rev = "v${version}";
-    sha256 = "sha256-rZUiaS32C77tFJmEkw/9MGbVTVscb6LOCyWaWO5FyR4=";
-  };
-  patches = old.patches or [] ++ [
-    ./flameshot_imgur_0.10.2.patch
-  ];
-})
diff --git a/tv/5pkgs/override/flameshot/flameshot_imgur_0.10.2.patch b/tv/5pkgs/override/flameshot/flameshot_imgur_0.10.2.patch
deleted file mode 100644
index c4c0bf38a..000000000
--- a/tv/5pkgs/override/flameshot/flameshot_imgur_0.10.2.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/src/tools/imgur/imguruploader.cpp
-+++ b/src/tools/imgur/imguruploader.cpp
-@@ -31,6 +31,7 @@
- #include <QTimer>
- #include <QUrlQuery>
- #include <QVBoxLayout>
-+#include <stdlib.h>
- 
- ImgurUploader::ImgurUploader(const QPixmap& capture, QWidget* parent)
-   : QWidget(parent)
-@@ -79,8 +80,11 @@ void ImgurUploader::handleReply(QNetworkReply* reply)
-         m_imageURL.setUrl(data[QStringLiteral("link")].toString());
- 
-         auto deleteToken = data[QStringLiteral("deletehash")].toString();
-+        char *deleteImageURLPattern = secure_getenv("IMGUR_DELETE_URL");
-+        if (deleteImageURLPattern == NULL)
-+            deleteImageURLPattern = "https://imgur.com/delete/%1";
-         m_deleteImageURL.setUrl(
--          QStringLiteral("https://imgur.com/delete/%1").arg(deleteToken));
-+          QString::fromUtf8(deleteImageURLPattern).arg(deleteToken));
- 
-         // save history
-         QString imageName = m_imageURL.toString();
-@@ -133,7 +137,10 @@ void ImgurUploader::upload()
-     QString description = FileNameHandler().parsedPattern();
-     urlQuery.addQueryItem(QStringLiteral("description"), description);
- 
--    QUrl url(QStringLiteral("https://api.imgur.com/3/image"));
-+    char *createImageURLPattern = secure_getenv("IMGUR_CREATE_URL");
-+    if (createImageURLPattern == NULL)
-+        createImageURLPattern = "https://api.imgur.com/3/image";
-+    QUrl url(QString::fromUtf8(createImageURLPattern));
-     url.setQuery(urlQuery);
-     QNetworkRequest request(url);
-     request.setHeader(QNetworkRequest::ContentTypeHeader,
diff --git a/tv/5pkgs/simple/flameshot-once/default.nix b/tv/5pkgs/simple/flameshot-once/default.nix
deleted file mode 100644
index 0524c2cfa..000000000
--- a/tv/5pkgs/simple/flameshot-once/default.nix
+++ /dev/null
@@ -1,28 +0,0 @@
-{ pkgs, stockholm, ... }@args:
-with stockholm.lib;
-
-let
-  # config cannot be declared in the input attribute set because that would
-  # cause callPackage to inject the wrong config.  Instead, get it from ...
-  # via args.
-  config = args.config or {};
-in
-
-  pkgs.symlinkJoin {
-    name = "flameshot-once-wrapper";
-    paths = [
-      (pkgs.writeDashBin "flameshot-once" ''
-        export PATH=${makeBinPath [
-          pkgs.flameshot
-          pkgs.qt5.qtbase
-          pkgs.xclip
-          pkgs.xwaitforwindow
-        ]}
-        ${optionalString (config != null) /* sh */ ''
-          . ${import ./profile.nix { inherit config pkgs; }}
-        ''}
-        exec ${pkgs.haskellPackages.flameshot-once}/bin/flameshot-once "$@"
-      '')
-      pkgs.haskellPackages.flameshot-once
-    ];
-  }
diff --git a/tv/5pkgs/simple/flameshot-once/profile.nix b/tv/5pkgs/simple/flameshot-once/profile.nix
deleted file mode 100644
index 269f13a66..000000000
--- a/tv/5pkgs/simple/flameshot-once/profile.nix
+++ /dev/null
@@ -1,235 +0,0 @@
-{ config, pkgs }:
-with pkgs.stockholm.lib;
-with generators;
-let
-
-  # Refs https://github.com/lupoDharkael/flameshot/blob/master/src/widgets/capture/capturebutton.h
-  ButtonType = {
-    PENCIL             = 0;
-    DRAWER             = 1;
-    ARROW              = 2;
-    SELECTION          = 3;
-    RECTANGLE          = 4;
-    CIRCLE             = 5;
-    MARKER             = 6;
-    SELECTIONINDICATOR = 7;
-    MOVESELECTION      = 8;
-    UNDO               = 9;
-    COPY               = 10;
-    SAVE               = 11;
-    EXIT               = 12;
-    IMAGEUPLOADER      = 13;
-    OPEN_APP           = 14;
-    BLUR               = 15;
-    REDO               = 16;
-    PIN                = 17;
-    TEXT               = 18;
-    CIRCLECOUNT        = 19;
-  };
-
-  cfg = eval.config;
-
-  eval = evalModules {
-    modules = singleton {
-      _file = toString ./profile.nix;
-      imports = singleton config;
-      options = {
-        buttons = mkOption {
-          apply = map (name: ButtonType.${name});
-          default = [
-            "PENCIL"
-            "DRAWER"
-            "ARROW"
-            "SELECTION"
-            "RECTANGLE"
-            "CIRCLE"
-            "MARKER"
-            "SELECTIONINDICATOR"
-            "MOVESELECTION"
-            "UNDO"
-            "SAVE"
-            "EXIT"
-            "BLUR"
-            "CIRCLECOUNT"
-          ]
-          ++ optional cfg.imgur.enable "IMAGEUPLOADER"
-          ;
-          type = types.listOf (types.enum (attrNames ButtonType));
-        };
-        copyAndCloseAfterUpload = mkOption {
-          default = false;
-          type = types.bool;
-        };
-        disabledTrayIcon = mkOption {
-          default = true;
-          type = types.bool;
-        };
-        drawColor = mkOption {
-          default = "#ff0000";
-          type =
-            types.addCheck types.str (test "#[0-9A-Fa-f]{6}");
-        };
-        drawThickness = mkOption {
-          default = 8;
-          type = types.positive;
-        };
-        filenamePattern = mkOption {
-          default = "%FT%T%z_flameshot";
-          type =
-            # This is types.filename extended by [%:][%:+]*
-            types.addCheck types.str (test "[%:0-9A-Za-z._][%:+0-9A-Za-z._-]*");
-        };
-        imgur = mkOption {
-          default = {};
-          type = types.submodule {
-            options = {
-              enable = mkEnableOption "imgur";
-              createUrl = mkOption {
-                example = "http://p.r/image";
-                type = types.str;
-              };
-              deleteUrl = mkOption {
-                example = "http://p.r/image/delete/%1";
-                type = types.str;
-              };
-              xdg-open = mkOption {
-                default = {};
-                type = types.submodule {
-                  options = {
-                    enable = mkEnableOption "imgur.xdg-open" // {
-                      default = true;
-                    };
-                    browser = mkOption {
-                      default = "${pkgs.coreutils}/bin/false";
-                      type = types.str;
-                    };
-                    createPrefix = mkOption {
-                      default = cfg.imgur.createUrl;
-                      type = types.str;
-                    };
-                    deletePrefix = mkOption {
-                      default = removeSuffix "/%1" cfg.imgur.deleteUrl;
-                      type = types.str;
-                    };
-                  };
-                };
-              };
-            };
-          };
-        };
-        savePath = mkOption {
-          default = "/tmp";
-          type = types.absolute-pathname;
-        };
-        showDesktopNotification = mkOption {
-          default = false;
-          type = types.bool;
-        };
-        showHelp = mkOption {
-          default = false;
-          type = types.bool;
-        };
-        showSidePanelButton = mkOption {
-          default = false;
-          type = types.bool;
-        };
-        showStartupLaunchMessage = mkOption {
-          default = false;
-          type = types.bool;
-        };
-        timeout = mkOption {
-          default = 200;
-          description = ''
-            Maximum time in milliseconds allowed for the flameshot daemon to
-            react.
-          '';
-          type = types.positive;
-        };
-      };
-    };
-  };
-
-  hexchars = stringToCharacters "0123456789abcdef";
-
-  # Encode integer to C-escaped string of bytes, little endian / LSB 0
-  le = rec {
-    x1 = i: let
-      i0 = mod i 16;
-      i1 = i / 16;
-    in
-      "\\x${elemAt hexchars i1}${elemAt hexchars i0}";
-
-    x2 = i: let
-      i0 = mod i 256;
-      i1 = i / 256;
-    in
-      "${x1 i0}${x1 i1}";
-
-    x4 = i: let
-      i0 = mod i 65536;
-      i1 = i / 65536;
-    in
-      "${x2 i0}${x2 i1}";
-  };
-
-  toQList = t: xs:
-    assert t == "int";
-    "QList<${t}>${le.x4 0}${le.x4 (length xs)}${concatMapStrings le.x4 xs}";
-
-  XDG_CONFIG_HOME = pkgs.write "flameshot-config" {
-    "/flameshot/flameshot.ini".text =
-      toINI {} {
-        General = {
-          buttons = ''@Variant(\0\0\0\x7f\0\0\0\v${toQList "int" cfg.buttons})'';
-          disabledTrayIcon = cfg.disabledTrayIcon;
-          checkForUpdates = false;
-          copyAndCloseAfterUpload = cfg.copyAndCloseAfterUpload;
-          drawColor = cfg.drawColor;
-          drawThickness = cfg.drawThickness;
-          filenamePattern = cfg.filenamePattern;
-          savePath = cfg.savePath;
-          showDesktopNotification = cfg.showDesktopNotification;
-          showHelp = cfg.showHelp;
-          showSidePanelButton = cfg.showSidePanelButton;
-          showStartupLaunchMessage = cfg.showStartupLaunchMessage;
-          startupLaunch = false;
-        };
-        Shortcuts = {
-          TYPE_COPY = "Return";
-        };
-      };
-  };
-
-in
-
-  pkgs.writeDash "flameshot.profile" ''
-    export FLAMESHOT_CAPTURE_PATH=${cfg.savePath}
-    export FLAMESHOT_ONCE_TIMEOUT=${toString cfg.timeout}
-    export XDG_CONFIG_HOME=${XDG_CONFIG_HOME}
-    ${optionalString cfg.imgur.enable /* sh */ ''
-      export IMGUR_CREATE_URL=${shell.escape cfg.imgur.createUrl}
-      export IMGUR_DELETE_URL=${shell.escape cfg.imgur.deleteUrl}
-      ${optionalString cfg.imgur.xdg-open.enable /* sh */ ''
-        PATH=$PATH:${makeBinPath [
-          (pkgs.writeDashBin "xdg-open" ''
-            set -efu
-            uri=$1
-            prefix=$(${pkgs.coreutils}/bin/dirname "$uri")
-            case $prefix in
-              (${shell.escape cfg.imgur.xdg-open.createPrefix})
-                echo "opening image in browser: $uri" >&2
-                exec ${config.imgur.xdg-open.browser} "$uri"
-                ;;
-              (${shell.escape cfg.imgur.xdg-open.deletePrefix})
-                echo "deleting image: $uri" >&2
-                exec ${pkgs.curl}/bin/curl -fsS -X DELETE "$uri"
-                ;;
-              (*)
-                echo "don't know how to open URI: $uri" >&2
-                exit 1
-            esac
-          '')
-        ]}
-      ''}
-    ''}
-  ''

From 8056151181e6b49ad3c1b80cdfc94a18e49846fc Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 5 Feb 2023 01:13:56 +0100
Subject: [PATCH 07/21] flameshot-once: alter flameshot packing char

This allows deleting images uploaded to htgen-imgur <1.2.0
---
 .../flameshot-once/flameshot/default.nix      |  1 +
 .../flameshot/flameshot-12.history.patch      | 28 +++++++++++++++++++
 2 files changed, 29 insertions(+)
 create mode 100644 krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.history.patch

diff --git a/krebs/5pkgs/simple/flameshot-once/flameshot/default.nix b/krebs/5pkgs/simple/flameshot-once/flameshot/default.nix
index b129270eb..f60acef08 100644
--- a/krebs/5pkgs/simple/flameshot-once/flameshot/default.nix
+++ b/krebs/5pkgs/simple/flameshot-once/flameshot/default.nix
@@ -11,5 +11,6 @@ pkgs.flameshot.overrideAttrs (old: rec {
   };
   patches = old.patches or [] ++ [
     ./flameshot-12.imgur.patch
+    ./flameshot-12.history.patch
   ];
 })
diff --git a/krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.history.patch b/krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.history.patch
new file mode 100644
index 000000000..66f28a661
--- /dev/null
+++ b/krebs/5pkgs/simple/flameshot-once/flameshot/flameshot-12.history.patch
@@ -0,0 +1,28 @@
+diff --git a/src/utils/history.cpp b/src/utils/history.cpp
+index f3ee09d0..7c85c34b 100644
+--- a/src/utils/history.cpp
++++ b/src/utils/history.cpp
+@@ -76,9 +76,9 @@ const HistoryFileName& History::unpackFileName(const QString& fileNamePacked)
+     int nPathIndex = fileNamePacked.lastIndexOf("/");
+     QStringList unpackedFileName;
+     if (nPathIndex == -1) {
+-        unpackedFileName = fileNamePacked.split("-");
++        unpackedFileName = fileNamePacked.split("|");
+     } else {
+-        unpackedFileName = fileNamePacked.mid(nPathIndex + 1).split("-");
++        unpackedFileName = fileNamePacked.mid(nPathIndex + 1).split("|");
+     }
+ 
+     switch (unpackedFileName.length()) {
+@@ -109,9 +109,9 @@ const QString& History::packFileName(const QString& storageType,
+     if (storageType.length() > 0) {
+         if (deleteToken.length() > 0) {
+             m_packedFileName =
+-              storageType + "-" + deleteToken + "-" + m_packedFileName;
++              storageType + "|" + deleteToken + "|" + m_packedFileName;
+         } else {
+-            m_packedFileName = storageType + "-" + m_packedFileName;
++            m_packedFileName = storageType + "|" + m_packedFileName;
+         }
+     }
+     return m_packedFileName;

From afe16677affb35291fc64def0ccb0656a5c47647 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sat, 4 Feb 2023 21:52:51 +0100
Subject: [PATCH 08/21] flameshot-once: add dev tools

---
 krebs/5pkgs/simple/flameshot-once/config.nix  |  8 ++
 krebs/5pkgs/simple/flameshot-once/default.nix | 78 +++++++++++++++++++
 2 files changed, 86 insertions(+)

diff --git a/krebs/5pkgs/simple/flameshot-once/config.nix b/krebs/5pkgs/simple/flameshot-once/config.nix
index 817c700a1..24df403aa 100644
--- a/krebs/5pkgs/simple/flameshot-once/config.nix
+++ b/krebs/5pkgs/simple/flameshot-once/config.nix
@@ -79,6 +79,8 @@ in
     };
     settings = {
       # Options without a description are not documented in flameshot's README.
+      # Compare with:
+      #   nix-shell -p flameshot-once.dev --run get-recognizedGeneralOptions
       General = mapAttrs (_: recursiveUpdate { default = null; }) {
         allowMultipleGuiInstances = mkOption {
           description = ''
@@ -100,6 +102,8 @@ in
         };
         buttons = let
           buttonTypes = {
+            # Generated with:
+            #   nix-shell -p flameshot-once.dev --run get-buttonTypes
             TYPE_PENCIL = 0;
             TYPE_DRAWER = 1;
             TYPE_ARROW = 2;
@@ -126,6 +130,8 @@ in
             TYPE_ACCEPT = 23;
           };
           iterableButtonTypes = [
+            # Generated with:
+            #   nix-shell -p flameshot-once.dev --run get-iterableButtonTypes
             "TYPE_ACCEPT"
             "TYPE_ARROW"
             "TYPE_CIRCLE"
@@ -359,6 +365,8 @@ in
         };
       };
       Shortcuts = genAttrs [
+        # Generated with:
+        #   nix-shell -p flameshot-once.dev --run get-Shortcuts
         "TYPE_ACCEPT"
         "TYPE_ARROW"
         "TYPE_CIRCLE"
diff --git a/krebs/5pkgs/simple/flameshot-once/default.nix b/krebs/5pkgs/simple/flameshot-once/default.nix
index 92dd5311f..3626409f3 100644
--- a/krebs/5pkgs/simple/flameshot-once/default.nix
+++ b/krebs/5pkgs/simple/flameshot-once/default.nix
@@ -69,3 +69,81 @@ pkgs.symlinkJoin {
     })
   ];
 }
+// {
+  dev = pkgs.write "flameshot-once-tools" {
+    "/bin/get-buttonTypes" = {
+      executable = true;
+      text = /* sh */ ''
+        #! ${pkgs.dash}/bin/dash
+        indent=$(${placeholder "out"}/bin/indent-of buttonTypes)
+        src=${cfg.package.src}/src/tools/capturetool.h
+        ${pkgs.coreutils}/bin/cat "$src" |
+        ${pkgs.gnused}/bin/sed -nr '
+          s/^\s*(TYPE_\S+)\s*=\s*([0-9]+),/\1 = \2;/p
+        ' |
+        ${placeholder "out"}/bin/prefix "  $indent"
+      '';
+    };
+    "/bin/get-iterableButtonTypes" = {
+      executable = true;
+      text = /* sh */ ''
+        #! ${pkgs.dash}/bin/dash
+        indent=$(${placeholder "out"}/bin/indent-of iterableButtonTypes)
+        src=${cfg.package.src}/src/widgets/capture/capturetoolbutton.cpp
+        ${pkgs.coreutils}/bin/cat "$src" |
+        ${pkgs.gnused}/bin/sed -n '/\<iterableButtonTypes = {/,/^}/p' |
+        ${pkgs.gcc}/bin/cpp |
+        ${pkgs.coreutils}/bin/tr , \\n |
+        ${pkgs.gnused}/bin/sed -rn 's/^ *CaptureTool::(TYPE_[A-Z_]+).*/"\1"/p' |
+        ${pkgs.coreutils}/bin/sort |
+        ${placeholder "out"}/bin/prefix "  $indent"
+      '';
+    };
+    "/bin/get-recognizedGeneralOptions" = {
+      executable = true;
+      text = /* sh */ ''
+        #! ${pkgs.dash}/bin/dash
+        src=${cfg.package.src}/src/utils/confighandler.cpp
+        ${pkgs.coreutils}/bin/cat "$src" |
+        ${pkgs.gnused}/bin/sed -n '/\<recognizedGeneralOptions = {/,/^};/p' |
+        ${pkgs.gcc}/bin/cpp |
+        ${pkgs.gnugrep}/bin/grep -F OPTION |
+        ${pkgs.coreutils}/bin/sort
+      '';
+    };
+    "/bin/get-Shortcuts" = {
+      executable = true;
+      text = /* sh */ ''
+        #! ${pkgs.dash}/bin/dash
+        indent=$(${placeholder "out"}/bin/indent-of Shortcuts)
+        src=${cfg.package.src}/src/utils/confighandler.cpp
+        ${pkgs.coreutils}/bin/cat "$src" |
+        ${pkgs.gnused}/bin/sed -n '/recognizedShortcuts = {/,/^};/p ' |
+        ${pkgs.gcc}/bin/cpp |
+        ${pkgs.gnused}/bin/sed -nr 's/^\s*SHORTCUT\("(TYPE_[^"]+).*/"\1"/p' |
+        ${pkgs.coreutils}/bin/sort |
+        ${placeholder "out"}/bin/prefix "  $indent"
+      '';
+    };
+    "/bin/indent-of" = {
+      executable = true;
+      text = /* sh */ ''
+        #! ${pkgs.dash}/bin/dash
+        # usage: indent-of NAME NIX_FILE
+        exec ${pkgs.gawk}/bin/awk -v name="$1" '
+          $1 == name && $2 == "=" {
+            sub("[^ ].*", "")
+            print
+          }
+        ' ${./config.nix}
+      '';
+    };
+    "/bin/prefix" = {
+      executable = true;
+      text = /* sh */ ''
+        #! ${pkgs.dash}/bin/dash
+        ${pkgs.gawk}/bin/awk -v prefix="$1" '{ print prefix $0 }'
+      '';
+    };
+  };
+}

From eb94ae0fa73ca71def294bd9689790ead363559a Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 5 Feb 2023 02:28:29 +0100
Subject: [PATCH 09/21] tv flameshot-once-tv: init

---
 tv/2configs/xserver/default.nix        |  8 +----
 tv/5pkgs/haskell/xmonad-tv/src/main.hs |  2 +-
 tv/5pkgs/simple/flameshot-once-tv.nix  | 48 ++++++++++++++++++++++++++
 3 files changed, 50 insertions(+), 8 deletions(-)
 create mode 100644 tv/5pkgs/simple/flameshot-once-tv.nix

diff --git a/tv/2configs/xserver/default.nix b/tv/2configs/xserver/default.nix
index f534b557e..f10ccb10e 100644
--- a/tv/2configs/xserver/default.nix
+++ b/tv/2configs/xserver/default.nix
@@ -120,13 +120,7 @@ in {
     };
     path = [
       config.tv.slock.package
-      (pkgs.flameshot-once.override {
-        config.imgur.enable = true;
-        config.imgur.createUrl = "http://ni.r/image";
-        config.imgur.deleteUrl = "http://ni.r/image/delete/%1";
-        config.imgur.xdg-open.browser = "/etc/profiles/per-user/tv/bin/cr";
-        config.timeout = 200;
-      })
+      pkgs.flameshot-once-tv
       pkgs.pulseaudio.out
       pkgs.rxvt_unicode
       pkgs.xcalib
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/main.hs b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
index 118f2da46..b3b411b01 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/main.hs
+++ b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
@@ -206,7 +206,7 @@ myKeys font conf = Map.fromList $
 
     , ((_4, xK_Prior), forkFile {-pkg-}"xcalib" ["-invert", "-alter"] Nothing)
 
-    , ((0, xK_Print), forkFile {-pkg-}"flameshot" [] Nothing)
+    , ((0, xK_Print), forkFile {-pkg:flameshot-once-tv-}"flameshot-once" [] Nothing)
 
     , ((_C, xF86XK_Forward), forkFile {-pkg:xdpytools-}"xdpychvt" ["next"] Nothing)
     , ((_C, xF86XK_Back), forkFile {-pkg:xdpytools-}"xdpychvt" ["prev"] Nothing)
diff --git a/tv/5pkgs/simple/flameshot-once-tv.nix b/tv/5pkgs/simple/flameshot-once-tv.nix
new file mode 100644
index 000000000..e3a9f9a4f
--- /dev/null
+++ b/tv/5pkgs/simple/flameshot-once-tv.nix
@@ -0,0 +1,48 @@
+{ pkgs }:
+
+pkgs.flameshot-once.override {
+  name = "flameshot-once-tv";
+  config.imgur.enable = true;
+  config.imgur.createUrl = "http://ni.r/image";
+  config.imgur.deleteUrl = "http://ni.r/image/delete/%1";
+  config.imgur.xdg-open.browser = "/etc/profiles/per-user/tv/bin/cr";
+  config.settings.General = {
+    autoCloseIdleDaemon = true;
+    buttons = [
+      "TYPE_ARROW"
+      "TYPE_CIRCLE"
+      "TYPE_CIRCLECOUNT"
+      "TYPE_COPY"
+      "TYPE_DRAWER"
+      "TYPE_IMAGEUPLOADER"
+      "TYPE_MARKER"
+      "TYPE_MOVESELECTION"
+      "TYPE_PENCIL"
+      "TYPE_PIXELATE"
+      "TYPE_RECTANGLE"
+      "TYPE_SAVE"
+      "TYPE_SELECTION"
+      "TYPE_TEXT"
+    ];
+    checkForUpdates = false;
+    contrastOpacity = 220;
+    copyPathAfterSave = true;
+    disabledTrayIcon = true;
+    drawColor = "#E4002B";
+    drawThickness = 8;
+    filenamePattern = "%FT%T%z_flameshot";
+    fontFamily = "iosevka tv 2";
+    savePath = "/tmp";
+    savePathFixed = true;
+    showDesktopNotification = false;
+    showHelp = false;
+    showSidePanelButton = false;
+    showStartupLaunchMessage = false;
+    squareMagnifier = true;
+    uploadWithoutConfirmation = true;
+  };
+  config.settings.Shortcuts = {
+    TYPE_COPY = "Return";
+    TYPE_TOGGLE_PANEL = "`";
+  };
+}

From 49788f82975ca43bcfae8671ef97d23c5a2288c7 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 5 Feb 2023 00:55:29 +0100
Subject: [PATCH 10/21] tv gitrepos: move flameshot-once to museum

---
 tv/2configs/gitrepos.nix | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tv/2configs/gitrepos.nix b/tv/2configs/gitrepos.nix
index d8e7755fe..eb87f26d1 100644
--- a/tv/2configs/gitrepos.nix
+++ b/tv/2configs/gitrepos.nix
@@ -74,9 +74,6 @@ with import ./lib;
     disko = {
       cgit.desc = "declarative partitioning and formatting tool";
     };
-    flameshot-once = {
-      cgit.desc = "flameshot runner that automatically starts/stops the daemon";
-    };
     fswm = {
       cgit.desc = "simple full screen window manager";
     };
@@ -139,6 +136,9 @@ with import ./lib;
     cgserver = {};
     crude-mail-setup = {};
     dot-xmonad = {};
+    flameshot-once = {
+      cgit.desc = "flameshot runner that automatically starts/stops the daemon";
+    };
     hirc = {};
     hstool = {
       cgit.desc = "Haskell Development Environment ^_^";

From e44fe862f0bedc658aabd1daafb16376f188857e Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 5 Feb 2023 03:37:49 +0100
Subject: [PATCH 11/21] xwaitforwindow: RIP

---
 krebs/5pkgs/simple/xwaitforwindow.nix | 15 ---------------
 1 file changed, 15 deletions(-)
 delete mode 100644 krebs/5pkgs/simple/xwaitforwindow.nix

diff --git a/krebs/5pkgs/simple/xwaitforwindow.nix b/krebs/5pkgs/simple/xwaitforwindow.nix
deleted file mode 100644
index 41ce65022..000000000
--- a/krebs/5pkgs/simple/xwaitforwindow.nix
+++ /dev/null
@@ -1,15 +0,0 @@
-{ writeDashBin, xdotool, xorg }:
-writeDashBin "xwaitforwindow" ''
-  # usage: xwaitforwindow ARGS
-  # see xdotool search for possible ARGS
-  # example: xwaitforwindow -name WINDOWNAME
-  set -efu
-
-  if id=$(${xdotool}/bin/xdotool search "$@"); then
-    printf 'waiting for window %#x\n' "$id" >&2
-    exec ${xorg.xprop}/bin/xprop -spy -id "$id" >/dev/null
-  else
-    printf 'no window found with xdotool search %s\n' "$*" >&2
-    exit 1
-  fi
-''

From 2ed5961330ba9375696facbd331de45c7b9f8897 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Mon, 6 Feb 2023 01:28:38 +0100
Subject: [PATCH 12/21] much: 1.3.1 -> 1.3.2

---
 krebs/5pkgs/haskell/much.nix | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/krebs/5pkgs/haskell/much.nix b/krebs/5pkgs/haskell/much.nix
index 5045465e6..865294daf 100644
--- a/krebs/5pkgs/haskell/much.nix
+++ b/krebs/5pkgs/haskell/much.nix
@@ -7,13 +7,13 @@
 , servant-server, split, terminal-size, text, time, transformers
 , transformers-compat, unix, vector, wai, warp
 }:
-mkDerivation {
+mkDerivation rec {
   pname = "much";
-  version = "1.3.1";
+  version = "1.3.2";
   src = fetchgit {
     url = "https://cgit.krebsco.de/much";
-    sha256 = "0gwyhqcvg9ywna8fhb9hnx97qh5inglj3l0pcwkgwcvm27mfpcqa";
-    rev = "77357335a3a88a4b93f91a46ab939a1a9b192977";
+    hash = "sha256-q65EYO1d3NYVv2NECkGWPb1TyHGdARNi/GX4pgQmljc=";
+    rev = "refs/tags/${version}";
     fetchSubmodules = true;
   };
   isLibrary = true;

From 1ce5e46e7b56d7da09802b909d74b6b3439710a8 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 14:28:16 +0100
Subject: [PATCH 13/21] pager: fc6105a -> dfa3ff3

---
 krebs/5pkgs/haskell/pager.nix | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/krebs/5pkgs/haskell/pager.nix b/krebs/5pkgs/haskell/pager.nix
index 2f4a71f34..36709788c 100644
--- a/krebs/5pkgs/haskell/pager.nix
+++ b/krebs/5pkgs/haskell/pager.nix
@@ -1,21 +1,22 @@
-{ mkDerivation, base, blessings, bytestring, containers
-, data-default, hack, lib, optparse-applicative, probability
-, scanner, speculate, split, terminal-size, text, unix, X11
-, fetchgit
+{ mkDerivation, aeson, base, blessings, bytestring, containers
+, data-default, extra, fetchgit, hack, lib, optparse-applicative
+, probability, scanner, speculate, split, terminal-size, text, unix
+, utf8-string, X11
 }:
 mkDerivation {
   pname = "pager";
   version = "1.0.0";
   src = fetchgit {
     url = "https://cgit.krebsco.de/pager";
-    sha256 = "1qlkhqidaa6w02ix9ambfdsm7lfyx30ap481b9ic1ppyfkhqzfp6";
-    rev = "fc6105a5e7d1e3a07bf07ea85e7902dd8e9fc849";
+    sha256 = "07wjlhnb27vfhkqq5vhi768mlrcpwl4b2yfk04v3lw047q6pmby0";
+    rev = "dfa3ff346d22d332ffbadd46963f1cc5cb2a4939";
     fetchSubmodules = true;
   };
-  isLibrary = false;
+  isLibrary = true;
   isExecutable = true;
+  libraryHaskellDepends = [ base extra utf8-string X11 ];
   executableHaskellDepends = [
-    base blessings bytestring containers data-default hack
+    aeson base blessings bytestring containers data-default hack
     optparse-applicative probability scanner speculate split
     terminal-size text unix X11
   ];

From e26b6d1f1546d48a482bd2a63c0113164eabb169 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 14:34:51 +0100
Subject: [PATCH 14/21] pager: expose utilities

---
 krebs/5pkgs/simple/pager.nix | 66 ++++++++++++++++++++----------------
 1 file changed, 36 insertions(+), 30 deletions(-)

diff --git a/krebs/5pkgs/simple/pager.nix b/krebs/5pkgs/simple/pager.nix
index ed740490d..f183dd1dc 100644
--- a/krebs/5pkgs/simple/pager.nix
+++ b/krebs/5pkgs/simple/pager.nix
@@ -1,36 +1,42 @@
 { pkgs }:
 
-pkgs.writeDashBin "pager" ''
-  # usage: pager {view,shift,shiftview}
-  #
-  # Environment variables
-  #
-  #   PAGER_NAME (default: Pager)
-  #     The environment variables specifies the application name under which
-  #     resources are to be obtained.  PAGER_NAME should not contain “.” or “*”
-  #     characters.
-  #
-  set -efu
+pkgs.symlinkJoin {
+  name = "pager-wrapper";
+  paths = [
+    (pkgs.writeDashBin "pager" ''
+      # usage: pager {view,shift,shiftview}
+      #
+      # Environment variables
+      #
+      #   PAGER_NAME (default: Pager)
+      #     The environment variables specifies the application name under
+      #     which resources are to be obtained.  PAGER_NAME should not contain
+      #     “.” or “*” characters.
+      #
+      set -efu
 
-  pidfile=$XDG_RUNTIME_DIR/pager.lock
-  name=''${PAGER_NAME-Pager}
+      pidfile=$XDG_RUNTIME_DIR/pager.lock
+      name=''${PAGER_NAME-Pager}
 
-  if test -e "$pidfile" &&
-     ${pkgs.procps}/bin/pgrep --pidfile="$pidfile" >/dev/null
-  then
-    ${pkgs.procps}/bin/pkill --pidfile="$pidfile"
-    ${pkgs.coreutils}/bin/rm "$pidfile"
-    exit
-  fi
+      if test -e "$pidfile" &&
+         ${pkgs.procps}/bin/pgrep --pidfile="$pidfile" >/dev/null
+      then
+        ${pkgs.procps}/bin/pkill --pidfile="$pidfile"
+        ${pkgs.coreutils}/bin/rm "$pidfile"
+        exit
+      fi
 
-  echo $$ > "$pidfile"
+      echo $$ > "$pidfile"
 
-  exec ${pkgs.xterm}/bin/xterm \
-      -name "$name" \
-      -ti vt340 \
-      -xrm 'Pager*geometry: 32x10' \
-      -xrm 'Pager*internalBorder: 2' \
-      -xrm 'Pager*background: #050505' \
-      -xrm 'Pager*foreground: #d0d7d0' \
-      -e ${pkgs.haskellPackages.pager}/bin/pager "$@"
-''
+      exec ${pkgs.xterm}/bin/xterm \
+          -name "$name" \
+          -ti vt340 \
+          -xrm 'Pager*geometry: 32x10' \
+          -xrm 'Pager*internalBorder: 2' \
+          -xrm 'Pager*background: #050505' \
+          -xrm 'Pager*foreground: #d0d7d0' \
+          -e ${pkgs.haskellPackages.pager}/bin/pager "$@"
+    '')
+    pkgs.haskellPackages.pager
+  ];
+}

From 33dc22d222673a9d4fd308b1ef76b280e7b2dbc6 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 14:35:01 +0100
Subject: [PATCH 15/21] pager: remove name from xrms

---
 krebs/5pkgs/simple/pager.nix | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/krebs/5pkgs/simple/pager.nix b/krebs/5pkgs/simple/pager.nix
index f183dd1dc..506ef2eb3 100644
--- a/krebs/5pkgs/simple/pager.nix
+++ b/krebs/5pkgs/simple/pager.nix
@@ -31,10 +31,10 @@ pkgs.symlinkJoin {
       exec ${pkgs.xterm}/bin/xterm \
           -name "$name" \
           -ti vt340 \
-          -xrm 'Pager*geometry: 32x10' \
-          -xrm 'Pager*internalBorder: 2' \
-          -xrm 'Pager*background: #050505' \
-          -xrm 'Pager*foreground: #d0d7d0' \
+          -xrm '*geometry: 32x10' \
+          -xrm '*internalBorder: 2' \
+          -xrm '*background: #050505' \
+          -xrm '*foreground: #d0d7d0' \
           -e ${pkgs.haskellPackages.pager}/bin/pager "$@"
     '')
     pkgs.haskellPackages.pager

From 4fc6487f633f8a5abef656f073b6d75984404524 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 20:31:32 +0100
Subject: [PATCH 16/21] tv xmonad: reformat build-depends

---
 .../haskell/xmonad-tv/src/xmonad-tv.cabal     | 26 +++++++++----------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
index 62faf2f00..eba7d3ade 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
+++ b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
@@ -9,19 +9,19 @@ cabal-version: >=1.10
 executable xmonad
   main-is: main.hs
   build-depends:
-    aeson,
-    base,
-    bytestring,
-    containers,
-    directory,
-    extra,
-    filepath,
-    template-haskell,
-    th-env,
-    unix,
-    X11,
-    xmonad,
-    xmonad-contrib
+      base
+    , X11
+    , aeson
+    , bytestring
+    , containers
+    , directory
+    , extra
+    , filepath
+    , template-haskell
+    , th-env
+    , unix
+    , xmonad
+    , xmonad-contrib
   other-modules:
     Shutdown
   default-language: Haskell2010

From 65d5e0413d7f69c46c3b9199e7d4cb2dcd9f4a92 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 20:48:19 +0100
Subject: [PATCH 17/21] tv xmonad: drop unused build-depends

---
 tv/5pkgs/haskell/xmonad-tv/default.nix         | 8 ++++----
 tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal | 2 --
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/tv/5pkgs/haskell/xmonad-tv/default.nix b/tv/5pkgs/haskell/xmonad-tv/default.nix
index be3eca982..60e9d3b43 100644
--- a/tv/5pkgs/haskell/xmonad-tv/default.nix
+++ b/tv/5pkgs/haskell/xmonad-tv/default.nix
@@ -1,6 +1,5 @@
 { mkDerivation, aeson, base, bytestring, containers, directory
-, extra, filepath, lib, systemd, template-haskell, th-env
-, transformers, unix, X11, xmonad, xmonad-contrib
+, extra, filepath, lib, unix, X11, xmonad, xmonad-contrib
 }:
 mkDerivation {
   pname = "xmonad-tv";
@@ -9,8 +8,9 @@ mkDerivation {
   isLibrary = false;
   isExecutable = true;
   executableHaskellDepends = [
-    aeson base bytestring containers directory extra filepath systemd
-    template-haskell th-env transformers unix X11 xmonad xmonad-contrib
+    aeson base bytestring containers directory extra filepath unix X11
+    xmonad xmonad-contrib
   ];
   license = lib.licenses.mit;
+  mainProgram = "xmonad";
 }
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
index eba7d3ade..a81d9dc3f 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
+++ b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
@@ -17,8 +17,6 @@ executable xmonad
     , directory
     , extra
     , filepath
-    , template-haskell
-    , th-env
     , unix
     , xmonad
     , xmonad-contrib

From 7f012e96af05002dcde1f7f5bdc7c3b6adb79bbe Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 20:50:40 +0100
Subject: [PATCH 18/21] tv xmonad: use non-threaded runtime

---
 tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
index a81d9dc3f..94aecd75f 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
+++ b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
@@ -23,4 +23,4 @@ executable xmonad
   other-modules:
     Shutdown
   default-language: Haskell2010
-  ghc-options: -O2 -Wall -threaded
+  ghc-options: -O2 -Wall

From fd90f35fd94ab949daf73e7253a2b0133311057c Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 21:03:36 +0100
Subject: [PATCH 19/21] tv xmonad: XMonad.Hooks.EwmhDesktops.Extra

---
 tv/5pkgs/haskell/xmonad-tv/default.nix        |   6 +-
 .../src/XMonad/Hooks/EwmhDesktops/Extra.hs    | 117 ++++++++++++++++++
 .../haskell/xmonad-tv/src/xmonad-tv.cabal     |   2 +
 3 files changed, 122 insertions(+), 3 deletions(-)
 create mode 100644 tv/5pkgs/haskell/xmonad-tv/src/XMonad/Hooks/EwmhDesktops/Extra.hs

diff --git a/tv/5pkgs/haskell/xmonad-tv/default.nix b/tv/5pkgs/haskell/xmonad-tv/default.nix
index 60e9d3b43..f42f97c2a 100644
--- a/tv/5pkgs/haskell/xmonad-tv/default.nix
+++ b/tv/5pkgs/haskell/xmonad-tv/default.nix
@@ -1,5 +1,5 @@
 { mkDerivation, aeson, base, bytestring, containers, directory
-, extra, filepath, lib, unix, X11, xmonad, xmonad-contrib
+, extra, filepath, lib, pager, unix, X11, xmonad, xmonad-contrib
 }:
 mkDerivation {
   pname = "xmonad-tv";
@@ -8,8 +8,8 @@ mkDerivation {
   isLibrary = false;
   isExecutable = true;
   executableHaskellDepends = [
-    aeson base bytestring containers directory extra filepath unix X11
-    xmonad xmonad-contrib
+    aeson base bytestring containers directory extra filepath pager
+    unix X11 xmonad xmonad-contrib
   ];
   license = lib.licenses.mit;
   mainProgram = "xmonad";
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/XMonad/Hooks/EwmhDesktops/Extra.hs b/tv/5pkgs/haskell/xmonad-tv/src/XMonad/Hooks/EwmhDesktops/Extra.hs
new file mode 100644
index 000000000..bf8431446
--- /dev/null
+++ b/tv/5pkgs/haskell/xmonad-tv/src/XMonad/Hooks/EwmhDesktops/Extra.hs
@@ -0,0 +1,117 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE NamedFieldPuns #-}
+
+module XMonad.Hooks.EwmhDesktops.Extra where
+
+import Control.Monad (when)
+import Data.Maybe (fromMaybe)
+import Data.Monoid (All)
+import Data.Tuple.Extra (both)
+import Graphics.X11.EWMH (getDesktopNames, setDesktopNames)
+import Graphics.X11.EWMH.Atom (_NET_DESKTOP_NAMES)
+import Graphics.X11.Xlib.Display.Extra (withDefaultDisplay)
+import XMonad hiding (workspaces)
+import XMonad.Actions.DynamicWorkspaces (addHiddenWorkspace, removeEmptyWorkspaceByTag)
+import XMonad.StackSet (mapWorkspace, tag, workspaces)
+import XMonad.Util.WorkspaceCompare (getSortByIndex)
+import qualified Data.Map.Strict as Map
+import qualified Data.Set as Set
+import qualified XMonad
+
+
+ewmhExtra :: XConfig a -> IO (XConfig a)
+ewmhExtra c = do
+    -- XMonad.Hooks.EwmhDesktops.setDesktopViewport uses _NET_DESKTOP_VIEWPORT
+    -- only if it exists.  This seems to be a harmless issue, but by creating
+    -- the atom here, we suppress the error message:
+    --
+    --    xmonad: X11 error: BadAtom (invalid Atom parameter),
+    --    request code=18, error code=5
+    --
+    _ <-
+      withDefaultDisplay $ \dpy -> internAtom dpy "_NET_DESKTOP_VIEWPORT" False
+
+    initialWorkspaces <-
+      Data.Maybe.fromMaybe (XMonad.workspaces def)
+        <$> withDefaultDisplay getDesktopNames
+
+    return
+      c { handleEventHook = ewmhDesktopsExtraEventHook <> handleEventHook c
+        , rootMask = rootMask c .|. propertyChangeMask
+        , XMonad.workspaces = initialWorkspaces
+        }
+
+ewmhDesktopsExtraEventHook :: Event -> X All
+ewmhDesktopsExtraEventHook = \case
+    PropertyEvent{ev_window, ev_atom} -> do
+      r <- asks theRoot
+      when (ev_window == r && ev_atom == _NET_DESKTOP_NAMES) $
+        withDisplay $ \dpy -> do
+          sort <- getSortByIndex
+
+          oldNames <- gets $ map tag . sort . workspaces . windowset
+          newNames <- fromMaybe oldNames <$> io (getDesktopNames dpy)
+
+          let
+            (renamesFrom, renamesTo) = both Set.fromList $ unzip renames
+
+            renames = go oldNames newNames where
+              go old@(headOld : tailOld) new@(headNew : tailNew) = do
+                let
+                  deleteOld = Set.member headOld deleteNameSet
+                  createNew = Set.member headNew createNameSet
+
+                if
+                  | headOld == headNew ->
+                    -- assert (not deleteOld && not createNew)
+                    go tailOld tailNew
+
+                  | deleteOld && createNew ->
+                    (headOld, headNew) :
+                    go tailOld tailNew
+
+                  | deleteOld ->
+                    go tailOld new
+
+                  | createNew ->
+                    go old tailNew
+
+                  | otherwise ->
+                    -- assert (headOld == headNew)
+                    go tailOld tailNew
+
+              go _ _ = []
+
+            oldNameSet = Set.fromList oldNames
+            newNameSet = Set.fromList newNames
+            deleteNameSet = Set.difference oldNameSet newNameSet
+            createNameSet = Set.difference newNameSet oldNameSet
+
+            deleteNames = Set.toAscList $
+                            Set.difference deleteNameSet renamesFrom
+            createNames = Set.toAscList $
+                            Set.difference createNameSet renamesTo
+
+          mapM_ addHiddenWorkspace createNames
+          mapM_ removeEmptyWorkspaceByTag deleteNames
+          when (not (null renames)) $ do
+            let
+              renameMap = Map.fromList renames
+              rename w =
+                case Map.lookup (tag w) renameMap of
+                  Just newName -> w { tag = newName }
+                  Nothing -> w
+
+            modifyWindowSet $ mapWorkspace rename
+
+          names <- gets $ map tag . sort . workspaces . windowset
+
+          when (names /= newNames) $ do
+            trace $ "setDesktopNames " <> show names
+            io (setDesktopNames names dpy)
+
+      mempty
+
+    _ ->
+      mempty
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
index 94aecd75f..0f61ba659 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
+++ b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
@@ -17,10 +17,12 @@ executable xmonad
     , directory
     , extra
     , filepath
+    , pager
     , unix
     , xmonad
     , xmonad-contrib
   other-modules:
     Shutdown
+    XMonad.Hooks.EwmhDesktops.Extra
   default-language: Haskell2010
   ghc-options: -O2 -Wall

From 19cfdf9a22eca956399fb7464a8112bf6616aa20 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 21:19:05 +0100
Subject: [PATCH 20/21] tv xmonad: use ewmhExtra

---
 tv/5pkgs/haskell/xmonad-tv/src/main.hs | 28 ++++----------------------
 1 file changed, 4 insertions(+), 24 deletions(-)

diff --git a/tv/5pkgs/haskell/xmonad-tv/src/main.hs b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
index b3b411b01..7256963a5 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/main.hs
+++ b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
@@ -5,16 +5,15 @@ module Main (main) where
 
 import System.Exit (exitFailure)
 import XMonad.Hooks.EwmhDesktops (ewmh)
+import XMonad.Hooks.EwmhDesktops.Extra (ewmhExtra)
 import XMonad.Hooks.RefocusLast (refocusLastLayoutHook, toggleFocus)
 
-import Control.Exception
 import Control.Monad.Extra (whenJustM)
 import qualified Data.Aeson
 import qualified Data.ByteString.Char8
 import qualified Data.List
 import qualified Data.Maybe
 import Graphics.X11.ExtraTypes.XF86
-import Text.Read (readEither)
 import XMonad
 import XMonad.Extra (isFloatingX)
 import System.IO (hPutStrLn, stderr)
@@ -76,11 +75,10 @@ mainNoArgs = do
     myTermFont <- getEnv "XMONAD_TERM_FONT"
     myTermFontWidth <- readEnv "XMONAD_TERM_FONT_WIDTH" :: IO Dimension
     myTermPadding <- readEnv "XMONAD_TERM_PADDING" :: IO Dimension
-    workspaces0 <- getWorkspaces0
     handleShutdownEvent <- newShutdownEventHandler
-    let
-      config =
-        ewmh
+    config <-
+      ewmhExtra
+        $ ewmh
         $ withUrgencyHookC
             BorderUrgencyHook
               { urgencyBorderColor = "#ff0000"
@@ -93,7 +91,6 @@ mainNoArgs = do
             { terminal          = {-pkg:alacritty-tv-}"alacritty"
             , modMask           = mod4Mask
             , keys              = myKeys myTermFont
-            , workspaces        = workspaces0
             , layoutHook =
                 refocusLastLayoutHook $
                 gaps (zip [U,R,D,L] myScreenGaps) $
@@ -125,23 +122,6 @@ mainNoArgs = do
     launch config directories
 
 
-getWorkspaces0 :: IO [String]
-getWorkspaces0 =
-    try (getEnv "XMONAD_WORKSPACES0_FILE") >>= \case
-      Left e -> warn (displaySomeException e)
-      Right p -> try (readFile p) >>= \case
-        Left e -> warn (displaySomeException e)
-        Right x -> case readEither x of
-          Left e -> warn e
-          Right y -> return y
-  where
-    warn msg = hPutStrLn stderr ("getWorkspaces0: " ++ msg) >> return []
-
-
-displaySomeException :: SomeException -> String
-displaySomeException = displayException
-
-
 forkFile :: FilePath -> [String] -> Maybe [(String, String)] -> X ()
 forkFile path args env =
     xfork (executeFile path True args env) >> return ()

From 9a52edeea22f686c189933de0538ab69a3c054bd Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Tue, 7 Feb 2023 21:21:18 +0100
Subject: [PATCH 21/21] tv xmonad: add XMonad.Extra to other-modules

---
 tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
index 0f61ba659..f211627bf 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
+++ b/tv/5pkgs/haskell/xmonad-tv/src/xmonad-tv.cabal
@@ -23,6 +23,7 @@ executable xmonad
     , xmonad-contrib
   other-modules:
     Shutdown
+    XMonad.Extra
     XMonad.Hooks.EwmhDesktops.Extra
   default-language: Haskell2010
   ghc-options: -O2 -Wall