diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d8640934d..2b3a9a5d5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -37,6 +37,11 @@ /modules/misc/xdg-system-dirs.nix @tadfisher /tests/modules/misc/xdg/system-dirs.nix @tadfisher +/modules/misc/xdg-desktop-entries.nix @cwyc +/tests/modules/misc/xdg/desktop-entries.nix @cwyc +/tests/modules/misc/xdg/desktop-full-expected.desktop @cwyc +/tests/modules/misc/xdg/desktop-min-expected.desktop @cwyc + /modules/programs/aria2.nix @JustinLovinger /modules/programs/autojump.nix @evanjs diff --git a/modules/misc/xdg-desktop-entries.nix b/modules/misc/xdg-desktop-entries.nix new file mode 100644 index 000000000..382d6c2b5 --- /dev/null +++ b/modules/misc/xdg-desktop-entries.nix @@ -0,0 +1,178 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + desktopEntry = { + options = { + # Since this module uses the nixpkgs/pkgs/build-support/make-desktopitem function, + # our options and defaults follow its parameters, with the following exceptions: + + # `desktopName` on makeDesktopItem is controlled by `name`. + # This is what we'd commonly consider the name of the application. + # `name` on makeDesktopItem is controlled by this module's key in the attrset. + # This is the file's filename excluding ".desktop". + + # `extraEntries` on makeDesktopItem is controlled by `extraConfig`, + # and `extraDesktopEntries` by `settings`, + # to match what's commonly used by other home manager modules. + + # `startupNotify` on makeDesktopItem asks for "true" or "false" strings, + # for usability's sake we ask for a boolean. + + # `mimeType` and `categories` on makeDesktopItem ask for a string in the format "one;two;three;", + # for the same reason we ask for a list of strings. + + # Descriptions are taken from the desktop entry spec: + # https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#recognized-keys + + type = mkOption { + description = "The type of the desktop entry."; + default = "Application"; + type = types.enum [ "Application" "Link" "Directory" ]; + }; + + exec = mkOption { + description = "Program to execute, possibly with arguments."; + type = types.str; + }; + + icon = mkOption { + description = "Icon to display in file manager, menus, etc."; + type = types.nullOr types.str; + default = null; + }; + + comment = mkOption { + description = "Tooltip for the entry."; + type = types.nullOr types.str; + default = null; + }; + + terminal = mkOption { + description = "Whether the program runs in a terminal window."; + type = types.bool; + default = false; + }; + + name = mkOption { + description = "Specific name of the application."; + type = types.str; + }; + + genericName = mkOption { + description = "Generic name of the application."; + type = types.nullOr types.str; + default = null; + }; + + mimeType = mkOption { + description = "The MIME type(s) supported by this application."; + type = types.nullOr (types.listOf types.str); + default = null; + }; + + categories = mkOption { + description = + "Categories in which the entry should be shown in a menu."; + type = types.nullOr (types.listOf types.str); + default = null; + }; + + startupNotify = mkOption { + description = '' + If true, it is KNOWN that the application will send a "remove" + message when started with the DESKTOP_STARTUP_ID + environment variable set. If false, it is KNOWN that the application + does not work with startup notification at all.''; + type = types.nullOr types.bool; + default = null; + }; + + extraConfig = mkOption { + description = '' + Extra configuration. Will be appended to the end of the file and + may thus contain extra sections. + ''; + type = types.lines; + default = ""; + }; + + settings = mkOption { + type = types.attrsOf types.string; + description = '' + Extra key-value pairs to add to the [Desktop Entry] section. + This may override other values. + ''; + default = { }; + example = literalExample '' + { + Keywords = "calc;math"; + DBusActivatable = "false"; + } + ''; + }; + + fileValidation = mkOption { + type = types.bool; + description = "Whether to validate the generated desktop file."; + default = true; + }; + }; + }; + + #formatting helpers + ifNotNull = a: a': if a == null then null else a'; + stringBool = bool: if bool then "true" else "false"; + semicolonList = list: + (concatStringsSep ";" list) + ";"; # requires trailing semicolon + + #passes config options to makeDesktopItem in expected format + makeFile = name: config: + pkgs.makeDesktopItem { + name = name; + type = config.type; + exec = config.exec; + icon = config.icon; + comment = config.comment; + terminal = config.terminal; + desktopName = config.name; + genericName = config.genericName; + mimeType = ifNotNull config.mimeType (semicolonList config.mimeType); + categories = + ifNotNull config.categories (semicolonList config.categories); + startupNotify = + ifNotNull config.startupNotify (stringBool config.startupNotify); + extraEntries = config.extraConfig; + extraDesktopEntries = config.settings; + }; +in { + meta.maintainers = with maintainers; [ cwyc ]; + + options.xdg.desktopEntries = mkOption { + description = '' + Desktop Entries allow applications to be shown in your desktop environment's app launcher. + You can define entries for programs without entries or override existing entries. + See for more information on options. + ''; + default = { }; + type = types.attrsOf (types.submodule desktopEntry); + example = literalExample '' + { + firefox = { + name = "Firefox"; + genericName = "Web Browser"; + exec = "firefox %U"; + terminal = false; + categories = [ "Application" "Network" "WebBrowser" ]; + mimeType = [ "text/html" "text/xml" ]; + }; + } + ''; + }; + + config.home.packages = mkIf (config.xdg.desktopEntries != { }) + (map hiPrio # we need hiPrio to override existing entries + (attrsets.mapAttrsToList makeFile config.xdg.desktopEntries)); + +} diff --git a/modules/modules.nix b/modules/modules.nix index e7f4c1b63..c0dd457c7 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -41,6 +41,7 @@ let (loadModule ./misc/version.nix { }) (loadModule ./misc/vte.nix { }) (loadModule ./misc/xdg-system-dirs.nix { condition = hostPlatform.isLinux; }) + (loadModule ./misc/xdg-desktop-entries.nix { condition = hostPlatform.isLinux; }) (loadModule ./misc/xdg-mime.nix { condition = hostPlatform.isLinux; }) (loadModule ./misc/xdg-mime-apps.nix { condition = hostPlatform.isLinux; }) (loadModule ./misc/xdg-user-dirs.nix { condition = hostPlatform.isLinux; }) diff --git a/tests/modules/misc/xdg/default.nix b/tests/modules/misc/xdg/default.nix index 45610154c..b637cd1bf 100644 --- a/tests/modules/misc/xdg/default.nix +++ b/tests/modules/misc/xdg/default.nix @@ -2,4 +2,5 @@ xdg-mime-apps-basics = ./mime-apps-basics.nix; xdg-file-attr-names = ./file-attr-names.nix; xdg-system-dirs = ./system-dirs.nix; + xdg-desktop-entries = ./desktop-entries.nix; } diff --git a/tests/modules/misc/xdg/desktop-entries.nix b/tests/modules/misc/xdg/desktop-entries.nix new file mode 100644 index 000000000..098aa7eee --- /dev/null +++ b/tests/modules/misc/xdg/desktop-entries.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + xdg.desktopEntries = { + full = { # full definition + type = "Application"; + exec = "test --option"; + icon = "test"; + comment = "My Application"; + terminal = true; + name = "Test"; + genericName = "Web Browser"; + mimeType = [ "text/html" "text/xml" ]; + categories = [ "Network" "WebBrowser" ]; + startupNotify = false; + extraConfig = '' + [X-ExtraSection] + Exec=foo -o + ''; + settings = { + Keywords = "calc;math"; + DBusActivatable = "false"; + }; + fileValidation = true; + }; + min = { # minimal definition + exec = "test --option"; + name = "Test"; + }; + }; + + #testing that preexisting entries in the store are overridden + home.packages = [ + (pkgs.makeDesktopItem { + name = "full"; + desktopName = "We don't want this"; + exec = "no"; + }) + (pkgs.makeDesktopItem { + name = "min"; + desktopName = "We don't want this"; + exec = "no"; + }) + ]; + + nmt.script = '' + assertFileExists home-path/share/applications/full.desktop + assertFileExists home-path/share/applications/min.desktop + assertFileContent home-path/share/applications/full.desktop \ + ${./desktop-full-expected.desktop} + assertFileContent home-path/share/applications/min.desktop \ + ${./desktop-min-expected.desktop} + ''; + }; +} diff --git a/tests/modules/misc/xdg/desktop-full-expected.desktop b/tests/modules/misc/xdg/desktop-full-expected.desktop new file mode 100644 index 000000000..fd5ace1b9 --- /dev/null +++ b/tests/modules/misc/xdg/desktop-full-expected.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Categories=Network;WebBrowser; +Comment=My Application +DBusActivatable=false +Exec=test --option +GenericName=Web Browser +Icon=test +Keywords=calc;math +MimeType=text/html;text/xml; +Name=Test +StartupNotify=false +Terminal=true +Type=Application +[X-ExtraSection] +Exec=foo -o + diff --git a/tests/modules/misc/xdg/desktop-min-expected.desktop b/tests/modules/misc/xdg/desktop-min-expected.desktop new file mode 100644 index 000000000..9475024ae --- /dev/null +++ b/tests/modules/misc/xdg/desktop-min-expected.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Exec=test --option +Name=Test +Terminal=false +Type=Application