summary refs log tree commit diff
diff options
context:
space:
mode:
authorK900 <me@0upti.me>2022-02-22 16:35:17 +0300
committerJonathan Ringer <jonringer@users.noreply.github.com>2022-02-25 13:40:38 -0800
commit0c713dbed40dd87090a4632cffc7b088653f35cb (patch)
treee65b4da6f219471754204272d4c7f5aa45bbc624
parent5364c438e78e737d4064d998bd8794247c3af9ca (diff)
downloadnixpkgs-0c713dbed40dd87090a4632cffc7b088653f35cb.tar
nixpkgs-0c713dbed40dd87090a4632cffc7b088653f35cb.tar.gz
nixpkgs-0c713dbed40dd87090a4632cffc7b088653f35cb.tar.bz2
nixpkgs-0c713dbed40dd87090a4632cffc7b088653f35cb.tar.lz
nixpkgs-0c713dbed40dd87090a4632cffc7b088653f35cb.tar.xz
nixpkgs-0c713dbed40dd87090a4632cffc7b088653f35cb.tar.zst
nixpkgs-0c713dbed40dd87090a4632cffc7b088653f35cb.zip
build-support/makeDesktopItem: make fully declarative, add all missing options
This updates makeDesktopItem to explicitly support all the fields in the spec,
converts list-like fields to native Nix lists instead of semicolon-separated strings,
and allows automatically generating [Desktop Action] sections from Nix code
instead of hardcoding them as extraConfig strings.
-rw-r--r--pkgs/build-support/make-desktopitem/default.nix151
1 files changed, 101 insertions, 50 deletions
diff --git a/pkgs/build-support/make-desktopitem/default.nix b/pkgs/build-support/make-desktopitem/default.nix
index 1491a3ad911..787f0f67847 100644
--- a/pkgs/build-support/make-desktopitem/default.nix
+++ b/pkgs/build-support/make-desktopitem/default.nix
@@ -1,67 +1,118 @@
-{ lib, runCommandLocal, desktop-file-utils }:
+{ lib, writeTextFile, desktop-file-utils }:
 
+# All possible values as defined by the spec, version 1.4.
+# Please keep in spec order for easier maintenance.
+# When adding a new value, don't forget to update the Version field below!
 # See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
 { name # The name of the desktop file
 , type ? "Application"
-, exec
-, icon ? null
-, comment ? null
-, terminal ? false
+# version is hardcoded
 , desktopName # The name of the application
 , genericName ? null
-, mimeType ? null
-, categories ? null
-, startupNotify ? null
 , noDisplay ? null
+, comment ? null
+, icon ? null
+# we don't support the Hidden key - if you don't need something, just don't install it
+, onlyShowIn ? []
+, notShowIn ? []
+, dbusActivatable ? null
+, tryExec ? null
+, exec ? null
+, path ? null
+, terminal ? null
+, actions ? {} # An attrset of [internal name] -> { name, exec?, icon? }
+, mimeTypes ? [] # The spec uses "MimeType" as singular, use plural here to signify list-ness
+, categories ? []
+, implements ? []
+, keywords ? []
+, startupNotify ? null
+, startupWMClass ? null
+, url ? null
 , prefersNonDefaultGPU ? null
-, extraDesktopEntries ? { } # Extra key-value pairs to add to the [Desktop Entry] section. This may override other values
-, extraEntries ? "" # Extra configuration. Will be appended to the end of the file and may thus contain extra sections
-, fileValidation ? true # whether to validate resulting desktop file.
+# not supported until version 1.5, which is not supported by our desktop-file-utils as of 2022-02-23
+# , singleMainWindow ? null
+, extraConfig ? {} # Additional values to be added literally to the final item, e.g. vendor extensions
 }:
 let
-  # like builtins.toString, but null -> null instead of null -> ""
-  nullableToString = value:
+  # There are multiple places in the FDO spec that make "boolean" values actually tristate,
+  # e.g. StartupNotify, where "unset" is literally defined as "do something reasonable".
+  # So, handle null values separately.
+  boolOrNullToString = value:
     if value == null then null
     else if builtins.isBool value then lib.boolToString value
-    else builtins.toString value;
+    else throw "Value must be a boolean or null!";
 
