Files
lhns--steam-frame-nix/modules/launchers.nix
T
Pierre Kisters 59afc2f616 Launchers: neutral examples and made-up fixtures
Examples showed specific apps as if the repository installed or
configured them.

- Docs, README, template and option examples: the Signal Flatpak
  (org.signal.Signal, sgnl:// and signalcaptcha://) and a made-up
  org.example.App instead of the previous chat and remote-desktop apps;
  no install lines (the app is installed by the user, as noted).
- Portal fix: the file-dialog symptom phrased generically.
- Launcher check: made-up Flatpak entries org.example.Chat (Electron,
  --file-forwarding @@u %U @@, an action, an SSO scheme) and
  org.example.Remote (Qt, -qwindowtitle %c @@u %u @@, localized keys)
  replace the copied ones; Firefox, gedit and Jellyfin stay.
2026-10-01 18:16:37 +02:00

152 lines
6.7 KiB
Nix

# Launchers (launchers.<desktop ID>, docs/launchers.md): the app's own desktop
# entry, rewritten (launchers/rewrite.awk), under the same desktop ID in
# ~/.local/share/applications. Package entries are rewritten at build time;
# Flatpak and host file entries only exist at runtime, so
# steam-frame-nix-launchers (launchers/generate.sh) writes them to
# <session.runtimeDir>/steam-frame-nix/applications (tmpfs), where the Home
# Manager links point. Replaces the keyring module (steamFrame.keyring.*),
# whose options fail with the new form.
{ config, lib, pkgs, ... }:
let
cfg = config.steamFrame.launchers;
session = config.steamFrame.session;
L = import ./launchers/lib.nix { inherit lib pkgs; };
all = lib.mapAttrs (id: l: l // { inherit id; }) cfg;
runtimeLaunchers = lib.filterAttrs (_: l: L.kindOf l != "package") all;
packageLaunchers = lib.filterAttrs (_: l: L.kindOf l == "package") all;
outDir = "${session.runtimeDir}/steam-frame-nix/applications";
exportDirs = [
"${config.xdg.dataHome}/flatpak/exports/share/applications"
"/var/lib/flatpak/exports/share/applications"
];
generator = L.generator {
launchers = runtimeLaunchers;
runtimeDir = session.runtimeDir;
applicationsDir = "${config.xdg.dataHome}/applications";
inherit exportDirs;
};
exe = lib.getExe generator;
defaults = lib.concatLists (lib.mapAttrsToList (id: l:
map (m: { inherit m id; }) (lib.unique l.defaultFor)) all);
byMime = lib.groupBy (d: d.m) defaults;
mimeAttrs = lib.mapAttrs (_: ds: "${(lib.head ds).id}.desktop") byMime;
removed = what: lib.mkRemovedOptionModule [ "steamFrame" "keyring" what ] ''
Keyring launchers are now steamFrame.launchers.<desktop ID>, made from the
app's own desktop entry (name, icon, actions come from it), e.g.
steamFrame.launchers."org.signal.Signal" = {
keyring = { enable = true; electron = true; };
defaultFor = [ "x-scheme-handler/sgnl" ];
};
steamFrame.launchers.signal.source.package = pkgs.signal-desktop; # a package's entry
(schemeHandlers = [ "x" ] -> defaultFor = [ "x-scheme-handler/x" ];
flatpakArgs, args, mimeTypes, settings keep their meaning; name, icon,
categories, actions, executable, fieldCode, ... are gone.)
See docs/launchers.md.'';
in {
imports = [ ./cleanup.nix (removed "flatpaks") (removed "programs") ];
options.steamFrame.launchers = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule (L.launcherModule { bus = session.bus; }));
default = { };
example = lib.literalExpression ''
{
"org.signal.Signal" = {
keyring = { enable = true; electron = true; };
defaultFor = [ "x-scheme-handler/sgnl" "x-scheme-handler/signalcaptcha" ];
};
"org.example.App".keyring.enable = true;
}
'';
description = ''
Desktop entries by desktop ID (without `.desktop`), made from the app's
own entry (a Flatpak by default) with extra environment, options,
wrappers and MIME defaults, replacing the original in the KDE menu and
the "+" menu. See docs/launchers.md.
'';
};
config = lib.mkMerge [ (lib.mkIf (cfg != { }) {
assertions = lib.concatLists (lib.mapAttrsToList (id: l: [
{
assertion = builtins.match "[A-Za-z0-9._-]+" id != null;
message = "steamFrame.launchers.\"${id}\": not a desktop ID (letters, digits, . _ -; without .desktop).";
}
{
assertion = lib.count (x: x != null) [ l.source.flatpak l.source.package l.source.file ] <= 1;
message = "steamFrame.launchers.\"${id}\": set only one of source.flatpak, source.package, source.file.";
}
{
assertion = L.kindOf l == "flatpak" || l.flatpakArgs == [ ];
message = "steamFrame.launchers.\"${id}\": flatpakArgs is only for Flatpaks (use args, env or wrappers).";
}
{
assertion = !l.keyring.electron || l.keyring.enable;
message = "steamFrame.launchers.\"${id}\": keyring.electron needs keyring.enable.";
}
{
assertion = lib.all (k: builtins.match "[A-Za-z0-9-]+" k != null) (lib.attrNames l.settings)
&& lib.all (v: v == null || !lib.hasInfix "\n" v) (lib.attrValues l.settings);
message = "steamFrame.launchers.\"${id}\".settings: keys are plain key names (no [locale]), values one line.";
}
]) all)
++ lib.mapAttrsToList (m: ds: {
assertion = lib.length ds == 1;
message = "steamFrame.launchers: ${m} is in defaultFor of ${lib.concatMapStringsSep " and " (d: "\"${d.id}\"") ds}.";
}) byMime;
xdg.dataFile = lib.mapAttrs' (id: _: lib.nameValuePair "applications/${id}.desktop" {
source = config.lib.file.mkOutOfStoreSymlink "${outDir}/${id}.desktop";
}) runtimeLaunchers // lib.mapAttrs' (id: l: lib.nameValuePair "applications/${id}.desktop" {
source = L.packageEntry l;
}) packageLaunchers;
xdg.mimeApps = lib.mkIf (defaults != [ ]) {
enable = true;
defaultApplications = mimeAttrs;
associations.added = mimeAttrs;
};
home.packages = lib.mkIf (runtimeLaunchers != { }) [ generator ];
systemd.user.services.steam-frame-nix-launchers = lib.mkIf (runtimeLaunchers != { }) {
Unit.Description = "steam-frame-nix: desktop entries of steamFrame.launchers";
Service = {
Type = "oneshot";
ExecStart = exe;
};
Install.WantedBy = [ "default.target" ];
};
# Flatpak touches .changed after installs, updates and removals.
systemd.user.paths.steam-frame-nix-launchers = lib.mkIf (runtimeLaunchers != { }) {
Unit.Description = "steam-frame-nix: rewrite launchers when their sources change";
Path = {
PathChanged = [
"${config.xdg.dataHome}/flatpak/.changed"
"/var/lib/flatpak/.changed"
] ++ exportDirs ++ lib.unique (lib.filter (f: f != null)
(lib.mapAttrsToList (_: l: l.source.file) runtimeLaunchers));
Unit = "steam-frame-nix-launchers.service";
};
Install.WantedBy = [ "default.target" ];
};
steamFrame.session.services.start =
lib.mkIf (runtimeLaunchers != { }) [ "steam-frame-nix-launchers.path" ];
}) {
# Also without launchers, to remove entries of ones no longer configured.
home.activation.steamFrameLaunchers = lib.hm.dag.entryAfter [ "linkGeneration" ] ''
if [[ ! -d ${lib.escapeShellArg session.runtimeDir} ]]; then
${lib.optionalString (runtimeLaunchers != { }) ''echo "steam-frame-nix: ${session.runtimeDir} doesn't exist (outer session not running?); launchers are written at its next login" >&2''}
:
elif [[ -v DRY_RUN ]]; then
echo "would run: STEAM_FRAME_NIX_RUNTIME_DIR=${session.runtimeDir} ${exe}"
else
STEAM_FRAME_NIX_RUNTIME_DIR=${lib.escapeShellArg session.runtimeDir} ${exe}
fi
'';
} ];
}