makeDesktopItem: allow configuring output directory (#368983)
This commit is contained in:
@@ -1,119 +1,207 @@
|
||||
{ lib, writeTextFile, buildPackages }:
|
||||
|
||||
# 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/latest
|
||||
lib.makeOverridable ({ name # The name of the desktop file
|
||||
, type ? "Application"
|
||||
# version is hardcoded
|
||||
, desktopName # The name of the application
|
||||
, genericName ? 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
|
||||
# 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
|
||||
{
|
||||
lib,
|
||||
writeTextFile,
|
||||
buildPackages,
|
||||
}:
|
||||
let
|
||||
# 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 throw "makeDesktopItem: value must be a boolean or null!";
|
||||
|
||||
# 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 = key: value:
|
||||
if !builtins.isList value then throw "makeDesktopItem: value for ${key} must be a list!"
|
||||
else if builtins.any (item: lib.hasInfix ";" item) value then throw "makeDesktopItem: values in ${key} 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" = type;
|
||||
"Version" = "1.4";
|
||||
"Name" = desktopName;
|
||||
"GenericName" = genericName;
|
||||
"NoDisplay" = boolOrNullToString noDisplay;
|
||||
"Comment" = comment;
|
||||
"Icon" = icon;
|
||||
"OnlyShowIn" = renderList "onlyShowIn" onlyShowIn;
|
||||
"NotShowIn" = renderList "notShowIn" notShowIn;
|
||||
"DBusActivatable" = boolOrNullToString dbusActivatable;
|
||||
"TryExec" = tryExec;
|
||||
"Exec" = exec;
|
||||
"Path" = path;
|
||||
"Terminal" = boolOrNullToString terminal;
|
||||
"Actions" = renderList "actions" (builtins.attrNames actions);
|
||||
"MimeType" = renderList "mimeTypes" mimeTypes;
|
||||
"Categories" = renderList "categories" categories;
|
||||
"Implements" = renderList "implements" implements;
|
||||
"Keywords" = renderList "keywords" keywords;
|
||||
"StartupNotify" = boolOrNullToString startupNotify;
|
||||
"StartupWMClass" = startupWMClass;
|
||||
"URL" = url;
|
||||
"PrefersNonDefaultGPU" = boolOrNullToString prefersNonDefaultGPU;
|
||||
# "SingleMainWindow" = boolOrNullToString singleMainWindow;
|
||||
} // extraConfig;
|
||||
/**
|
||||
A utility builder to create a desktop entry file at a predetermined location (by default, $out/share/applications).
|
||||
|
||||
# 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;
|
||||
# Examples
|
||||
|
||||
# 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: v != null))
|
||||
(builtins.concatStringsSep "\n")
|
||||
(section: ''
|
||||
[${sectionName}]
|
||||
${section}
|
||||
'')
|
||||
];
|
||||
```nix
|
||||
makeDesktopItem {
|
||||
name = "Eclipse";
|
||||
exec = "eclipse";
|
||||
icon = "eclipse";
|
||||
comment = "Integrated Development Environment";
|
||||
desktopName = "Eclipse IDE";
|
||||
genericName = "Integrated Development Environment";
|
||||
categories = [ "Development" ];
|
||||
}
|
||||
=> «derivation /nix/store/sh017x5n50i2qwns2na1sxdp7zb2zgcw-Eclipse.desktop.drv»
|
||||
```
|
||||
|
||||
mainSectionRendered = renderSection "Desktop Entry" mainSection;
|
||||
# Type
|
||||
|
||||
# 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;
|
||||
```
|
||||
makeDesktopItem :: AttrSet -> Derivation
|
||||
```
|
||||
|
||||
extension = if type == "Directory" then "directory" else "desktop";
|
||||
content = [ mainSectionRendered ] ++ actionsRendered;
|
||||
in
|
||||
writeTextFile {
|
||||
name = "${name}.${extension}";
|
||||
destination = "/share/applications/${name}.${extension}";
|
||||
text = builtins.concatStringsSep "\n" content;
|
||||
checkPhase = ''${buildPackages.desktop-file-utils}/bin/desktop-file-validate "$target"'';
|
||||
})
|
||||
# Input
|
||||
|
||||
`attrs`
|
||||
|
||||
: An AttrSet with the following definitions. See https://specifications.freedesktop.org/desktop-entry-spec/1.4/recognized-keys.html#id-1.7.6 for definitions.
|
||||
|
||||
- `name` (string): The name of the desktop file (excluding the .desktop or .directory file extensions)
|
||||
- `destination` (string): The directory that will contain the desktop entry file (Default: "/share/applications")
|
||||
- `type` ("Application" | "Link" | "Directory"): The `Type` of the desktop entry
|
||||
- `desktopName` (string): The `Name` of the desktop entry
|
||||
- `genericName` (string): The `GenericName` of the desktop entry
|
||||
- `noDisplay` (bool): The `NoDisplay` of the desktop entry
|
||||
- `comment` (string): The `Comment` of the desktop entry
|
||||
- `icon` (string): The `Icon` of the desktop entry
|
||||
- `onlyShowIn` (string[]): The `OnlyShowIn` of the desktop entry
|
||||
- `notShowIn` (string[]): The `NotShowIn` of the desktop entry
|
||||
- `dbusActivatable` (bool): The `DBusActivatable` of the desktop entry
|
||||
- `tryExec` (string): The `TryExec` of the desktop entry
|
||||
- `exec` (string): The `Exec` of the desktop entry
|
||||
- `path` (string): The `Path` of the desktop entry
|
||||
- `terminal` (bool): The `Terminal` of the desktop entry
|
||||
- `actions` (AttrSet): An attrset of [internal name] -> { name, exec?, icon? } representing the `Actions` of the desktop entry
|
||||
- `mimeTypes` (string[]): The `MimeType` of the desktop entry
|
||||
- `categories` (string[]): The `Categories` of the desktop entry; see https://specifications.freedesktop.org/menu-spec/1.0/category-registry.html for possible values
|
||||
- `implements` (string[]): The `Implements` of the desktop entry
|
||||
- `keywords` (string[]): The `Keywords` of the desktop entry
|
||||
- `startupNotify` (bool): The `StartupNotify` of the desktop entry
|
||||
- `startupWMClass` (string): The `StartupWMClass` of the desktop entry
|
||||
- `url` (string): The `URL` of the Link-type desktop entry
|
||||
- `prefersNonDefaultGPU` (bool): The `PrefersNonDefaultGPU` (non-standard) of the desktop entry
|
||||
- `extraConfig` (AttrSet): Additional values to be added literally to the final item, e.g. vendor extensions
|
||||
|
||||
# Output
|
||||
|
||||
A derivation that contains the output desktop entry file.
|
||||
|
||||
# Developer Note
|
||||
|
||||
All possible values are 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/latest
|
||||
*/
|
||||
lib.makeOverridable (
|
||||
{
|
||||
name, # The name of the desktop file
|
||||
destination ? "/share/applications",
|
||||
type ? "Application",
|
||||
# version is hardcoded
|
||||
desktopName, # The name of the application
|
||||
genericName ? 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,
|
||||
# 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
|
||||
# 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
|
||||
throw "makeDesktopItem: value must be a boolean or null!";
|
||||
|
||||
# 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 =
|
||||
key: value:
|
||||
if !builtins.isList value then
|
||||
throw "makeDesktopItem: value for ${key} must be a list!"
|
||||
else if builtins.any (item: lib.hasInfix ";" item) value then
|
||||
throw "makeDesktopItem: values in ${key} 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" = type;
|
||||
"Version" = "1.4";
|
||||
"Name" = desktopName;
|
||||
"GenericName" = genericName;
|
||||
"NoDisplay" = boolOrNullToString noDisplay;
|
||||
"Comment" = comment;
|
||||
"Icon" = icon;
|
||||
"OnlyShowIn" = renderList "onlyShowIn" onlyShowIn;
|
||||
"NotShowIn" = renderList "notShowIn" notShowIn;
|
||||
"DBusActivatable" = boolOrNullToString dbusActivatable;
|
||||
"TryExec" = tryExec;
|
||||
"Exec" = exec;
|
||||
"Path" = path;
|
||||
"Terminal" = boolOrNullToString terminal;
|
||||
"Actions" = renderList "actions" (builtins.attrNames actions);
|
||||
"MimeType" = renderList "mimeTypes" mimeTypes;
|
||||
"Categories" = renderList "categories" categories;
|
||||
"Implements" = renderList "implements" implements;
|
||||
"Keywords" = renderList "keywords" 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: v != null))
|
||||
(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;
|
||||
|
||||
extension = if type == "Directory" then "directory" else "desktop";
|
||||
content = [ mainSectionRendered ] ++ actionsRendered;
|
||||
in
|
||||
writeTextFile {
|
||||
name = "${name}.${extension}";
|
||||
destination = "${destination}/${name}.${extension}";
|
||||
text = builtins.concatStringsSep "\n" content;
|
||||
checkPhase = ''${buildPackages.desktop-file-utils}/bin/desktop-file-validate "$target"'';
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user