-  # The [Desktop entry] section of the desktop file, as attribute set.
+  # Multiple values are represented as one string, joined by semicolons.
+  # Technically, it's possible to escape semicolons in values with \;, but this is currently not implemented.
+  renderList = value:
+    if !builtins.isList value then throw "Value must be a list!"
+    else if builtins.any (item: lib.hasInfix ";" item) value then throw "Values in list must not contain semicolons!"
+    else if value == [] then null
+    else builtins.concatStringsSep ";" value;
+
+  # The [Desktop Entry] section of the desktop file, as an attribute set.
+  # Please keep in spec order.
   mainSection = {
-    "Type" = toString type;
-    "Exec" = nullableToString exec;
-    "Icon" = nullableToString icon;
-    "Comment" = nullableToString comment;
-    "Terminal" = nullableToString terminal;
-    "Name" = toString desktopName;
-    "GenericName" = nullableToString genericName;
-    "MimeType" = nullableToString mimeType;
-    "Categories" = nullableToString categories;
-    "StartupNotify" = nullableToString startupNotify;
-    "NoDisplay" = nullableToString noDisplay;
-    "PrefersNonDefaultGPU" = nullableToString prefersNonDefaultGPU;
-  } // extraDesktopEntries;
+    "Type" = type;
+    "Version" = "1.4";
+    "Name" = desktopName;
+    "GenericName" = genericName;
+    "NoDisplay" = boolOrNullToString noDisplay;
+    "Comment" = comment;
+    "Icon" = icon;
+    "OnlyShowIn" = renderList onlyShowIn;
+    "NotShowIn" = renderList notShowIn;
+    "DBusActivatable" = boolOrNullToString dbusActivatable;
+    "TryExec" = tryExec;
+    "Exec" = exec;
+    "Path" = path;
+    "Terminal" = boolOrNullToString terminal;
+    "Actions" = renderList (builtins.attrNames actions);
+    "MimeType" = renderList mimeTypes;
+    "Categories" = renderList categories;
+    "Implements" = renderList implements;
+    "Keywords" = renderList keywords;
+    "StartupNotify" = boolOrNullToString startupNotify;
+    "StartupWMClass" = startupWMClass;
+    "URL" = url;
+    "PrefersNonDefaultGPU" = boolOrNullToString prefersNonDefaultGPU;
+    # "SingleMainWindow" = boolOrNullToString singleMainWindow;
+  } // extraConfig;
+
+  # Render a single attribute pair to a Key=Value line.
+  # FIXME: this isn't entirely correct for arbitrary strings, as some characters
+  # need to be escaped. There are currently none in nixpkgs though, so this is OK.
+  renderLine = name: value: if value != null then "${name}=${value}" else null;
+
+  # Render a full section of the file from an attrset.
+  # Null values are intentionally left out.
+  renderSection = sectionName: attrs:
+    lib.pipe attrs [
+      (lib.mapAttrsToList renderLine)
+      (builtins.filter (v: !isNull v))
+      (builtins.concatStringsSep "\n")
+      (section: ''
+        [${sectionName}]
+        ${section}
+      '')
+    ];
+
+  mainSectionRendered = renderSection "Desktop Entry" mainSection;
+
+  # Convert from javaCase names as used in Nix to PascalCase as used in the spec.
+  preprocessAction = { name, icon ? null, exec ? null }: {
+    "Name" = name;
+    "Icon" = icon;
+    "Exec" = exec;
+  };
+  renderAction = name: attrs: renderSection "Desktop Action ${name}" (preprocessAction attrs);
+  actionsRendered = lib.mapAttrsToList renderAction actions;
 
-  # Map all entries to a list of lines
-  desktopFileStrings =
-    [ "[Desktop Entry]" ]
-    ++ builtins.filter
-      (v: v != null)
-      (lib.mapAttrsToList
-        (name: value: if value != null then "${name}=${value}" else null)
-        mainSection
-      )
-    ++ (if extraEntries == "" then [ ] else [ "${extraEntries}" ]);
+  content = [ mainSectionRendered ] ++ actionsRendered;
 in
-runCommandLocal "${name}.desktop"
-{
-  nativeBuildInputs = [ desktop-file-utils ];
+writeTextFile {
+  name = "${name}.desktop";
+  destination = "/share/applications/${name}.desktop";
+  text = builtins.concatStringsSep "\n" content;
+  checkPhase = "${desktop-file-utils}/bin/desktop-file-validate $target";
 }
-  (''
-    mkdir -p "$out/share/applications"
-    cat > "$out/share/applications/${name}.desktop" <<EOF
-    ${builtins.concatStringsSep "\n" desktopFileStrings}
-    EOF
-  '' + lib.optionalString fileValidation ''
-    echo "Running desktop-file validation"
-    desktop-file-validate "$out/share/applications/${name}.desktop"
-  '')