1{ lib, writeTextFile, buildPackages }:
2
3# All possible values as defined by the spec, version 1.4.
4# Please keep in spec order for easier maintenance.
5# When adding a new value, don't forget to update the Version field below!
6# See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
7lib.makeOverridable ({ name # The name of the desktop file
8, type ? "Application"
9# version is hardcoded
10, desktopName # The name of the application
11, genericName ? null
12, noDisplay ? null
13, comment ? null
14, icon ? null
15# we don't support the Hidden key - if you don't need something, just don't install it
16, onlyShowIn ? []
17, notShowIn ? []
18, dbusActivatable ? null
19, tryExec ? null
20, exec ? null
21, path ? null
22, terminal ? null
23, actions ? {} # An attrset of [internal name] -> { name, exec?, icon? }
24, mimeTypes ? [] # The spec uses "MimeType" as singular, use plural here to signify list-ness
25, categories ? []
26, implements ? []
27, keywords ? []
28, startupNotify ? null
29, startupWMClass ? null
30, url ? null
31, prefersNonDefaultGPU ? null
32# not supported until version 1.5, which is not supported by our desktop-file-utils as of 2022-02-23
33# , singleMainWindow ? null
34, extraConfig ? {} # Additional values to be added literally to the final item, e.g. vendor extensions
35}:
36let
37 # There are multiple places in the FDO spec that make "boolean" values actually tristate,
38 # e.g. StartupNotify, where "unset" is literally defined as "do something reasonable".
39 # So, handle null values separately.
40 boolOrNullToString = value:
41 if value == null then null
42 else if builtins.isBool value then lib.boolToString value
43 else throw "makeDesktopItem: value must be a boolean or null!";
44
45 # Multiple values are represented as one string, joined by semicolons.
46 # Technically, it's possible to escape semicolons in values with \;, but this is currently not implemented.
47 renderList = key: value:
48 if !builtins.isList value then throw "makeDesktopItem: value for ${key} must be a list!"
49 else if builtins.any (item: lib.hasInfix ";" item) value then throw "makeDesktopItem: values in ${key} list must not contain semicolons!"
50 else if value == [] then null
51 else builtins.concatStringsSep ";" value;
52
53 # The [Desktop Entry] section of the desktop file, as an attribute set.
54 # Please keep in spec order.
55 mainSection = {
56 "Type" = type;
57 "Version" = "1.4";
58 "Name" = desktopName;
59 "GenericName" = genericName;
60 "NoDisplay" = boolOrNullToString noDisplay;
61 "Comment" = comment;
62 "Icon" = icon;
63 "OnlyShowIn" = renderList "onlyShowIn" onlyShowIn;
64 "NotShowIn" = renderList "notShowIn" notShowIn;
65 "DBusActivatable" = boolOrNullToString dbusActivatable;
66 "TryExec" = tryExec;
67 "Exec" = exec;
68 "Path" = path;
69 "Terminal" = boolOrNullToString terminal;
70 "Actions" = renderList "actions" (builtins.attrNames actions);
71 "MimeType" = renderList "mimeTypes" mimeTypes;
72 "Categories" = renderList "categories" categories;
73 "Implements" = renderList "implements" implements;
74 "Keywords" = renderList "keywords" keywords;
75 "StartupNotify" = boolOrNullToString startupNotify;
76 "StartupWMClass" = startupWMClass;
77 "URL" = url;
78 "PrefersNonDefaultGPU" = boolOrNullToString prefersNonDefaultGPU;
79 # "SingleMainWindow" = boolOrNullToString singleMainWindow;
80 } // extraConfig;
81
82 # Render a single attribute pair to a Key=Value line.
83 # FIXME: this isn't entirely correct for arbitrary strings, as some characters
84 # need to be escaped. There are currently none in nixpkgs though, so this is OK.
85 renderLine = name: value: if value != null then "${name}=${value}" else null;
86
87 # Render a full section of the file from an attrset.
88 # Null values are intentionally left out.
89 renderSection = sectionName: attrs:
90 lib.pipe attrs [
91 (lib.mapAttrsToList renderLine)
92 (builtins.filter (v: v != null))
93 (builtins.concatStringsSep "\n")
94 (section: ''
95 [${sectionName}]
96 ${section}
97 '')
98 ];
99
100 mainSectionRendered = renderSection "Desktop Entry" mainSection;
101
102 # Convert from javaCase names as used in Nix to PascalCase as used in the spec.
103 preprocessAction = { name, icon ? null, exec ? null }: {
104 "Name" = name;
105 "Icon" = icon;
106 "Exec" = exec;
107 };
108 renderAction = name: attrs: renderSection "Desktop Action ${name}" (preprocessAction attrs);
109 actionsRendered = lib.mapAttrsToList renderAction actions;
110
111 content = [ mainSectionRendered ] ++ actionsRendered;
112in
113writeTextFile {
114 name = "${name}.desktop";
115 destination = "/share/applications/${name}.desktop";
116 text = builtins.concatStringsSep "\n" content;
117 checkPhase = ''${buildPackages.desktop-file-utils}/bin/desktop-file-validate "$target"'';
118})