diff --git a/README.md b/README.md index 6059f0c..f1df55c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ configuration, limitations and how it works. - [Firefox](docs/firefox.md) (`firefox`): launcher for the Flatpak with a VR fullscreen fix, optional AV1 off, a separate desktop profile, optionally the default browser. - [Jellyfin](docs/jellyfin.md) (`jellyfin.hardwareDecoding`): hardware video decoding in the Jellyfin Desktop Flatpak. -- [Keyring launchers](docs/keyring.md) (`keyring.flatpaks`, `keyring.programs`): apps (Flatpak or Nix, Electron too) keep their KDE wallet logins in both sessions. +- [Launchers](docs/launchers.md) (`launchers.`): an app's own desktop entry (Flatpak, Nix package) with extra options, environment, wrappers and MIME defaults; `keyring` keeps its KDE wallet logins in both sessions (Electron too). - [Docker](docs/docker.md) (`docker`): rootless Docker as a user service, CLI working in both sessions. **Your own patches** of Steam's UI, and fixing patches after a Steam update: [UI patches](docs/ui-patches.md). @@ -115,10 +115,10 @@ What this means for you: ([session settings](docs/session.md#session-settings-and-services)). - **Wallet:** there should be one `kwalletd6`, on the outer bus; apps started from the desktop would otherwise start a second one whose secrets VR can't - see. List such apps in [keyring](docs/keyring.md). + see. Give such apps a [launcher](docs/launchers.md) with `keyring.enable`. - **Launchers:** the "+" menu only sees `~/.local/share/applications` (not - `~/.nix-profile/share`), so entries are written there, shadowing - Flatpak/package entries with the same ID. + `~/.nix-profile/share`), so [launchers](docs/launchers.md) are linked + there, replacing Flatpak/package entries with the same ID. - **Keyboard layout, clipboard, Firefox:** each session has its own; see [keyboard layout](docs/session.md#keyboard-layout), [clipboard sync](docs/session.md#clipboard-sync), @@ -195,10 +195,9 @@ these two files ([`template/`](template), with more comments): firefox.disableAv1 = true; firefox.defaultBrowser = true; jellyfin.hardwareDecoding.enable = true; # install the Flatpak yourself - keyring.flatpaks."im.riot.Riot" = { # Element: logins in both sessions - name = "Element"; - electron = true; - schemeHandlers = [ "element" "io.element.desktop" ]; + launchers."im.riot.Riot" = { # Element: logins in both sessions + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/element" "x-scheme-handler/io.element.desktop" ]; }; docker.enable = true; # rootless }; @@ -215,9 +214,9 @@ home-manager switch --flake .#steamos # manual setup, from the flake's director In your own flake, add the input as above and `steam-frame-nix.homeManagerModules.default` to the modules. `default` imports all modules; single ones: -`homeManagerModules.{session,portal,keyboard-layout,vr-keyboard-extra-keys,vr-keyboard,hidden-apps,steam-ui-patches,launcher-menu,steamvr-debugger,cleanup,dashboard-windows,steam-close-button,window-curvature,frame-controls,clipboard-sync,firefox,jellyfin,keyring,docker}` -(`steam-keyboard-patch` still works as the former name of -`vr-keyboard-extra-keys`). Every module imports `cleanup` (see +`homeManagerModules.{session,portal,keyboard-layout,vr-keyboard-extra-keys,vr-keyboard,hidden-apps,steam-ui-patches,launcher-menu,steamvr-debugger,cleanup,dashboard-windows,steam-close-button,window-curvature,frame-controls,clipboard-sync,firefox,jellyfin,launchers,docker}` +(`steam-keyboard-patch` and `keyring` still work as the former names of +`vr-keyboard-extra-keys` and `launchers`). Every module imports `cleanup` (see [Changes outside Nix](#changes-outside-nix-exceptions)); which file is which: [Repository layout](docs/development.md). @@ -227,7 +226,7 @@ which: [Repository layout](docs/development.md). |---|---|---|---| | `steamFrame.session.runtimeDir` | str | `"/run/user/1000"` | `XDG_RUNTIME_DIR` of the outer (Steam/VR) session. | | `steamFrame.session.bus` | str | `"unix:path=${runtimeDir}/bus"` | Outer session D-Bus (user manager, `kwalletd6`). | -| `steamFrame.session.busEnv` | str, read-only | `"env DBUS_SESSION_BUS_ADDRESS=${bus}"` | Prefix for launchers that must use the outer bus. | +| `steamFrame.session.busEnv` | str, read-only | `"env DBUS_SESSION_BUS_ADDRESS=${bus}"` | `Exec=` prefix for hand-written entries that must use the outer bus. | | `steamFrame.session.services.start` | list of str | `[ ]` | User units started on switch if not running. | | `steamFrame.session.services.restart` | list of str | `[ ]` | User units restarted on every switch. | | `steamFrame.session.services.stop` | list of str | `[ ]` | User units stopped on switch if running (e.g. of a disabled feature). | @@ -306,22 +305,21 @@ which: [Repository layout](docs/development.md). | `steamFrame.jellyfin.hardwareDecoding.enable` | bool | `false` | Hardware video decoding in the Jellyfin Desktop Flatpak, see [Jellyfin](docs/jellyfin.md). | | `steamFrame.jellyfin.hardwareDecoding.hwdec` | str | `"v4l2m2m-copy,auto-copy"` | mpv `hwdec` used instead of Jellyfin's automatic one. | | `steamFrame.jellyfin.hardwareDecoding.command` | str, read-only | | The `flatpak run …` command line of the desktop entry, for a terminal. | -| `steamFrame.keyring.flatpaks` | attrs of submodules | `{ }` | Flatpaks by app ID getting a wallet launcher (outer bus, wallet D-Bus names), see [Keyring launchers](docs/keyring.md). Fields below. | -| `steamFrame.keyring.programs` | attrs of submodules | `{ }` | Other programs by desktop ID (without `.desktop`) getting a wallet launcher (outer bus). Fields below. | -| `steamFrame.keyring.{flatpaks,programs}..name` | str | required | `Name=`. | -| `steamFrame.keyring.{flatpaks,programs}..genericName`, `.comment`, `.startupWMClass` | null or str | `null` | `GenericName=`, `Comment=`, `StartupWMClass=`. | -| `steamFrame.keyring.{flatpaks,programs}..icon` | null or str | flatpaks: the app ID; programs: `null` | `Icon=`. | -| `steamFrame.keyring.{flatpaks,programs}..categories` | list of str | `[ ]` | `Categories=`. | -| `steamFrame.keyring.{flatpaks,programs}..schemeHandlers` | list of str | `[ ]` | URL schemes (e.g. login callbacks) the app handles and is made the default and recommended handler for. | -| `steamFrame.keyring.{flatpaks,programs}..mimeTypes` | list of str | `[ ]` | Further `MimeType=` entries, not made default. | -| `steamFrame.keyring.{flatpaks,programs}..electron` | bool | `false` | Pass `--password-store=kwallet6` (Electron apps). | -| `steamFrame.keyring.{flatpaks,programs}..args` | list of str | `[ ]` | Further app arguments (desktop entry syntax). | -| `steamFrame.keyring.{flatpaks,programs}..fieldCode` | `"%U"`, `"%u"`, `"%F"`, `"%f"`, `""` | `"%U"` | How the entry passes URLs/files. | -| `steamFrame.keyring.{flatpaks,programs}..actions` | attrs of `{ name; args; }` | `{ }` | Desktop actions, each running the command with its args. | -| `steamFrame.keyring.{flatpaks,programs}..settings` | attrs of str | `{ }` | Further `[Desktop Entry]` keys. | -| `steamFrame.keyring.{flatpaks,programs}..command` | str, read-only | | The command line without args, for a terminal. | -| `steamFrame.keyring.flatpaks..flatpakArgs` | list of str | `[ ]` | Further `flatpak run` options. | -| `steamFrame.keyring.programs..executable` | str | required | The program to run, e.g. `"${pkgs.claude-desktop}/bin/claude-desktop"`. | +| `steamFrame.launchers` | attrs of submodules | `{ }` | Launchers by desktop ID (without `.desktop`): the app's own entry, rewritten, see [Launchers](docs/launchers.md). Fields below. | +| `steamFrame.launchers..source.flatpak` | null or str | the desktop ID, if no other source | Flatpak app ID whose exported entry is rewritten (at runtime). | +| `steamFrame.launchers..source.package` | null or package | `null` | Package whose `share/applications/.desktop` is rewritten (at build time; the build fails without it). | +| `steamFrame.launchers..source.file` | null or str | `null` | Desktop entry on the host, rewritten at runtime. | +| `steamFrame.launchers..keyring.enable` | bool | `false` | KDE wallet shared by both sessions: outer bus; Flatpaks may talk to `org.kde.kwalletd6`, `org.freedesktop.secrets`. | +| `steamFrame.launchers..keyring.electron` | bool | `false` | Pass `--password-store=kwallet6` (Electron apps; needs `keyring.enable`). | +| `steamFrame.launchers..defaultFor` | list of str | `[ ]` | MIME types / `x-scheme-handler/` the app becomes the default and a recommended handler for (also in `MimeType=`); a type claimed twice fails. | +| `steamFrame.launchers..mimeTypes` | list of str | `[ ]` | Added to `MimeType=`, not made default. | +| `steamFrame.launchers..flatpakArgs` | list of str | `[ ]` | `flatpak run` options before the app ID (Flatpaks only). | +| `steamFrame.launchers..env` | attrs of str | `{ }` | App environment: `--env=K=V` for Flatpaks, like `hostEnv` otherwise. | +| `steamFrame.launchers..hostEnv` | attrs of str | `{ }` | Environment of the started process (`env K=V`; for Flatpaks the `flatpak` client). | +| `steamFrame.launchers..args` | list of str | `[ ]` | Arguments right after the app ID / program. | +| `steamFrame.launchers..wrappers` | list of str | `[ ]` | Commands the command line is passed to, outermost first. | +| `steamFrame.launchers..settings` | attrs of null or str | `{ }` | `[Desktop Entry]` keys to set (key-file syntax) or remove (`null`); drops their localized variants. | +| `steamFrame.launchers..command` | str, read-only | | About the command the entry runs, for a terminal. | | `steamFrame.docker.enable` | bool | `false` | Rootless Docker as a user service, CLI for both sessions, see [Docker](docs/docker.md). | | `steamFrame.docker.package` | package | `pkgs.docker` | Docker package (daemon and CLI). | | `steamFrame.docker.host` | str, read-only | `"unix://${runtimeDir}/docker.sock"` | The daemon's `DOCKER_HOST` (the CLI's default). | @@ -344,6 +342,12 @@ Renamed options still work under their old names, with a warning: | `dashboard.windowCurvature.snapPixels`, `snapPoints` | `detentPixels`, `detentPoints` | | `dashboard.windowCurvature.dragThreshold` | `dragThresholdPixels` | +Removed options fail with the new form: + +| Old | New | +|---|---| +| `keyring.flatpaks.`, `keyring.programs.` | `launchers..keyring` (fields: [Launchers](docs/launchers.md#configuration)) | + ## Changes outside Nix (exceptions) Everything not listed here is a Home Manager link into the Nix store or @@ -355,6 +359,8 @@ lives in memory (the UI patches). These are written at runtime: | `~/.local/state/steam-frame-nix/steamvr-debugger.armed` | SteamVR debugger: the key's previous value | while SteamVR runs; after a power loss until the next SteamVR start or cleanup | SteamVR stopping; `steam-frame-nix-cleanup` | | `/run/user/1000/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf`, `/run/user/1000/steam-frame-nix/steamvr-debugger-restore` | SteamVR debugger: puts the key back when SteamVR stops, without Nix | until reboot (tmpfs) | reboot; `steam-frame-nix-cleanup` while SteamVR is stopped and the debugger is off | | `~/.local/state/steam-frame-nix/ui-patches/.json` | Saved choices of dashboard patches ([persistent state](docs/ui-patches.md#persistent-state)): window control bar placements (`frame-controls`), "Steam hidden" (`steam-close-button`). SteamOS's `steamvr.service` deletes `~/.cache/SteamVR` (the dashboard's own browser storage) on every SteamVR start. | until removed: kept when a patch is disabled (the choices come back when you enable it again) | `steam-frame-nix-cleanup --all`, `install.sh uninstall` | +| `/run/user/1000/steam-frame-nix/applications/.desktop` (with `..desktop.sum`, `.lock`) | [Launchers](docs/launchers.md) of Flatpaks and host files; `~/.local/share/applications/.desktop` is a Home Manager link to it | until reboot (tmpfs), written again at login | reboot; the next switch or rewrite when the launcher or its app is gone; `steam-frame-nix-cleanup --all` | +| mtime of `~/.local/share/applications` | [Launchers](docs/launchers.md#how-it-works): a running Steam rescans the "+" menu | only the directory's timestamp | nothing to remove | | mtime of `~/.local/share/icons/hicolor` | [icon fallbacks](docs/launcher-menu.md#icon-fallbacks): a running Steam rescans icons | only the directory's timestamp | nothing to remove | **`steam-frame-nix-cleanup`** (`install.sh cleanup`, @@ -379,8 +385,8 @@ versions left: [docs/cleanup.md](docs/cleanup.md). - clipboard-sync runs from KDE autostart (a Home Manager link). - Firefox: the desktop profile's `user.js` link exists only while its Firefox runs (see [Firefox](docs/firefox.md#how-it-works)). -- Jellyfin, keyring launchers: the permissions are `flatpak run` options of - the desktop entries, not Flatpak overrides. +- Launchers (Jellyfin, wallet access): the permissions are `flatpak run` + options of the desktop entries, not Flatpak overrides. - Docker: the daemon's socket in `/run/user/1000` (tmpfs) exists while `docker.service` runs. diff --git a/docs/cleanup.md b/docs/cleanup.md index ceec38d..93cc61a 100644 --- a/docs/cleanup.md +++ b/docs/cleanup.md @@ -25,6 +25,7 @@ is reported as "left alone" and never touched: | `firefox`: `user.js` in Firefox profiles | links to `/app/etc/firefox/steam-frame-nix-desktop-user.js` (left alone while the profile is in use), older links to `*-firefox-*user.js` and copies starting with the steam-frame-nix marker comment, and the values they left in `prefs.js` (only with Firefox closed) | | `jellyfin` | the hwdec shim entries in the Jellyfin Flatpak's user override `~/.local/share/flatpak/overrides/org.jellyfin.JellyfinDesktop` (nix-flatpak), an empty override file, and the shim copy of earlier versions in `~/.var/app/org.jellyfin.JellyfinDesktop` (marker `~/.local/state/steam-frame-nix/jellyfin-hwdec-shim`) | | `ui-state`: `~/.local/state/steam-frame-nix/ui-patches/.json` | the dashboard patches' saved choices; `--all` only, never `--orphans`; stray `*.json.tmp` files | +| `launchers`: `/run/user/1000/steam-frame-nix/applications` | the entries of [launchers](launchers.md) (tmpfs); `--all` only (switches remove those of removed launchers themselves) | | `dirs` | `~/.local/state/steam-frame-nix` and `/run/user/1000/steam-frame-nix` when empty | ### Left by older versions diff --git a/docs/development.md b/docs/development.md index 951b1ab..12d13f7 100644 --- a/docs/development.md +++ b/docs/development.md @@ -53,12 +53,18 @@ modules/ keyboard-layout.nix gamescope-session drop-in with XKB_DEFAULT_* (keyboard.layout) clipboard-sync.nix app: KDE autostart of clipboard-sync (clipboardSync) hidden-apps.nix Hidden=true desktop entries (launcherMenu.hiddenApps) - keyring.nix app: launchers sharing the KDE wallet (keyring) + launchers.nix links, MIME defaults, units of the launchers (launchers.) + launchers/ + lib.nix the launcher option type, Exec quoting, package entries (build) + rewrite.awk build+service: rewrites a desktop entry's Exec lines and keys + generate.sh service steam-frame-nix-launchers (+ .path), switch: entries + of Flatpaks/host files in /steam-frame-nix/applications + check.nix, fixtures/ test: real entries rewritten (fixtures/expected), generator runs docker.nix service: rootless dockerd (docker) - firefox.nix Flatpak prefs extension and launcher entry (firefox) - firefox/launcher.nix app: launcher script (desktop profile, fullscreen fix) - firefox/check.nix test: launcher against a fake flatpak - jellyfin.nix desktop entry with flatpak run options (jellyfin.hardwareDecoding) + firefox.nix Flatpak prefs extension and launcher (firefox) + firefox/wrapper.nix app: launcher wrapper (desktop profile, fullscreen fix) + firefox/check.nix test: wrapper against a fake flatpak + jellyfin.nix launcher with flatpak run options (jellyfin.hardwareDecoding) jellyfin/mpv-hwdec-shim.c app: LD_PRELOAD shim, hwdec auto* -> v4l2m2m-copy jellyfin/shim.nix build: the shim as lib/mpv-hwdec-shim.so jellyfin/check.nix test: shim ELF and rewriting @@ -116,6 +122,7 @@ they are kept even where a file name says more: | `window-curvature` | `dashboard.windowCurvature` | `window-curvature` (SteamVR) | `steam-ui-patches` | | `frame-controls` | `dashboard.frameControls` | `frame-controls` (SteamVR, state) | `steam-ui-patches` | | `steamvr-debugger` | `steamvrDebugger` | | `steamvr-webhelper-debugger` | +| `launchers` | `launchers` | | `steam-frame-nix-launchers` (`.service`, `.path`) | Log of all patches: `journalctl --user -u steam-ui-patches -u steam-keyboard-patch -u vr-keyboard-relay`. diff --git a/docs/firefox.md b/docs/firefox.md index 39b55d7..0a3ed5f 100644 --- a/docs/firefox.md +++ b/docs/firefox.md @@ -14,9 +14,10 @@ browser the portal opens links with the first installed `https` handler ## What you get -A launcher for the Flathub Firefox Flatpak (`org.mozilla.firefox`, stable; -install it yourself). The launcher shadows the Flatpak's own entry (same -ID), so default-browser associations keep working. +A [launcher](launchers.md) for the Flathub Firefox Flatpak +(`org.mozilla.firefox`, stable; install it yourself): the Flatpak's own +entry with a wrapper in front, same ID, so default-browser associations +keep working. - **`vrFullscreenFix`** (on): `full-screen-api.ignore-widgets` makes fullscreen fill just the window. Not applied in the desktop profile. @@ -26,11 +27,11 @@ ID), so default-browser associations keep working. - **`prefs`:** further `about:config` values for every profile; they can also override the fixes above. - **`desktopProfile`** (`"desktop"`): in the nested desktop the launcher - uses this separate profile (a normal Firefox profile with its own browser + uses this separate profile (not for "Open Profile Manager") (a normal Firefox profile with its own browser data, created on first use). `null`: the default profile in both sessions. - **`defaultBrowser`** (off): the launcher becomes the default for `http`, - `https` and `text/html` (Home Manager's `xdg.mimeApps`, which then owns - `~/.config/mimeapps.list`). + `https` and `text/html` (the launcher's `defaultFor`: Home Manager's + `xdg.mimeApps`, which then owns `~/.config/mimeapps.list`). `prefs` and the fixes are *default* values, not user values: `about:config` can still change them per profile, and removing one leaves nothing behind. @@ -71,9 +72,11 @@ to a store directory; Flatpak mounts it itself, so the sandbox doesn't get ### Desktop profile -The launcher (a desktop entry with the Flatpak's ID) picks `desktopProfile` -when started in the nested desktop. The desktop profile undoes the -fullscreen fix only while its Firefox runs: the launcher links the +The launcher's wrapper (`modules/firefox/wrapper.nix`) gets the entry's +`flatpak run …` command line and, in the nested desktop, adds `--profile +` (not to the profile manager action, `--ProfileManager`). +The desktop profile undoes the fullscreen fix only while its Firefox runs: +the wrapper links the profile's `user.js` to `/app/etc/firefox/steam-frame-nix-desktop-user.js` (a sandbox path) right before starting Firefox, waits for it, and once it has exited and the profile is no longer in use removes the link and the diff --git a/docs/jellyfin.md b/docs/jellyfin.md index fd65cb2..ea2420a 100644 --- a/docs/jellyfin.md +++ b/docs/jellyfin.md @@ -19,8 +19,9 @@ which ## What you get -The Jellyfin desktop entry (same ID as the Flatpak's, so the KDE menu and -the "+" menu start it) runs the Flatpak with device access (`devices=all`) +A [launcher](launchers.md) (the Flatpak's own entry, rewritten under its +ID, so the KDE menu and the "+" menu start it) runs the Flatpak with device +access (`devices=all`) and makes mpv use `hwdec` (default `v4l2m2m-copy,auto-copy`); explicit values such as `no` stay. mpv tries the listed decoders in order and falls back to software decoding per stream. With the default, 1080p H.264 plays @@ -38,7 +39,7 @@ steamFrame.jellyfin.hardwareDecoding.enable = true; ``` From a terminal, start it with the command line of -`steamFrame.jellyfin.hardwareDecoding.command` (or `grep ^Exec= +`steamFrame.jellyfin.hardwareDecoding.command` (or the one of `grep ^Exec= ~/.local/share/applications/org.jellyfin.JellyfinDesktop.desktop`); its output shows `mpv-hwdec-shim: hwdec "auto-copy" -> "v4l2m2m-copy,auto-copy"`, then mpv's `Using hardware decoding (v4l2m2m-copy)`. @@ -63,12 +64,13 @@ option). It is preloaded from the Nix store; only its store path is exposed (read-only) to the sandbox. It would work for any libmpv app that sets `hwdec=auto*`, but only Jellyfin Desktop is set up here. -### The desktop entry +### The launcher -Nothing is written to Flatpak's overrides: the entry -(`~/.local/share/applications/org.jellyfin.JellyfinDesktop.desktop`) passes -device access and the shim as `flatpak run` options, so they apply to -launches from that entry and are gone with it: +Nothing is written to Flatpak's overrides: the launcher +`steamFrame.launchers."org.jellyfin.JellyfinDesktop"` passes device access +and the shim as `flatpak run` options (`flatpakArgs`, `env`), so they apply +to launches from the entry and are gone with it. Its command lines (the +entry's and the four actions') become ```sh flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop \ @@ -77,7 +79,8 @@ flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop \ --env=SFN_MPV_HWDEC=v4l2m2m-copy,auto-copy org.jellyfin.JellyfinDesktop ``` -(`command` is this line with the store path.) +(`command` is about this line, with the store path and without the +entry's `--branch`/`--arch`/`--command`.) Older versions used a Flatpak override (via nix-flatpak or a Home Manager link) and a shim copy in `~/.var/app/org.jellyfin.JellyfinDesktop`; diff --git a/docs/keyring.md b/docs/keyring.md index 462ab54..d19d620 100644 --- a/docs/keyring.md +++ b/docs/keyring.md @@ -1,128 +1,6 @@ # Keyring launchers -`keyring.flatpaks.`, `keyring.programs.`, module -`keyring`. Options: [README, Options](../README.md#options). - -## Problem - -Apps that keep logins or passwords in the KDE wallet lose them between the -Frame's [two sessions](../README.md#two-sessions): - -- **A second wallet:** the nested desktop has its own D-Bus. An app started - there starts a second `kwalletd6` on that bus; what it stores there is - invisible to the same app in the Steam session, which talks to the running - `kwalletd6` on the outer bus. -- **Electron in the Steam session:** Electron picks its keyring from - `XDG_CURRENT_DESKTOP`. In the Steam session that is `gamescope`, which it - doesn't know, so it falls back to `basic` (a local, unencrypted store) and - the login made in the desktop (stored in the wallet) is gone. -- **Flatpak permissions:** many Flatpaks may not talk to the wallet at all - (Element), or only to `org.kde.kwalletd6` while their KF6 wallet client - reads through the Secret Service `org.freedesktop.secrets` (KRDC: "Password - not found"). -- **Login callbacks:** SSO logins come back through a URL scheme - (`io.element.desktop://`, `claude://`) opened by the portal. Unless the app - is the default and a recommended handler, the portal opens an app chooser, - which isn't shown in VR. - -## What you get - -For each listed app, a desktop entry in `~/.local/share/applications` with -the app's own desktop ID, so it replaces the Flatpak's or package's entry in -the KDE menu and the "+" menu. It starts the app - -- on the outer bus (`session.busEnv`): one `kwalletd6` for both sessions; -- for Flatpaks, with `--talk-name=org.kde.kwalletd6` and - `--talk-name=org.freedesktop.secrets` as `flatpak run` options (not a - Flatpak override: they apply only to launches from this entry and are - gone with it); -- with `electron = true`, with `--password-store=kwallet6`; -- as the default and recommended handler of its `schemeHandlers` - (`xdg.mimeApps`). - -Logins then survive switching between the desktop and VR windows. Changes -take effect at the next start of the app. - -## Configuration - -The Flatpak isn't in the Nix store, so its entry can't be read at build -time: give the fields you want in menus (`name` is required; `icon` -defaults to the app ID, as Flatpaks export it). A Flatpak (installing it is -up to you): - -```nix -steamFrame.keyring.flatpaks = { - "im.riot.Riot" = { - name = "Element"; - electron = true; - categories = [ "Network" "InstantMessaging" ]; - schemeHandlers = [ "element" "io.element.desktop" ]; # SSO callback - }; - "org.kde.krdc" = { - name = "KRDC"; - fieldCode = "%u"; - categories = [ "Qt" "KDE" "Network" "RemoteAccess" ]; - mimeTypes = [ "x-scheme-handler/vnc" "x-scheme-handler/rdp" ]; # listed, not made default - }; -}; -``` - -A program from a Nix package: use the package's own desktop ID (without -`.desktop`), so the entry replaces the package's: - -```nix -{ pkgs, ... }: { - home.packages = [ pkgs.claude-desktop ]; - steamFrame.keyring.programs."com.anthropic.Claude" = { - name = "Claude"; - executable = "${pkgs.claude-desktop}/bin/claude-desktop"; - icon = "claude-desktop"; - electron = true; - startupWMClass = "com.anthropic.Claude"; - schemeHandlers = [ "claude" ]; # login callback - settings.StartupNotify = "true"; - actions.NewChat = { - name = "New Chat"; - args = [ ''"claude://claude.ai/new?surface=chat&source=desktop_action"'' ]; - }; - }; -} -``` - -`args`, `actions..args` and `settings` are written into the entry as -given (desktop entry syntax: quote yourself). Each app's `command` (read -only) is its command line without arguments, to start it from a terminal -the same way. The fields each entry takes are in the -[options](../README.md#options). - -## Caveats - -- The wallet must be KDE's (`kwalletd6`, and `ksecretd` for the Secret - Service), as SteamOS ships it. -- Only launches from the entry get the fixes: a Flatpak started with plain - `flatpak run`, or a program from `~/.nix-profile/bin`, doesn't. -- `schemeHandlers` turns on Home Manager's `xdg.mimeApps`, which then owns - `~/.config/mimeapps.list`: set other defaults there too. -- A Flatpak's own entry may have fields not given here (translations, - `Keywords`, actions); add what you need through `settings` and `actions`. - -## How it works - -For `"im.riot.Riot" = { name = "Element"; electron = true; ... }` the entry's -command line is - -```sh -env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \ - flatpak run --talk-name=org.kde.kwalletd6 --talk-name=org.freedesktop.secrets \ - im.riot.Riot --password-store=kwallet6 %U -``` - -`flatpak run` connects the sandbox's D-Bus proxy to the bus in its own -environment, so the prefix (not `--env=`) moves the app to the outer bus. -`--talk-name` adds to the Flatpak's permissions for this launch only. The -entry also sets `X-Flatpak=`. For `programs` the prefix runs -`executable` directly. - -`schemeHandlers` go into `xdg.mimeApps.defaultApplications` and -`associations.added` (Added Associations, what the portal treats as -recommended), and into the entry's `MimeType=` with `mimeTypes`. +Moved: apps that keep their logins in the KDE wallet are now +[launchers](launchers.md) with `keyring.enable` (the former +`keyring.flatpaks` / `keyring.programs` options and how to convert them are +described there). diff --git a/docs/launchers.md b/docs/launchers.md new file mode 100644 index 0000000..d7812da --- /dev/null +++ b/docs/launchers.md @@ -0,0 +1,204 @@ +# Launchers + +`launchers.`, module `launchers`. Options: +[README, Options](../README.md#options). + +## Problem + +Starting an app the way the Frame needs often means changing its desktop +entry: extra `flatpak run` options, environment, a wrapper script, making it +a default handler. A hand-written entry with the same ID replaces the +original, but loses what the original has (translations, icon, actions, +MIME types) and goes stale when the app changes its entry in an update. + +The most common case is the KDE wallet, where apps lose their logins between +the Frame's [two sessions](../README.md#two-sessions): + +- **A second wallet:** the nested desktop has its own D-Bus. An app started + there starts a second `kwalletd6` on that bus; what it stores there is + invisible to the same app in the Steam session, which talks to the running + `kwalletd6` on the outer bus. +- **Electron in the Steam session:** Electron picks its keyring from + `XDG_CURRENT_DESKTOP`. In the Steam session that is `gamescope`, which it + doesn't know, so it falls back to `basic` (a local, unencrypted store) and + the login made in the desktop (stored in the wallet) is gone. +- **Flatpak permissions:** many Flatpaks may not talk to the wallet at all + (Element), or only to `org.kde.kwalletd6` while their KF6 wallet client + reads through the Secret Service `org.freedesktop.secrets` (KRDC: "Password + not found"). +- **Login callbacks:** SSO logins come back through a URL scheme + (`io.element.desktop://`, `claude://`) opened by the portal. Unless the app + is the default and a recommended handler, the portal opens an app chooser, + which isn't shown in VR. + +## What you get + +For each `launchers.`, the app's own desktop entry, rewritten, +under the same ID in `~/.local/share/applications`: it replaces the original +in the KDE menu and the "+" menu (which reads only that directory). Name, +icon, translations, categories, actions and MIME types stay the app's; only +the command lines (the entry's and every action's) and the keys you set +change. When a Flatpak update changes its entry, the launcher follows within +seconds. + +- **`keyring.enable`:** the app runs on the outer bus (one `kwalletd6` for + both sessions); a Flatpak may also talk to the wallet + (`--talk-name=org.kde.kwalletd6 --talk-name=org.freedesktop.secrets`). + **`keyring.electron`** adds `--password-store=kwallet6`. Logins then + survive switching between the desktop and VR windows. +- **`defaultFor`:** the app becomes the default and a recommended handler of + these MIME types / URL schemes (login callbacks open without a chooser). +- **`flatpakArgs`, `env`, `hostEnv`, `args`, `wrappers`:** options, + environment and wrapper commands for the launch; Flatpak permissions this + way apply only to launches from the entry (no Flatpak override files). +- **`mimeTypes`, `settings`:** further `MimeType=` entries; set, override or + remove `[Desktop Entry]` keys. + +Changes take effect at the next start of the app. [Firefox](firefox.md) +(`firefox.*`) and [Jellyfin](jellyfin.md) (`jellyfin.hardwareDecoding`) are +launchers too. + +## Configuration + +The desktop ID is the attribute name; the source is the Flatpak with that +app ID unless `source.package` or `source.file` is set. Installing the app +is up to you. + +A Flatpak whose logins live in the wallet (Element, an Electron app), with +its SSO callback schemes: + +```nix +steamFrame.launchers."im.riot.Riot" = { + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/element" "x-scheme-handler/io.element.desktop" ]; +}; +steamFrame.launchers."org.kde.krdc".keyring.enable = true; +``` + +A program from a Nix package, from the package's own entry +(`share/applications/.desktop`, rewritten at build time): + +```nix +{ pkgs, ... }: { + home.packages = [ pkgs.claude-desktop ]; + steamFrame.launchers."com.anthropic.Claude" = { + source.package = pkgs.claude-desktop; + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/claude" ]; # login callback + }; +} +``` + +Flatpak permissions and environment for one app (what +[Jellyfin](jellyfin.md) sets): + +```nix +steamFrame.launchers."org.jellyfin.JellyfinDesktop" = { + flatpakArgs = [ "--device=all" ]; + env.SFN_MPV_HWDEC = "v4l2m2m-copy"; # --env= inside the sandbox +}; +``` + +A wrapper, which gets the whole original command line as its arguments (how +[Firefox](firefox.md) picks its desktop profile), and keys of the entry: + +```nix +{ pkgs, ... }: { + steamFrame.launchers."org.chromium.Chromium" = { + wrappers = [ "${pkgs.writeShellScript "no-gpu" ''exec "$@" --disable-gpu''}" ]; + settings = { Name = "Chromium (no GPU)"; Keywords = null; }; # null: removed + }; +} +``` + +`args`, `flatpakArgs`, `wrappers` and the environment values are plain +strings, quoted for the entry for you; `settings` values are written as +given (key-file syntax). Each launcher's `command` (read only) is roughly its +command line, to start it the same way from a terminal. A host file as +source: `source.file = "/usr/share/applications/.desktop"`. + +**From `keyring.*` (until 2026-09):** `keyring.flatpaks.` and +`keyring.programs.` are gone; using them fails with the new form. +`electron = true` is `keyring = { enable = true; electron = true; }`, +`schemeHandlers = [ "x" ]` is `defaultFor = [ "x-scheme-handler/x" ]`, +`programs..executable` becomes `source.package`; name, icon, categories, +actions and field codes come from the app's entry. + +## Caveats + +- Only launches from the entry get the changes: a Flatpak started with plain + `flatpak run`, or a program from `~/.nix-profile/bin`, doesn't. +- A Flatpak's or host file's launcher exists only while its source does: an + app not installed (or uninstalled) has none. The entry is written at + runtime into `/run/user/1000` (tmpfs); until then, e.g. right after boot + before the Steam session's user services started, the link in + `~/.local/share/applications` points nowhere, and menus show neither the + launcher nor the original entry. +- An entry whose command line isn't recognised (a Flatpak's without + `flatpak run ... `, a program's starting with `env -…`) is used + unchanged, with a warning in the journal. +- Edits with KDE's "Edit Application" don't last: use `settings`. An + edited entry is overwritten at the next rewrite; a file that replaced the + link is reported, and Home Manager refuses the next switch until it is + removed. Warnings go to the journal + (`journalctl --user -u steam-frame-nix-launchers`). +- `defaultFor` turns on Home Manager's `xdg.mimeApps`, which then owns + `~/.config/mimeapps.list`: set other defaults there too. Two launchers + claiming the same type fail the build. +- The wallet must be KDE's (`kwalletd6`, and `ksecretd` for the Secret + Service), as SteamOS ships it. + +## How it works + +The Exec lines are rewritten, everything else is copied line by line +(comments, unknown and localized keys as they are): + +| Source | Exec= becomes | +|---|---| +| Flatpak | ` <--env=…> ` | +| package, file | ` ` | + +For a Flatpak the app ID is the first token equal to `X-Flatpak=` (or the +source ID) after `flatpak run`; for others the program is the first token +after an optional leading `env A=B …`. The rest (`--file-forwarding`, +`@@u %U @@`, `%c`, action arguments) stays. `DBusActivatable=true` becomes +`false` (otherwise the desktop would start the app over D-Bus, skipping +Exec), `defaultFor` and `mimeTypes` are added to `MimeType=`, and +`X-SteamFrameNix-Source=` names the source. Element's entry with +`keyring = { enable = true; electron = true; }`: + +```sh +env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \ + /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=/app/bin/element \ + --file-forwarding --talk-name=org.kde.kwalletd6 --talk-name=org.freedesktop.secrets \ + im.riot.Riot --password-store=kwallet6 @@u %U @@ +``` + +`flatpak run` connects the sandbox's D-Bus proxy (and portals) to the bus in +its own environment, so `hostEnv` (not `--env=`) moves the app to the outer +bus. `defaultFor` goes into `xdg.mimeApps.defaultApplications` and +`associations.added` (Added Associations, what the portal treats as +recommended). + +**When it is written:** a package's entry at build time (a Home Manager link +into the store). Flatpak and host file entries can't be read at build time, +so `steam-frame-nix-launchers` (`modules/launchers/generate.sh`, +`rewrite.awk`) writes them to `/run/user/1000/steam-frame-nix/applications` +(tmpfs, `session.runtimeDir`), where the Home Manager links in +`~/.local/share/applications` point. It runs + +- on every switch (Home Manager activation; skipped with a message when the + Steam session isn't running), +- at login (`steam-frame-nix-launchers.service` of the Steam session's user + manager), +- when Flatpak installs, updates or removes apps + (`steam-frame-nix-launchers.path` watches `.changed` of both Flatpak + installations and their exported `applications` directories). + +Sources: `~/.local/share/flatpak/exports/share/applications/.desktop`, +then `/var/lib/flatpak/exports/share/applications/.desktop`. An entry is +replaced (temp file and rename) only when its content changes; entries of +launchers no longer configured or without a source are removed. If anything +changed, the mtime of `~/.local/share/applications` is updated, so a running +Steam rescans the "+" menu. A hash of the last written entry +(`..desktop.sum`) shows edits by others. diff --git a/docs/session.md b/docs/session.md index 69b45f5..236b22e 100644 --- a/docs/session.md +++ b/docs/session.md @@ -23,18 +23,17 @@ running") and skips `reloadSystemd`. and applies `session.services.start` / `stop` / `restart`, which other modules fill (you can add your own units). -**Configuration:** apps that keep secrets in the wallet get launchers from -[keyring](keyring.md). Another launcher that must reach the outer session -(its bus, services or wallet) uses the prefix: +**Configuration:** apps that keep secrets in the wallet get a +[launcher](launchers.md) with `keyring.enable`. Another app that must reach +the outer session (its bus, services or wallet) gets the bus through its +launcher's `hostEnv`; a hand-written entry uses the prefix `session.busEnv`: ```nix { config, ... }: { - xdg.dataFile."applications/org.example.App.desktop".text = '' - [Desktop Entry] - Type=Application - Name=Example - Exec=${config.steamFrame.session.busEnv} flatpak run org.example.App %U - ''; + steamFrame.launchers."org.example.App".hostEnv.DBUS_SESSION_BUS_ADDRESS = + config.steamFrame.session.bus; + # or, in an entry of your own: + # Exec=${config.steamFrame.session.busEnv} flatpak run org.example.App %U } ``` diff --git a/flake.nix b/flake.nix index 8a971b8..c25d0f2 100644 --- a/flake.nix +++ b/flake.nix @@ -34,13 +34,14 @@ }; firefox = ./modules/firefox.nix; jellyfin = ./modules/jellyfin.nix; - keyring = ./modules/keyring.nix; + launchers = ./modules/launchers.nix; docker = ./modules/docker.nix; }; # Former attribute names, kept so existing imports keep working (not in # `default`, which imports each module once under its current name). aliases = { steam-keyboard-patch = modules.vr-keyboard-extra-keys; # its name until 2026-09 + keyring = modules.launchers; # until 2026-09 (its options now fail with the new form) }; systems = [ "aarch64-linux" "x86_64-linux" ]; forSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); @@ -64,11 +65,12 @@ # Tests of the VR keyboard (text model, corrector, swipe decoder on the # default German + English dictionary), the Jellyfin mpv shim, the - # Firefox launcher and install.sh cleanup: nix flake check + # Firefox wrapper, the launchers and install.sh cleanup: nix flake check checks = forSystems (pkgs: { cleanup = import ./modules/cleanup/check.nix { inherit pkgs; }; firefox = import ./modules/firefox/check.nix { inherit pkgs; }; jellyfin = import ./modules/jellyfin/check.nix { inherit pkgs; }; + launchers = import ./modules/launchers/check.nix { inherit pkgs; }; vr-keyboard = import ./modules/vr-keyboard/check.nix { inherit pkgs; }; }); diff --git a/install.sh b/install.sh index 51456e5..de24632 100755 --- a/install.sh +++ b/install.sh @@ -397,7 +397,7 @@ EOF # $SFN_STATE/icon-fallbacks). # firefox user.js in Firefox profiles: links to # /app/etc/firefox/steam-frame-nix-desktop-user.js (the -# launcher's, while the desktop profile runs; left alone while +# wrapper's, while the desktop profile runs; left alone while # the profile is in use), older links to *-firefox-*user.js and # copies starting with FF_MARKER, and the values they left in # prefs.js (only with Firefox closed). @@ -407,6 +407,9 @@ EOF # ui-state the dashboard patches' saved choices # ($SFN_STATE/ui-patches/.json; --all only, never # --orphans) and stray *.json.tmp files. +# launchers entries of steamFrame.launchers in +# $OUTER_RUNTIME_DIR/steam-frame-nix/applications (tmpfs; --all +# only: the switch's own run removes those of removed launchers). # dirs $SFN_STATE and $OUTER_RUNTIME_DIR/steam-frame-nix when empty. # # --orphans keeps what the current configuration still uses (--keep ...); @@ -720,7 +723,7 @@ clean_firefox() { # all t="$(readlink "$u")" case $t in "$FF_DESKTOP_JS") - # The launcher's, while Firefox runs in the desktop profile. + # The wrapper's, while Firefox runs in the desktop profile. if ff_in_use "$prof"; then (( all )) && c_defer "$u: Firefox is using profile $name; close it and run this again" continue @@ -825,6 +828,17 @@ clean_jellyfin() { return 0 } +# --- launcher entries (tmpfs) --- + +clean_launchers() { + local d=$SFN_RUNTIME/applications f + [[ -d $d && ! -L $d ]] || return 0 + while IFS= read -r -d '' f; do + c_rm "$f" "generated launcher entry" + done < <(find "$d" -mindepth 1 -maxdepth 1 -type f -print0) + c_rmdir "$d" +} + # --- dashboard patch state --- clean_ui_state() { # all @@ -877,7 +891,7 @@ cmd_cleanup() { clean_jellyfin clean_ui_state "$([[ $mode == all ]] && echo 1 || echo 0)" c_rmdir "$SFN_STATE" - if [[ $mode == all ]]; then c_rmdir "$SFN_RUNTIME"; fi + if [[ $mode == all ]]; then clean_launchers; c_rmdir "$SFN_RUNTIME"; fi (( CLEAN_ACTIONS || ${#CLEAN_DEFERRED[@]} || CLEAN_QUIET )) || info "nothing to clean up" return 0 } diff --git a/modules/cleanup/check.nix b/modules/cleanup/check.nix index 41a0d15..7696a05 100644 --- a/modules/cleanup/check.nix +++ b/modules/cleanup/check.nix @@ -53,6 +53,10 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } '' touch $S/steamvr-debugger # marker of the first version mkdir -p $root/run/steam-frame-nix $root/run/systemd/user/steamvr.service.d echo old > $root/run/steam-frame-nix/steamvr-debugger-restore + mkdir -p $root/run/steam-frame-nix/applications # steamFrame.launchers + echo '[Desktop Entry]' > $root/run/steam-frame-nix/applications/im.riot.Riot.desktop + echo x > $root/run/steam-frame-nix/applications/.im.riot.Riot.desktop.sum + : > $root/run/steam-frame-nix/applications/.lock echo old > $root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf echo keep > $root/run/systemd/user/steamvr.service.d/other.conf ln -s /nix/store/00000000000000000000000000000000-breeze-icons-6.30.0/share/icons/breeze/apps/64/utilities-terminal.svg $I/utilities-terminal.svg @@ -156,7 +160,7 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } '' echo absent > $S/steamvr-debugger.armed mkdir -p $F/desktop $F/x.default ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/desktop/user.js - touch $F/desktop/.parentlock; exec 8< $F/desktop/.parentlock # the launcher's, Firefox running + touch $F/desktop/.parentlock; exec 8< $F/desktop/.parentlock # the wrapper's, Firefox running ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/x.default/user.js printf '[Context]\ndevices=all;\nfilesystems=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro;\n\n[Environment]\nLD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so\nSFN_MPV_HWDEC=x\n' > $O/org.jellyfin.JellyfinDesktop mkdir -p $HOME/.var/app/org.jellyfin.JellyfinDesktop diff --git a/modules/firefox.nix b/modules/firefox.nix index 39fdff6..d73d780 100644 --- a/modules/firefox.nix +++ b/modules/firefox.nix @@ -11,10 +11,10 @@ # fullscreen inside the window. # - desktopProfile: the sessions have separate buses/displays, so a second # Firefox can't reach the running one and hits the profile lock; the nested -# desktop gets its own profile, where fullscreen works: the launcher undoes -# the fix there while its Firefox runs (firefox/launcher.nix). -# The entry shadows the Flatpak's (same ID), keeping MIME associations, and is -# seen by the "+" menu (which reads only ~/.local/share/applications). +# desktop gets its own profile, where fullscreen works: the wrapper undoes +# the fix there while its Firefox runs (firefox/wrapper.nix). +# The launcher (launchers.nix) is the Flatpak's own entry with the wrapper in +# front, same ID, so MIME associations keep working and the "+" menu sees it. # defaultBrowser: without a default the portal picks the first installed # https handler (e.g. Chromium) in both sessions. { config, pkgs, lib, ... }: @@ -41,13 +41,12 @@ let (k: "user_pref(${builtins.toJSON k}, false);\n") desktopKeys)} $out/${desktopUserJsName} ''); - firefox = pkgs.callPackage ./firefox/launcher.nix { - profileDir = if cfg.desktopProfile == null then null else profileDir; - inherit desktopFix desktopKeys; + wrapper = pkgs.callPackage ./firefox/wrapper.nix { + inherit profileDir desktopFix desktopKeys; desktopJs = "/app/etc/firefox/${desktopUserJsName}"; }; in { - imports = [ ./cleanup.nix ]; + imports = [ ./launchers.nix ]; options.steamFrame.firefox = { enable = lib.mkEnableOption '' @@ -97,49 +96,25 @@ in { type = lib.types.bool; default = false; description = '' - Make the launcher the default for http, https and text/html (in - Home Manager's ~/.config/mimeapps.list). Without a default the - portal opens links with the first installed https handler, in both - sessions. + Make the launcher the default for http, https and text/html (its + `defaultFor`, in Home Manager's ~/.config/mimeapps.list). Without a + default the portal opens links with the first installed https + handler, in both sessions. ''; }; }; config = lib.mkIf cfg.enable { - xdg.mimeApps = lib.mkIf cfg.defaultBrowser { - enable = true; - defaultApplications = lib.genAttrs - [ "x-scheme-handler/http" "x-scheme-handler/https" "text/html" ] - (_: "org.mozilla.firefox.desktop"); + steamFrame.launchers."org.mozilla.firefox" = { + wrappers = lib.optional (cfg.desktopProfile != null) "${wrapper}"; + defaultFor = lib.optionals cfg.defaultBrowser + [ "x-scheme-handler/http" "x-scheme-handler/https" "text/html" ]; }; - # stable: the branch the launcher runs (the extension point has no + # stable: the branch the Flatpak's entry runs (the extension point has no # version, so it takes the app's branch). - xdg.dataFile = lib.optionalAttrs (defaultPrefs != { }) { # desktopFix implies a pref + xdg.dataFile = lib.mkIf (defaultPrefs != { }) { # desktopFix implies a pref "flatpak/extension/org.mozilla.firefox.systemconfig/aarch64/stable".source = sysconfig; - } // { - "applications/org.mozilla.firefox.desktop".text = '' - [Desktop Entry] - Type=Application - Name=Firefox - GenericName=Web Browser - Icon=org.mozilla.firefox - Exec=${firefox} @@u %u @@ - StartupWMClass=firefox - StartupNotify=true - Terminal=false - Categories=Network;WebBrowser; - MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto; - Actions=new-window;new-private-window; - - [Desktop Action new-window] - Name=New Window - Exec=${firefox} --new-window @@u %u @@ - - [Desktop Action new-private-window] - Name=New Private Window - Exec=${firefox} --private-window @@u %u @@ - ''; }; }; } diff --git a/modules/firefox/check.nix b/modules/firefox/check.nix index 808f20e..6690fc2 100644 --- a/modules/firefox/check.nix +++ b/modules/firefox/check.nix @@ -1,8 +1,9 @@ -# Checks of the Firefox launcher's desktop profile user.js (launcher.nix) +# Checks of the Firefox wrapper's desktop profile user.js (wrapper.nix) # against a fake flatpak: the link exists only while Firefox runs, a second # launch that hands over to the running Firefox doesn't remove it, the value -# Firefox stored from it leaves prefs.js once the profile is unlocked, and a -# user.js of the user's own is never touched. +# Firefox stored from it leaves prefs.js once the profile is unlocked, a +# user.js of the user's own is never touched, the profile manager action and +# the Steam session get no --profile. { pkgs }: let # Fake `flatpak run … org.mozilla.firefox --profile DIR …`: with the @@ -22,11 +23,12 @@ let while [ ! -e $FAKE/release ]; do sleep 0.1; done exit 3 ''; - launcher = pkgs.callPackage ./launcher.nix { - flatpakBin = fakeFlatpak; + wrapper = pkgs.callPackage ./wrapper.nix { profileDir = "$HOME/ff/desktop"; desktopFix = true; }; + # What the launcher's Exec= runs: the wrapper, then the Flatpak's command. + launcher = "${wrapper} ${fakeFlatpak} run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox"; in pkgs.runCommand "firefox-check" { nativeBuildInputs = [ pkgs.findutils pkgs.gnugrep ]; } '' set -euo pipefail @@ -36,7 +38,7 @@ pkgs.runCommand "firefox-check" { nativeBuildInputs = [ pkgs.findutils pkgs.gnug p=$HOME/ff/desktop # first launch: link made, Firefox runs - ${launcher} https://example.org & first=$! + ${launcher} @@u https://example.org @@ & first=$! for i in $(seq 100); do grep -q started $FAKE/log && break; sleep 0.1; done grep -qx 'started /app/etc/firefox/steam-frame-nix-desktop-user.js' $FAKE/log || fail "not linked at start" echo 'user_pref("other", 1);' >> $p/prefs.js @@ -59,6 +61,12 @@ pkgs.runCommand "firefox-check" { nativeBuildInputs = [ pkgs.findutils pkgs.gnug grep -q ignore-widgets $p/prefs.js || fail "prefs.js changed with an own user.js" rm $p/user.js + # profile manager action: no --profile, no link + : > $FAKE/log + ${launcher} --ProfileManager || true + grep -qx 'default profile' $FAKE/log || fail "--profile with --ProfileManager" + [ ! -e $p/user.js ] || fail "linked for the profile manager" + # Steam session: default profile, no link : > $FAKE/log XDG_CURRENT_DESKTOP=gamescope ${launcher} || true diff --git a/modules/firefox/launcher.nix b/modules/firefox/wrapper.nix similarity index 54% rename from modules/firefox/launcher.nix rename to modules/firefox/wrapper.nix index 29ce57f..c40d3b1 100644 --- a/modules/firefox/launcher.nix +++ b/modules/firefox/wrapper.nix @@ -1,25 +1,24 @@ -# The Firefox Flatpak launcher (firefox.nix). In the nested desktop (KDE) it -# uses the desktop profile; with the fullscreen fix it links that profile's -# user.js to the sandbox path desktopJs (undoing the fix there) right before -# Firefox starts, and once Firefox has exited and the profile is no longer in -# use (a second launch that only hands its URL to the running Firefox returns -# at once) removes the link and the value Firefox stored from it in -# prefs.js. A user.js of the user's own is never touched. After a crash the -# next launch or steam-frame-nix-cleanup finishes the removal. +# The Firefox Flatpak wrapper (firefox.nix, a steamFrame.launchers wrapper): +# gets the entry's full command line (`flatpak run … org.mozilla.firefox …`) +# as its arguments. Outside the nested desktop it just runs it. In the nested +# desktop (KDE) it adds `--profile ` (not to the profile +# manager action, --ProfileManager); with the fullscreen fix it links that +# profile's user.js to the sandbox path desktopJs (undoing the fix there) +# right before Firefox starts, and once Firefox has exited and the profile is +# no longer in use (a second launch that only hands its URL to the running +# Firefox returns at once) removes the link and the value Firefox stored from +# it in prefs.js. A user.js of the user's own is never touched. After a crash +# the next launch or steam-frame-nix-cleanup finishes the removal. { lib, writeShellScript, coreutils, findutils, gnugrep -, flatpakBin ? "/usr/bin/flatpak" # (not `flatpak`: callPackage would pass pkgs.flatpak) -, profileDir ? null # shell word, e.g. "$HOME/.var/app/…/desktop"; null: no desktop profile +, profileDir # shell word, e.g. "$HOME/.var/app/…/desktop" , desktopFix ? false , desktopJs ? "/app/etc/firefox/steam-frame-nix-desktop-user.js" , desktopKeys ? [ "full-screen-api.ignore-widgets" ] }: -writeShellScript "firefox-launcher" ('' +writeShellScript "firefox-wrapper" ('' PATH=${lib.makeBinPath [ coreutils findutils gnugrep ]}:$PATH - run=(${flatpakBin} run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox) -'' + (if profileDir == null then '' - exec "''${run[@]}" "$@" -'' else '' - if [ "$XDG_CURRENT_DESKTOP" != KDE ]; then exec "''${run[@]}" "$@"; fi + if [ "$XDG_CURRENT_DESKTOP" != KDE ]; then exec "$@"; fi + for a; do [ "$a" = --ProfileManager ] && exec "$@"; done prof="${profileDir}" # Firefox exits (status 1) if the --profile dir doesn't exist yet. mkdir -p "$prof" @@ -45,10 +44,10 @@ writeShellScript "firefox-launcher" ('' else ln -s "$js" "$u" fi - "''${run[@]}" --profile "$prof" "$@" + "$@" --profile "$prof" status=$? sfn_unlink exit $status '' + lib.optionalString (!desktopFix) '' - exec "''${run[@]}" --profile "$prof" "$@" -'')) + exec "$@" --profile "$prof" +'') diff --git a/modules/jellyfin.nix b/modules/jellyfin.nix index 815c55f..c697c8f 100644 --- a/modules/jellyfin.nix +++ b/modules/jellyfin.nix @@ -7,11 +7,11 @@ # - an LD_PRELOAD shim (jellyfin/mpv-hwdec-shim.c) rewrites an "auto*" hwdec # to $SFN_MPV_HWDEC. Preloaded from the store: only its store path is # exposed (read-only) to the sandbox. -# - a desktop entry shadowing the Flatpak's (same ID, also seen by the "+" -# menu) starts it with those permissions as `flatpak run` options -# (--device=all, --filesystem, --env): nothing is written to Flatpak's -# override files, so they apply only to launches from this entry and -# disappear with it. +# - a launcher (launchers.nix: the Flatpak's own entry, same ID, also seen +# by the "+" menu) starts it with those permissions as `flatpak run` +# options (--device=all, --filesystem, --env): nothing is written to +# Flatpak's override files, so they apply only to launches from the entry +# and disappear with it. # Earlier versions used an override file (nix-flatpak or Home Manager); # steam-frame-nix-cleanup removes their entries. { config, lib, pkgs, ... }: @@ -21,16 +21,16 @@ let shim = pkgs.callPackage ./jellyfin/shim.nix { }; - run = lib.concatStringsSep " " [ - "flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop" - "--device=all" - "--filesystem=${shim}:ro" - "--env=LD_PRELOAD=${shim}/lib/mpv-hwdec-shim.so" - "--env=SFN_MPV_HWDEC=${cfg.hwdec}" - app - ]; + launcher = { + flatpakArgs = [ "--device=all" "--filesystem=${shim}:ro" ]; + env = { + LD_PRELOAD = "${shim}/lib/mpv-hwdec-shim.so"; + SFN_MPV_HWDEC = cfg.hwdec; + }; + }; + L = import ./launchers/lib.nix { inherit lib pkgs; }; in { - imports = [ ./cleanup.nix ]; + imports = [ ./launchers.nix ]; options.steamFrame.jellyfin.hardwareDecoding = { enable = lib.mkOption { @@ -38,7 +38,7 @@ in { default = false; description = '' Hardware video decoding (the Frame's V4L2 decoder) in the Jellyfin - Desktop Flatpak: a desktop entry (shadowing the Flatpak's) that + Desktop Flatpak: a launcher (the Flatpak's entry, rewritten) that starts it with device access (devices=all) and an LD_PRELOAD shim that makes mpv try hwdec's value below. Only launches from that entry (menus, the "+" menu) get it; nothing is written to Flatpak's @@ -58,43 +58,21 @@ in { command = lib.mkOption { type = lib.types.str; readOnly = true; - default = run; + # Its launcher's, which exists only when enabled. + default = if cfg.enable then config.steamFrame.launchers.${app}.command + else L.command (launcher // { + id = app; source = { flatpak = null; package = null; file = null; }; + hostEnv = { }; wrappers = [ ]; args = [ ]; + }); defaultText = lib.literalMD "`flatpak run … org.jellyfin.JellyfinDesktop` with the options"; - description = "The command line the desktop entry runs (for a terminal)."; + description = '' + About the command line the launcher runs (for a terminal; the same + as `steamFrame.launchers."org.jellyfin.JellyfinDesktop".command`). + ''; }; }; - # Fields as in the Flatpak's own entry (1.x), which has no MimeType. config = lib.mkIf cfg.enable { - xdg.dataFile."applications/${app}.desktop".text = '' - [Desktop Entry] - Version=1.0 - Name=Jellyfin - Comment=Desktop client for Jellyfin - Exec=${run} - Icon=${app} - Terminal=false - Type=Application - StartupWMClass=${app} - Categories=AudioVideo;Video;Player;TV; - Actions=DesktopF;DesktopW;TVF;TVW - X-Flatpak=${app} - - [Desktop Action DesktopF] - Name=Desktop [Fullscreen] - Exec=${run} --fullscreen --desktop - - [Desktop Action DesktopW] - Name=Desktop [Windowed] - Exec=${run} --windowed --desktop - - [Desktop Action TVF] - Name=TV [Fullscreen] - Exec=${run} --fullscreen --tv - - [Desktop Action TVW] - Name=TV [Windowed] - Exec=${run} --windowed --tv - ''; + steamFrame.launchers.${app} = launcher; }; } diff --git a/modules/keyring.nix b/modules/keyring.nix deleted file mode 100644 index b07db7c..0000000 --- a/modules/keyring.nix +++ /dev/null @@ -1,264 +0,0 @@ -# Launchers for apps that keep secrets in the KDE wallet, so both sessions -# share them. -# - One kwalletd6, on the outer bus: the nested desktop has its own D-Bus, -# where an app would start a second kwalletd6 whose secrets the Steam -# session never sees. The launchers run with session.busEnv. -# - Electron picks its keyring from XDG_CURRENT_DESKTOP; in the Steam session -# (gamescope) it falls back to "basic" (a local file) and the login made in -# the desktop is gone. `electron` passes --password-store=kwallet6. -# - Flatpaks: the wallet's D-Bus names (kwalletd6 and the Secret Service, -# served by ksecretd) as `flatpak run --talk-name` options, not Flatpak -# override files: they apply only to launches from the entry and are gone -# with it. -# - schemeHandlers: login callbacks (claude://, io.element.desktop://) go -# through the portal, which opens an app chooser (invisible in VR) unless -# the app is the default and a recommended handler (Added Associations). -# Each entry is written to ~/.local/share/applications with the app's own -# desktop ID: it shadows the Flatpak's/package's entry and is seen by the "+" -# menu (which reads only that directory). -{ config, lib, ... }: -let - cfg = config.steamFrame.keyring; - busEnv = config.steamFrame.session.busEnv; - - walletNames = [ "org.kde.kwalletd6" "org.freedesktop.secrets" ]; - - actionModule = lib.types.submodule { - options = { - name = lib.mkOption { - type = lib.types.str; - description = "Name of the action (Name=)."; - }; - args = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ ''"claude://claude.ai/new"'' ]; - description = '' - Arguments after the app's (in desktop entry Exec syntax: quote - yourself). - ''; - }; - }; - }; - - # Shared by flatpaks and programs; `flatpak` switches the defaults and the - # command. - appModule = flatpak: { name, config, ... }: { - options = { - name = lib.mkOption { - type = lib.types.str; - example = "Element"; - description = "Name shown in menus (Name=)."; - }; - genericName = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "GenericName=."; - }; - comment = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "Comment=."; - }; - icon = lib.mkOption ({ - type = lib.types.nullOr lib.types.str; - default = if flatpak then name else null; - description = "Icon name (Icon=)."; - } // lib.optionalAttrs flatpak { defaultText = lib.literalMD "the app ID"; }); - categories = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ "Network" "InstantMessaging" ]; - description = "Categories=."; - }; - startupWMClass = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "StartupWMClass=."; - }; - mimeTypes = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ "x-scheme-handler/rdp" ]; - description = '' - Further MIME types listed in the entry (MimeType=), without making - the app their default. - ''; - }; - schemeHandlers = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ "element" "io.element.desktop" ]; - description = '' - URL schemes (without `x-scheme-handler/`) the app handles and is - made the default and a recommended handler for, e.g. login - callbacks: otherwise the portal asks with an app chooser, which - isn't shown in VR. Listed in MimeType= too. - ''; - }; - electron = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Pass `--password-store=kwallet6`: Electron picks its keyring from - XDG_CURRENT_DESKTOP and in the Steam session (gamescope) would use - an unencrypted local store instead of the wallet. - ''; - }; - args = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = '' - Further arguments to the app (desktop entry Exec syntax), before - the field code. - ''; - }; - fieldCode = lib.mkOption { - type = lib.types.enum [ "%U" "%u" "%F" "%f" "" ]; - default = "%U"; - description = '' - How the entry passes URLs/files: `%U` several URLs, `%u` one, - `%F`/`%f` local files, `""` none. - ''; - }; - actions = lib.mkOption { - type = lib.types.attrsOf actionModule; - default = { }; - example = lib.literalExpression - ''{ NewChat = { name = "New Chat"; args = [ '''"claude://claude.ai/new"''' ]; }; }''; - description = '' - Desktop actions (right-click menu entries); each runs the command - with its args. - ''; - }; - settings = lib.mkOption { - type = lib.types.attrsOf lib.types.str; - default = { }; - example = { StartupNotify = "true"; Keywords = "Matrix;chat;"; }; - description = "Further keys of the [Desktop Entry] group."; - }; - command = lib.mkOption { - type = lib.types.str; - readOnly = true; - description = '' - The command line the entry runs, without args and field code (for - a terminal). - ''; - }; - } // lib.optionalAttrs flatpak { - flatpakArgs = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ "--branch=stable" "--command=/app/bin/element" ]; - description = "Further `flatpak run` options."; - }; - } // lib.optionalAttrs (!flatpak) { - executable = lib.mkOption { - type = lib.types.str; - example = lib.literalExpression ''"''${pkgs.claude-desktop}/bin/claude-desktop"''; - description = "The program to run (a path, e.g. into a package)."; - }; - }; - - config.command = lib.concatStringsSep " " ([ busEnv ] - ++ (if flatpak - then [ "flatpak run" ] ++ map (n: "--talk-name=${n}") walletNames - ++ config.flatpakArgs ++ [ name ] - else [ config.executable ]) - ++ lib.optional config.electron "--password-store=kwallet6"); - }; - - entry = id: app: let - exec = args: lib.concatStringsSep " " ([ app.command ] ++ args); - mime = map (s: "x-scheme-handler/${s}") app.schemeHandlers ++ app.mimeTypes; - list = xs: lib.concatMapStrings (x: "${x};") xs; - line = k: v: lib.optionalString (v != null && v != "") "${k}=${v}\n"; - in '' - [Desktop Entry] - Type=Application - '' + line "Name" app.name - + line "GenericName" app.genericName - + line "Comment" app.comment - + line "Icon" app.icon - + line "Exec" (exec (app.args ++ lib.optional (app.fieldCode != "") app.fieldCode)) - + line "StartupWMClass" app.startupWMClass - + line "Categories" (list app.categories) - + line "MimeType" (list mime) - + line "Actions" (list (lib.attrNames app.actions)) - + line "X-Flatpak" (if app ? flatpakArgs then id else null) - + lib.concatStrings (lib.mapAttrsToList line app.settings) - + lib.concatStrings (lib.mapAttrsToList (a: act: '' - - [Desktop Action ${a}] - Name=${act.name} - Exec=${exec act.args} - '') app.actions); - - apps = cfg.flatpaks // cfg.programs; - handlers = lib.concatLists (lib.mapAttrsToList (id: app: - map (s: lib.nameValuePair "x-scheme-handler/${s}" "${id}.desktop") app.schemeHandlers) apps); -in { - imports = [ ./cleanup.nix ]; - - options.steamFrame.keyring = { - flatpaks = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule (appModule true)); - default = { }; - example = lib.literalExpression '' - { - "im.riot.Riot" = { - name = "Element"; - electron = true; - schemeHandlers = [ "element" "io.element.desktop" ]; - }; - "org.kde.krdc" = { name = "KRDC"; fieldCode = "%u"; }; - } - ''; - description = '' - Flatpaks (by app ID) that keep secrets in the KDE wallet: a desktop - entry shadowing the Flatpak's runs it on the outer bus, allowed to - talk to the wallet (`flatpak run --talk-name=...`). - ''; - }; - programs = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule (appModule false)); - default = { }; - example = lib.literalExpression '' - { - "com.anthropic.Claude" = { - name = "Claude"; - executable = "''${pkgs.claude-desktop}/bin/claude-desktop"; - icon = "claude-desktop"; - electron = true; - schemeHandlers = [ "claude" ]; - }; - } - ''; - description = '' - Other programs (e.g. from Nix packages), by desktop ID without - `.desktop` (use the package's own, so the entry replaces it): a - desktop entry that runs them on the outer bus. - ''; - }; - }; - - config = lib.mkIf (apps != { }) { - assertions = map (id: { - assertion = false; - message = "steamFrame.keyring: \"${id}\" is in both flatpaks and programs."; - }) (lib.intersectLists (lib.attrNames cfg.flatpaks) (lib.attrNames cfg.programs)) - ++ lib.mapAttrsToList (mime: ids: { - assertion = lib.length ids == 1; - message = "steamFrame.keyring: ${lib.removePrefix "x-scheme-handler/" mime} is in the schemeHandlers of ${lib.concatStringsSep " and " ids}."; - }) (lib.zipAttrs (map (h: { ${h.name} = lib.removeSuffix ".desktop" h.value; }) handlers)); - - xdg.dataFile = lib.mapAttrs' (id: app: - lib.nameValuePair "applications/${id}.desktop" { text = entry id app; }) apps; - - xdg.mimeApps = lib.mkIf (handlers != [ ]) { - enable = true; - defaultApplications = lib.listToAttrs handlers; - associations.added = lib.listToAttrs handlers; - }; - }; -} diff --git a/modules/launchers.nix b/modules/launchers.nix new file mode 100644 index 0000000..50892cc --- /dev/null +++ b/modules/launchers.nix @@ -0,0 +1,167 @@ +# Launchers: an app's own desktop entry (from its Flatpak, Nix package or a +# host file), rewritten with extra environment, options, wrappers, MIME +# defaults and wallet access, under the same desktop ID in +# ~/.local/share/applications, so it replaces the original in the KDE menu +# and the "+" menu (which reads only that directory). +# - Flatpak/host file entries only exist at runtime: steam-frame-nix-launchers +# (launchers/generate.sh + rewrite.awk) writes them to +# /steam-frame-nix/applications (tmpfs), on switch, at +# login and when Flatpak installs/updates apps (path unit); the Home +# Manager links point there. +# - Package entries are rewritten at build time. +# - keyring: one kwalletd6 for both sessions (the outer bus), the wallet's +# D-Bus names for Flatpaks, --password-store=kwallet6 for Electron. +# 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., made from the + app's own desktop entry (name, icon, actions come from it), e.g. + steamFrame.launchers."im.riot.Riot" = { + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/element" "x-scheme-handler/io.element.desktop" ]; + }; + steamFrame.launchers."com.anthropic.Claude" = { + source.package = pkgs.claude-desktop; + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/claude" ]; + }; + (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 '' + { + "im.riot.Riot" = { + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/element" "x-scheme-handler/io.element.desktop" ]; + }; + "org.kde.krdc".keyring.enable = true; + "com.anthropic.Claude" = { + source.package = pkgs.claude-desktop; + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/claude" ]; + }; + } + ''; + 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 + ''; + } ]; +} diff --git a/modules/launchers/check.nix b/modules/launchers/check.nix new file mode 100644 index 0000000..51d8a93 --- /dev/null +++ b/modules/launchers/check.nix @@ -0,0 +1,191 @@ +# Checks of steamFrame.launchers (lib.nix, rewrite.awk, generate.sh) on +# copies of real entries (fixtures/sources: Element, KRDC, Firefox, gedit +# (DBusActivatable) from /var/lib/flatpak, Jellyfin from the user +# installation, Claude from the claude-desktop package) and two made-up ones +# (quoting, an Exec without anchor): +# - the rewritten entries equal fixtures/expected (X-SteamFrameNix-Source +# compared separately) and pass desktop-file-validate; +# - the generator: unchanged sources change nothing (mtimes kept, no touch of +# the applications dir), a changed source is rewritten and touches it, +# entries of removed launchers and of missing sources are removed, an +# unknown Exec format gives the original entry with a warning, edits and +# replaced links are reported, no outer runtime dir is a no-op; +# - a package without the entry fails to build. +# The rewritten entries end up in $out/actual (to update fixtures/expected). +{ pkgs }: +let + inherit (pkgs) lib; + L = import ./lib.nix { inherit lib pkgs; }; + src = ./fixtures/sources; + shim = "/nix/store/00000000000000000000000000000000-mpv-hwdec-shim"; + + fakeClaude = pkgs.runCommand "claude-desktop-fixture" { } '' + mkdir -p $out/share/applications + cp ${src}/com.anthropic.Claude.desktop $out/share/applications/ + ''; + noEntry = pkgs.runCommand "no-entry-fixture" { } "mkdir -p $out/share/applications"; + + launchers = (lib.evalModules { + modules = [ { + options.launchers = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule + (L.launcherModule { bus = "unix:path=/run/user/1000/bus"; })); + }; + config.launchers = { + "im.riot.Riot" = { + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/element" "x-scheme-handler/io.element.desktop" ]; + }; + "org.kde.krdc".keyring.enable = true; + "org.mozilla.firefox" = { + wrappers = [ "/nix/store/00000000000000000000000000000000-firefox-wrapper" ]; + defaultFor = [ "x-scheme-handler/http" "x-scheme-handler/https" "text/html" ]; + }; + "org.jellyfin.JellyfinDesktop" = { + flatpakArgs = [ "--device=all" "--filesystem=${shim}:ro" ]; + env = { LD_PRELOAD = "${shim}/lib/mpv-hwdec-shim.so"; SFN_MPV_HWDEC = "v4l2m2m-copy,auto-copy"; }; + }; + "org.gnome.gedit" = { + mimeTypes = [ "text/x-nix" "text/plain" ]; + settings = { Name = "gedit (test)"; Keywords = null; StartupWMClass = "gedit"; }; + }; + "org.example.NoAnchor" = { }; + "org.example.Quoting" = { + source.file = "${src}/org.example.Quoting.desktop"; + wrappers = [ "/w/outer" "/w/in ner" ]; + hostEnv.A = "x y"; + env.B = "100%"; + args = [ "a b" "50%" "q\"uote" "$HOME" "back\\slash" "back`tick" "" ]; + settings.Keywords = "x;"; + }; + "com.anthropic.Claude" = { + source.package = fakeClaude; + keyring = { enable = true; electron = true; }; + defaultFor = [ "x-scheme-handler/claude" ]; + }; + }; + } ]; + }).config.launchers; + all = lib.mapAttrs (id: l: l // { inherit id; }) launchers; + runtime = lib.filterAttrs (_: l: L.kindOf l != "package") all; + + generator = L.generator { + launchers = runtime; + runtimeDir = "/nonexistent"; # overridden below + applicationsDir = "/nonexistent"; + exportDirs = [ "/nonexistent" ]; + }; + empty = L.generator { + launchers = { }; + runtimeDir = "/nonexistent"; + applicationsDir = "/nonexistent"; + exportDirs = [ "/nonexistent" ]; + }; + claude = L.packageEntry all."com.anthropic.Claude"; + missing = pkgs.testers.testBuildFailure (L.packageEntry (all."com.anthropic.Claude" // { + source = all."com.anthropic.Claude".source // { package = noEntry; }; + })); +in +pkgs.runCommand "launchers-check" { + nativeBuildInputs = [ generator pkgs.desktop-file-utils pkgs.diffutils ]; + passthru = { inherit generator claude; }; +} '' + set -euo pipefail + fail() { echo "FAIL: $*" >&2; exit 1; } + root=$PWD + export STEAM_FRAME_NIX_RUNTIME_DIR=$root/run + export STEAM_FRAME_NIX_APPLICATIONS_DIR=$root/apps + export STEAM_FRAME_NIX_FLATPAK_EXPORTS=$root/user:$root/system + out_dir=$root/run/steam-frame-nix/applications + mkdir -p $root/user $root/system $root/apps + for id in im.riot.Riot org.kde.krdc org.mozilla.firefox org.gnome.gedit org.example.NoAnchor; do + cp ${src}/$id.desktop $root/system/ + done + cp ${src}/org.jellyfin.JellyfinDesktop.desktop $root/user/ + # a user installation wins over the system one + cp ${src}/org.jellyfin.JellyfinDesktop.desktop $root/system/ + chmod u+w $root/system/* $root/user/* + sed -i 's/^Name=Jellyfin$/Name=system copy/' $root/system/org.jellyfin.JellyfinDesktop.desktop + + run() { steam-frame-nix-launchers 2> $root/log || fail "status $?: $(cat $root/log)"; cat $root/log >&2; } + logged() { grep -qF -- "$1" $root/log || fail "log lacks '$1': $(cat $root/log)"; } + mt() { stat -c %Y "$1"; } + old() { touch -d @1000000000 "$@"; } + + # no outer runtime dir: nothing happens + run; logged "doesn't exist"; [ ! -e $root/run ] || fail "created the runtime dir" + + mkdir -p $root/run + old $root/apps + run + [ "$(mt $root/apps)" != 1000000000 ] || fail "applications dir not touched" + logged "org.example.NoAnchor: unknown Exec format" + cmp -s ${src}/org.example.NoAnchor.desktop $out_dir/org.example.NoAnchor.desktop || fail "unknown Exec format: not the original" + + mkdir -p $out/actual + for id in ${lib.concatStringsSep " " (lib.attrNames runtime)}; do + [ $id = org.example.NoAnchor ] && continue + cp $out_dir/$id.desktop $out/actual/ + done + cp ${claude} $out/actual/com.anthropic.Claude.desktop + grep -qx "X-SteamFrameNix-Source=$root/user/org.jellyfin.JellyfinDesktop.desktop" $out/actual/org.jellyfin.JellyfinDesktop.desktop \ + || fail "Jellyfin not from the user installation" + grep -qx "X-SteamFrameNix-Source=$root/system/org.kde.krdc.desktop" $out/actual/org.kde.krdc.desktop || fail "KRDC source" + grep -qx "X-SteamFrameNix-Source=${src}/org.example.Quoting.desktop" $out/actual/org.example.Quoting.desktop || fail "file source" + grep -qx "X-SteamFrameNix-Source=${fakeClaude}/share/applications/com.anthropic.Claude.desktop" $out/actual/com.anthropic.Claude.desktop || fail "package source" + sed -i 's|^X-SteamFrameNix-Source=.*|X-SteamFrameNix-Source=@SOURCE@|' $out/actual/*.desktop + desktop-file-validate --no-hints $out/actual/*.desktop || fail "desktop-file-validate" + for f in ${./fixtures/expected}/*.desktop; do + diff -u $f $out/actual/''${f##*/} || fail "''${f##*/} differs from fixtures/expected" + done + [ "$(ls ${./fixtures/expected} | wc -l)" = "$(ls $out/actual | wc -l)" ] || fail "expected/actual count" + + # unchanged: nothing written, applications dir untouched + old $root/apps $out_dir/*.desktop + run + [ "$(mt $root/apps)" = 1000000000 ] || fail "unchanged run touched the applications dir" + for f in $out_dir/*.desktop; do [ "$(mt $f)" = 1000000000 ] || fail "unchanged run rewrote $f"; done + + # changed source: rewritten, dir touched + echo '# changed' >> $root/system/org.kde.krdc.desktop + run + logged "org.kde.krdc: entry written" + [ "$(mt $out_dir/org.kde.krdc.desktop)" != 1000000000 ] || fail "changed source not rewritten" + [ "$(mt $out_dir/im.riot.Riot.desktop)" = 1000000000 ] || fail "other entry rewritten" + [ "$(mt $root/apps)" != 1000000000 ] || fail "changed run didn't touch the applications dir" + grep -qx '# changed' $out_dir/org.kde.krdc.desktop || fail "comment not kept" + + # launcher no longer configured, source gone: removed + echo stale > $out_dir/org.example.Old.desktop; echo x > $out_dir/.org.example.Old.desktop.sum + rm $root/system/org.gnome.gedit.desktop + old $root/apps + run + [ ! -e $out_dir/org.example.Old.desktop ] && [ ! -e $out_dir/.org.example.Old.desktop.sum ] || fail "stale entry kept" + [ ! -e $out_dir/org.gnome.gedit.desktop ] || fail "entry without source kept" + logged "org.gnome.gedit: entry removed (no source entry" + [ "$(mt $root/apps)" != 1000000000 ] || fail "removal didn't touch the applications dir" + + # edited entry (e.g. KDE's editor writing through the link): overwritten + cp $out_dir/im.riot.Riot.desktop $root/riot + echo 'Name[de]=Edited' >> $out_dir/im.riot.Riot.desktop + run + logged "im.riot.Riot.desktop was edited" + cmp -s $root/riot $out_dir/im.riot.Riot.desktop || fail "edit not overwritten" + # replaced link: reported; our own link: not + ln -s $out_dir/im.riot.Riot.desktop $root/apps/im.riot.Riot.desktop + echo '[Desktop Entry]' > $root/apps/org.kde.krdc.desktop + run + logged "org.kde.krdc.desktop no longer leads to" + ! grep -q "im.riot.Riot.desktop no longer" $root/log || fail "own link reported" + + # no launchers left (the activation runs it anyway): all entries removed + old $root/apps + ${lib.getExe empty} 2> $root/log || fail "empty: $(cat $root/log)" + [ -z "$(ls $out_dir)" ] || fail "entries left: $(ls $out_dir)" + [ "$(mt $root/apps)" != 1000000000 ] || fail "removal didn't touch the applications dir" + + # a package without the entry: the build fails + [ -e ${missing}/testBuildFailure.log ] && grep -q "has no share/applications/com.anthropic.Claude.desktop" ${missing}/testBuildFailure.log \ + || fail "package without the entry built" + echo ok +'' diff --git a/modules/launchers/fixtures/expected/com.anthropic.Claude.desktop b/modules/launchers/fixtures/expected/com.anthropic.Claude.desktop new file mode 100644 index 0000000..022d8c4 --- /dev/null +++ b/modules/launchers/fixtures/expected/com.anthropic.Claude.desktop @@ -0,0 +1,26 @@ +[Desktop Entry] +Name=Claude +Comment=Desktop application for Claude.ai +GenericName=AI Assistant +Keywords=AI;Chat;Assistant;Claude;Code;LLM; +Exec=env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus /nix/store/8cc1cplpbdlsqy4x0v1c9w66w3dq80w4-claude-desktop-2.7032.0/bin/claude-desktop --password-store=kwallet6 %U +Icon=claude-desktop +Type=Application +StartupNotify=true +# Matches the Wayland app_id / X11 WM_CLASS Chromium derives from +# package.json desktopName, so docks group windows under this entry. +StartupWMClass=com.anthropic.Claude +# second-instance just focuses mainWindow; suppress GNOME's default "New Window" item +SingleMainWindow=true +Categories=Utility;Development; +MimeType=x-scheme-handler/claude; +Actions=NewChat;NewCode; +X-SteamFrameNix-Source=@SOURCE@ + +[Desktop Action NewChat] +Name=New Chat +Exec=env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus /nix/store/8cc1cplpbdlsqy4x0v1c9w66w3dq80w4-claude-desktop-2.7032.0/bin/claude-desktop --password-store=kwallet6 "claude://claude.ai/new?surface=chat&source=desktop_action" + +[Desktop Action NewCode] +Name=New Claude Code Session +Exec=env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus /nix/store/8cc1cplpbdlsqy4x0v1c9w66w3dq80w4-claude-desktop-2.7032.0/bin/claude-desktop --password-store=kwallet6 "claude://code/new?source=desktop_action" diff --git a/modules/launchers/fixtures/expected/im.riot.Riot.desktop b/modules/launchers/fixtures/expected/im.riot.Riot.desktop new file mode 100644 index 0000000..26e5c95 --- /dev/null +++ b/modules/launchers/fixtures/expected/im.riot.Riot.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=Element +Comment=A Matrix-based instant messaging client +Icon=im.riot.Riot +Exec=env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=/app/bin/element --file-forwarding --talk-name=org.kde.kwalletd6 --talk-name=org.freedesktop.secrets im.riot.Riot --password-store=kwallet6 @@u %U @@ +Categories=Network;InstantMessaging;Chat;VideoConference; +MimeType=x-scheme-handler/element;x-scheme-handler/io.element.desktop; +StartupWMClass=element +Keywords=Matrix;matrix.org;chat;irc;communications;talk;riot;vector; +X-Flatpak=im.riot.Riot +X-SteamFrameNix-Source=@SOURCE@ diff --git a/modules/launchers/fixtures/expected/org.example.Quoting.desktop b/modules/launchers/fixtures/expected/org.example.Quoting.desktop new file mode 100644 index 0000000..98fdcce --- /dev/null +++ b/modules/launchers/fixtures/expected/org.example.Quoting.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Type=Application +Name=Quoting test +Name[de]=Zitiertest +# a comment +Exec=env "A=x y" "B=100%%" /w/outer "/w/in ner" env FOO=1 "/opt/My App/bin/app" "a b" "50%%" "q\\"uote" "\\$HOME" "back\\\\slash" "back\\`tick" "" --flag "with \\"quote\\"" %F +Icon=app +Actions=second; +Keywords=x; +X-Unknown-Key=kept\sas is +X-SteamFrameNix-Source=@SOURCE@ + +[Desktop Action second] +Name=Second +Exec=env "A=x y" "B=100%%" /w/outer "/w/in ner" "/opt/My App/bin/app" "a b" "50%%" "q\\"uote" "\\$HOME" "back\\\\slash" "back\\`tick" "" --second + +[X-Other Group] +Exec=untouched diff --git a/modules/launchers/fixtures/expected/org.gnome.gedit.desktop b/modules/launchers/fixtures/expected/org.gnome.gedit.desktop new file mode 100644 index 0000000..8383f11 --- /dev/null +++ b/modules/launchers/fixtures/expected/org.gnome.gedit.desktop @@ -0,0 +1,246 @@ +[Desktop Entry] +Name=gedit (test) +Comment[af]=Redigeer tekslêers +Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ +Comment[an]=Edita fichers de texto +Comment[ar]=حرّر الملفات النصية +Comment[as]=লিখনি ফাইল সম্পাদনা +Comment[ast]=Editar ficheros de testu +Comment[az]=Mətn fayllarını redaktə edin +Comment[be]=Рэдагаванне тэкставых файлаў +Comment[be@latin]=Redahuj tekstavyja fajły +Comment[bg]=Редактиране на текстови файлове +Comment[bn]=টেক্সট ফাইল সম্পাদনা করুন +Comment[bn_IN]=টেক্সট ফাইল সম্পাদনা +Comment[br]=Embann restroù testenn +Comment[bs]=Izmijeni tekstualne datoteke +Comment[ca]=Editeu fitxers de text +Comment[ca@valencia]=Editeu fitxers de text +Comment[ckb]=دەق دەستکاریبکە +Comment[crh]=Metin dosyelerini tahrir et +Comment[cs]=Upravujte textové soubory +Comment[cy]=Golygu ffeiliau testun +Comment[da]=Redigér tekstfiler +Comment[de]=Textdateien bearbeiten +Comment[dz]=ཚིག་ཡིག་ཡིག་སྣོད་ཚུ་ ཞུན་དག་རྐྱབས་ +Comment[el]=Επεξεργασία αρχείων κειμένου +Comment[en_CA]=Edit text files +Comment[en_GB]=Edit text files +Comment[en@shaw]=𐑧𐑛𐑦𐑑 𐑑𐑧𐑒𐑕𐑑 𐑓𐑲𐑤𐑟 +Comment[eo]=Redakti tekstdosierojn +Comment[es]=Edite archivos de texto +Comment[et]=Tekstifailide redigeerimine +Comment[eu]=Editatu testu-fitxategiak +Comment[fa]=ویرایش پرونده‌های متنی +Comment[fi]=Muokkaa tekstitiedostoja +Comment[fr]=Éditer des fichiers texte +Comment[fur]=Modifiche file di test +Comment[ga]=Cuir comhaid téacs in eagar +Comment[gd]=Deasaich faidhlichean teacsa +Comment[gl]=Editar ficheiros de texto +Comment[gu]=લખાણ ફાઇલોમાં ફેરફાર કરો +Comment[he]=עריכת קובצי טקסט +Comment[hi]=पाठ फ़ाइलें संपादित करें +Comment[hr]=Uređivanje tekstovnih datoteka +Comment[hu]=Szövegfájlok szerkesztése +Comment[ia]=Rediger files de texto +Comment[id]=Menyunting berkas teks +Comment[ie]=Redacte files textual +Comment[is]=Sýsla með textaskrár +Comment[it]=Modifica file di testo +Comment[ja]=テキストファイルを編集します +Comment[ka]=ფაილების რედაქტირება +Comment[kab]=Ẓreg ifuyla n uḍris +Comment[kk]=Мәтіндік файлдарды түзету +Comment[km]=កែ​សម្រួល​ឯកសារ​អត្ថបទ +Comment[kn]=ಪಠ್ಯ ಕಡತಗಳನ್ನು ಸಂಪಾದಿಸು +Comment[ko]=텍스트 파일을 편집합니다 +Comment[ku]=Pelên nivîsê sererast bike +Comment[ln]=Bɔngisa kásá ya nkomá +Comment[lt]=Redaguoti tekstinius failus +Comment[lv]=Rediģēt teksta datnes +Comment[mai]=पाठ फाइलकेँ संपादित करू +Comment[mg]=Hanova raki-dahabolana +Comment[mk]=Уредувај текстуални датотеки +Comment[ml]=പദാവലി ഫയലുകളില്‍ മാറ്റം വരുത്തുക +Comment[mn]=Текст файл боловсруулах +Comment[mr]=मजकूर फाइल्स् संपादीत करा +Comment[ms]=Sunting fail teks +Comment[my]=စာသားဖိုင်များပြင်ဆင်ရန် +Comment[nb]=Rediger tekstfiler +Comment[nds]=Textdateien bewarken +Comment[ne]=पाठ फाइल सम्पादन गर्नुहोस् +Comment[nl]=Bewerk tekstbestanden +Comment[nn]=Rediger tekstfiler +Comment[oc]=Editar de fichièrs tèxte +Comment[or]=ପାଠ୍ଯ ଫାଇଲଗୁଡ଼ିକୁ ସମ୍ପାଦନ କରନ୍ତୁ +Comment[pa]=ਲਿਖਤ ਫਾਇਲਾਂ ਸੋਧੋ +Comment[pl]=Edytor plików tekstowych +Comment[ps]=ليکن دوتنې سمول +Comment[pt]=Editar ficheiros de texto +Comment[pt_BR]=Edite arquivos de texto +Comment[ro]=Editare fișiere text +Comment[ru]=Редактор текстовых файлов +Comment[si]=පෙළ ගොනු සකසන්න +Comment[sk]=Úprava textových súborov +Comment[sl]=Enostavni urejevalnik besedilnih datotek +Comment[sq]=Ndrysho file teksti +Comment[sr]=Уређујте текстуалне документе +Comment[sr@latin]=Uređujte tekstualne dokumente +Comment[sv]=Redigera textfiler +Comment[ta]=உரை கோப்புகளை திருத்தவும் +Comment[te]=పాఠ్య ఫైళ్ళను సవరించు +Comment[th]=แก้ไขไฟล์ข้อความ +Comment[tk]=Metin faýllary editle +Comment[tr]=Metin dosyalarını düzenle +Comment[ug]=تېكىست ھۆججەتلىرىنى تەھرىرلەش +Comment[uk]=Редактор текстових файлів +Comment[vi]=Soạn thảo tập tin văn bản +Comment[wa]=Asspougnî des fitchîs tecses +Comment[xh]=Hlela iifayili zombhalo +Comment[zh_CN]=编辑文本文件 +Comment[zh_HK]=編輯文字檔 +Comment[zh_TW]=編輯文字檔 +Comment=Edit text files +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=gedit --file-forwarding org.gnome.gedit @@u %U @@ +Terminal=false +Type=Application +StartupNotify=true +MimeType=text/plain;application/x-zerosize;text/x-nix; +Icon=org.gnome.gedit +Categories=GNOME;GTK;Utility;TextEditor; +Actions=new-window;new-document; +DBusActivatable=false +X-Flatpak=org.gnome.gedit +StartupWMClass=gedit +X-SteamFrameNix-Source=@SOURCE@ + +[Desktop Action new-window] +Name[af]=Nuwe venster +Name[ar]=نافذة جديدة +Name[be]=Новае акно +Name[bg]=Нов прозорец +Name[bn_IN]=নতুন উইন্ডো +Name[ca]=Finestra nova +Name[ca@valencia]=_Finestra nova +Name[ckb]=پەنجەریەکی نوێ +Name[cs]=Nové okno +Name[da]=Nyt vindue +Name[de]=Neues Fenster +Name[el]=Νέο παράθυρο +Name[en_GB]=New Window +Name[eo]=Nova fenestro +Name[es]=Ventana nueva +Name[eu]=Leiho berria +Name[fa]=پنجره جدید +Name[fi]=Uusi ikkuna +Name[fr]=Nouvelle fenêtre +Name[fur]=Gnûf barcon +Name[gd]=Uinneag ùr +Name[gl]=_Xanela nova +Name[he]=חלון חדש +Name[hr]=Novi prozor +Name[hu]=Új ablak +Name[ia]=Nove fenestra +Name[id]=Jendela Baru +Name[ie]=Nov fenestre +Name[is]=Nýr gluggi +Name[it]=Nuova finestra +Name[ja]=新しいウィンドウ +Name[ka]=ახალი ფანჯარა +Name[kab]=Asfaylu amaynut +Name[kk]=Жаңа терезе +Name[ko]=새 창 +Name[lt]=Naujas langas +Name[lv]=Jauns logs +Name[mjw]=Kimi Window +Name[ml]=പുതിയ ജാലകം +Name[ms]=Tetingkap Baharu +Name[nb]=Nytt vindu +Name[ne]=नयाँ सञ्झ्याल +Name[nl]=Nieuw venster +Name[oc]=Novèla fenèstra +Name[pa]=ਨਵੀਂ ਵਿੰਡੋ +Name[pl]=Nowe okno +Name[pt]=Nova janela +Name[pt_BR]=Nova janela +Name[ro]=Fereastră nouă +Name[ru]=Создать окно +Name[sk]=Nové okno +Name[sl]=Novo okno +Name[sr]=Нови прозор +Name[sr@latin]=Novi prozor +Name[sv]=Nytt fönster +Name[th]=หน้าต่างใหม่ +Name[tr]=Yeni Pencere +Name[uk]=Створити вікно +Name[vi]=Cửa sổ mới +Name[zh_CN]=新建窗口 +Name[zh_TW]=新增視窗 +Name=New Window +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=gedit org.gnome.gedit --new-window + +[Desktop Action new-document] +Name[af]=Nuwe dokument +Name[ar]=مستند جديد +Name[be]=Новы дакумент +Name[bg]=Нов документ +Name[bn_IN]=নতুন ডকুমেন্ট +Name[ca]=Crea un document nou +Name[ca@valencia]=Crea un document nou +Name[ckb]=بەڵگەنامەی نوێ +Name[cs]=Nový dokument +Name[da]=Nyt dokument +Name[de]=Neues Dokument +Name[el]=Νέο έγγραφο +Name[en_GB]=New Document +Name[eo]=Nova dokumento +Name[es]=Documento nuevo +Name[eu]=Dokumentu berria +Name[fa]=سند جدید +Name[fi]=Uusi asiakirja +Name[fr]=Nouveau document +Name[fur]=Gnûf document +Name[gd]=Sgrìobhainn ùr +Name[gl]=Novo documento +Name[he]=מסמך חדש +Name[hr]=Novi dokument +Name[hu]=Új dokumentum +Name[ia]=Nove documento +Name[id]=Dokumen Baru +Name[ie]=Nov document +Name[is]=Nýtt skjal +Name[it]=Nuovo documento +Name[ja]=新しいドキュメント +Name[ka]=ახალი დოკუმენტი +Name[kab]=Isemli amaynut +Name[kk]=Жаңа құжат +Name[ko]=새 문서 +Name[lt]=Naujas dokumentas +Name[lv]=Jauns dokuments +Name[mjw]=Kimi Document +Name[ml]=പൂതിയ രേഖ +Name[ms]=Dokumen Baharu +Name[nb]=Nytt dokument +Name[ne]=नयाँ कागजात +Name[nl]=Nieuw document +Name[oc]=Novèl document +Name[pa]=ਨਵਾਂ ਡੌਕੂਮੈਂਟ +Name[pl]=Nowy dokument +Name[pt]=Novo documento +Name[pt_BR]=Novo documento +Name[ro]=Document nou +Name[ru]=Создать документ +Name[sk]=Nový dokument +Name[sl]=Nov dokument +Name[sr]=Нови документ +Name[sr@latin]=Novi dokument +Name[sv]=Nytt dokument +Name[th]=เอกสารใหม่ +Name[tr]=Yeni Belge +Name[uk]=Створити документ +Name[vi]=Tài liệu mới +Name[zh_CN]=新建文档 +Name[zh_TW]=新增文件 +Name=New Document +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=gedit org.gnome.gedit --new-document diff --git a/modules/launchers/fixtures/expected/org.jellyfin.JellyfinDesktop.desktop b/modules/launchers/fixtures/expected/org.jellyfin.JellyfinDesktop.desktop new file mode 100644 index 0000000..d8dc8cf --- /dev/null +++ b/modules/launchers/fixtures/expected/org.jellyfin.JellyfinDesktop.desktop @@ -0,0 +1,29 @@ +[Desktop Entry] +Version=1.0 +Name=Jellyfin +Comment=Desktop client for Jellyfin +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop --device=all --filesystem=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro --env=LD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so --env=SFN_MPV_HWDEC=v4l2m2m-copy,auto-copy org.jellyfin.JellyfinDesktop +Icon=org.jellyfin.JellyfinDesktop +Terminal=false +Type=Application +StartupWMClass=org.jellyfin.JellyfinDesktop +Categories=AudioVideo;Video;Player;TV; +Actions=DesktopF;DesktopW;TVF;TVW +X-Flatpak=org.jellyfin.JellyfinDesktop +X-SteamFrameNix-Source=@SOURCE@ + +[Desktop Action DesktopF] +Name=Desktop [Fullscreen] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop --device=all --filesystem=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro --env=LD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so --env=SFN_MPV_HWDEC=v4l2m2m-copy,auto-copy org.jellyfin.JellyfinDesktop --fullscreen --desktop + +[Desktop Action DesktopW] +Name=Desktop [Windowed] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop --device=all --filesystem=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro --env=LD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so --env=SFN_MPV_HWDEC=v4l2m2m-copy,auto-copy org.jellyfin.JellyfinDesktop --windowed --desktop + +[Desktop Action TVF] +Name=TV [Fullscreen] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop --device=all --filesystem=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro --env=LD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so --env=SFN_MPV_HWDEC=v4l2m2m-copy,auto-copy org.jellyfin.JellyfinDesktop --fullscreen --tv + +[Desktop Action TVW] +Name=TV [Windowed] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop --device=all --filesystem=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro --env=LD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so --env=SFN_MPV_HWDEC=v4l2m2m-copy,auto-copy org.jellyfin.JellyfinDesktop --windowed --tv diff --git a/modules/launchers/fixtures/expected/org.kde.krdc.desktop b/modules/launchers/fixtures/expected/org.kde.krdc.desktop new file mode 100644 index 0000000..b5ac1cc --- /dev/null +++ b/modules/launchers/fixtures/expected/org.kde.krdc.desktop @@ -0,0 +1,182 @@ +[Desktop Entry] +Type=Application +Exec=env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=/app/bin/krdc --file-forwarding --talk-name=org.kde.kwalletd6 --talk-name=org.freedesktop.secrets org.kde.krdc -qwindowtitle %c @@u %u @@ +Icon=org.kde.krdc +Terminal=false +Name=KRDC +Name[ar]=كردس +Name[az]=KRDC +Name[bg]=KRDC +Name[bs]=KRDC +Name[ca]=KRDC +Name[ca@valencia]=KRDC +Name[cs]=KRDC +Name[da]=KRDC +Name[de]=KRDC +Name[el]=KRDC +Name[en_GB]=KRDC +Name[eo]=KRDC +Name[es]=KRDC +Name[et]=KRDC +Name[eu]=KRDC +Name[fi]=KRDC +Name[fr]=KRDC +Name[ga]=KRDC +Name[gl]=KRDC +Name[he]=KRDC +Name[hne]=केआरडीसी +Name[hr]=KRDC +Name[hu]=KRDC +Name[ia]=KRDC +Name[is]=KRDC +Name[it]=KRDC +Name[ja]=KRDC +Name[ka]=KRDC +Name[kk]=KRDC +Name[km]=KRDC +Name[ko]=KRDC +Name[lt]=KRDC +Name[lv]=KRDC +Name[ml]=കെആര്‍ഡിസി +Name[mr]=के-आर-डी-सी +Name[nb]=KRDC +Name[nds]=KRDC +Name[nl]=KRDC +Name[nn]=KRDC +Name[pa]=KRDC +Name[pl]=KRDC +Name[pt]=KRDC +Name[pt_BR]=KRDC +Name[ro]=KRDC +Name[ru]=KRDC +Name[sa]=केआरडीसी +Name[si]=KRDC +Name[sk]=KRDC +Name[sl]=KRDC +Name[sq]=KRDC +Name[sr]=КРДЦ +Name[sr@ijekavian]=КРДЦ +Name[sr@ijekavianlatin]=KRDC +Name[sr@latin]=KRDC +Name[sv]=KRDC +Name[tr]=KRDC +Name[ug]=KRDC +Name[uk]=KRDC +Name[wa]=KRDC +Name[zh_CN]=KRDC +Name[zh_TW]=遠端桌面_KRDC +GenericName=Remote Desktop Client +GenericName[ar]=عميل سطح المكتب البعيد +GenericName[az]=İş masasının uzaqdan idarə olunması +GenericName[bg]=Отдалечен работен плот +GenericName[bs]=Klijent udaljene radne površine +GenericName[ca]=Client d'escriptori remot +GenericName[ca@valencia]=Client d'escriptori remot +GenericName[cs]=Klient vzdálené pracovní plochy +GenericName[da]=Klient til fjernskrivebord +GenericName[de]=Verbindung zu entferntem Rechner +GenericName[el]=Πελάτης απομακρυσμένης επιφάνειας εργασίας +GenericName[en_GB]=Remote Desktop Client +GenericName[eo]=Kliento por fora labortablo +GenericName[es]=Cliente de escritorio remoto +GenericName[et]=Kaugtöölaua klient +GenericName[eu]=Urruneko mahaigainaren bezeroa +GenericName[fi]=Etätyöpöytäasiakas +GenericName[fr]=Connexion à un bureau distant +GenericName[ga]=Cliant Deisce Cianda +GenericName[gl]=Cliente de escritorio remoto +GenericName[he]=לקוח שולחן עבודה מרוחק +GenericName[hne]=रिमोट डेस्कटाप क्लाएंट +GenericName[hr]=Klijent za udaljeno povezivanje +GenericName[hu]=Távoli asztal kliens +GenericName[ia]=Cliente de scriptorio remote +GenericName[is]=Tengingar við fjarlæg skjáborð +GenericName[it]=Client per connessione a desktop remoto +GenericName[ja]=リモートデスクトップクライアント +GenericName[ka]=დისტანციური სამუშაო მაგიდის კლიენტი +GenericName[kk]=Қашықтан үстелге қосылу клиенті +GenericName[km]=កម្មវិធី​មើល​ផ្ទៃតុ​ពី​ចម្ងាយ +GenericName[ko]=원격 데스크톱 클라이언트 +GenericName[lt]=Nuotolinio darbalaukio kliento programa +GenericName[lv]=Attālās darbvirsmas klients +GenericName[ml]=വിദൂര പണിയിട ക്ലയന്റ് +GenericName[mr]=दूरस्थ डेस्कटॉप ग्राहक +GenericName[nb]=Tilkobling til annet skrivebord +GenericName[nds]=Schriefdisch-Feernverbinnen +GenericName[nl]=Client voor verbinding met extern bureaublad +GenericName[nn]=Tilkopling til eksternt skrivebord +GenericName[pa]=ਰਿਮੋਟ ਡੈਸਕਟਾਪ ਕਲਾਈਟ +GenericName[pl]=Klient zdalnego dostępu do pulpitu +GenericName[pt]=Cliente de Ecrãs Remotos +GenericName[pt_BR]=Cliente de área de trabalho remota +GenericName[ro]=Client pentru birou la distanță +GenericName[ru]=Удалённый доступ к рабочему столу +GenericName[sa]=दूरस्थ डेस्कटॉप क्लायन्ट +GenericName[si]=දුරස්ථ වැඩතල යෙදුම +GenericName[sk]=Klient vzdialenej pracovnej plochy +GenericName[sl]=Odjemalec za oddaljena namizja +GenericName[sr]=Клијент удаљене површи +GenericName[sr@ijekavian]=Клијент удаљене површи +GenericName[sr@ijekavianlatin]=Klijent udaljene površi +GenericName[sr@latin]=Klijent udaljene površi +GenericName[sv]=Fjärranslutning till skrivbord +GenericName[th]=ไคลเอนต์เชื่อมต่อพื้นที่ทำงานทางไกล +GenericName[tr]=Uzak Masaüstü İstemcisi +GenericName[ug]=يىراقتىكى ئۈستەلئۈستى خېرىدارى +GenericName[uk]=Клієнт з'єднання з віддаленою стільницею +GenericName[zh_CN]=远程桌面客户端 +GenericName[zh_TW]=遠端桌面用戶端 +Comment=Connect with RDP or VNC to another computer +Comment[ar]=اتصل حاسوب آخر بواسطة RDP أو VNC +Comment[az]=RDP və ya VNC ilə başqa kompüterə qoşulun +Comment[bg]=Свързване чрез RDP or VNC с друг компютър +Comment[ca]=Connecta amb RDP o VNC a un altre ordinador +Comment[ca@valencia]=Connecta amb RDP o VNC a un altre ordinador +Comment[cs]=Připojte se k jinému počítači pomocí RDP nebo VNC +Comment[da]=Opret forbindelse til en anden computer med RDP eller VNC +Comment[de]=Verbindung mit RDP oder VNC zu einem anderen Rechner +Comment[el]=Σύνδεση με RDP ή VNC σε άλλον υπολογιστή +Comment[en_GB]=Connect with RDP or VNC to another computer +Comment[eo]=Konektiĝi per RDP aŭ VNC al alia komputilo +Comment[es]=Conectar con RDP o VNC a otro equipo +Comment[et]=Ühendumine RDP või VNC vahendusel teise arvutiga +Comment[eu]=Konektatu RDP edo VNC bidez beste ordenagailu batera +Comment[fi]=Yhdistä RDP:llä tai VNC:llä toiseen tietokoneeseen +Comment[fr]=Se connecter à un autre ordinateur avec RDP ou VNC +Comment[gl]=Conectarse por RDP ou VNC a outro computador. +Comment[he]=התחברות ל־RDP או ל־VNC במחשב אחר +Comment[hu]=Csatlakozás másik számítógéphez RDP-vel vagy VNC-vel +Comment[ia]=Connecte con RDP o VNC a un altere computator +Comment[is]=Tengstu með RDP eða VNC við aðrar tölvur +Comment[it]=Connessione via RDP o VNC ad un altro computer +Comment[ka]=მიუერთდით მოშორებულ კომპიუტერზე RDP ან VNC პროტოკოლით +Comment[ko]=다른 컴퓨터에 RDP나 VNC로 연결 +Comment[lt]=Prisijungti prie kito kompiuterio naudojant RDP ar VNC +Comment[lv]=Izmantojiet RDP vai VNC, lai pievienotos citam datoram +Comment[nl]=Met RDP of VNC naar een andere computer verbinden +Comment[nn]=Kopla til ei anna datamaskin via RDP eller VNC +Comment[pl]=Połącz się przy użyciu RDP lub VNC do innego komputera +Comment[pt]=Ligar-se com RDP ou VNC a outro computador +Comment[pt_BR]=Conecte com RDP ou VNC em outro computador +Comment[ro]=Conectare la alt calculator prin RDP sau VNC +Comment[ru]=Подключение к другому компьютеру по RDP или VNC +Comment[sa]=RDP अथवा VNC इत्यनेन सह अन्यसङ्गणकेन सह सम्बद्धं कुर्वन्तु +Comment[sk]=Pripojiť pomocou RDP alebo VNC k inému počítaču +Comment[sl]=Povežite se preko RDP-ja ali VNC-ja z drugim računalnikom +Comment[sq]=Lidhu me një kompjuter tjetër përmes RDP ose VNC +Comment[sr]=Повежите се РДП‑ом или ВНЦ‑ом са другим рачунаром +Comment[sr@ijekavian]=Повежите се РДП‑ом или ВНЦ‑ом са другим рачунаром +Comment[sr@ijekavianlatin]=Povežite se RDP‑om ili VNC‑om sa drugim računarom +Comment[sr@latin]=Povežite se RDP‑om ili VNC‑om sa drugim računarom +Comment[sv]=Anslut till en annan dator med RDP eller VNC +Comment[tr]=Başka bir bilgisayara RDP veya VNC ile bağlan +Comment[uk]=З'єднання з іншим комп'ютером за допомогою RDP або VNC +Comment[zh_CN]=通过 RDP 或 VNC 连接到另一台计算机 +Comment[zh_TW]=以 RDP 或 VNC 連線到其他電腦 +X-DocPath=krdc/index.html +StartupWMClass=krdc +MimeType=x-scheme-handler/vnc;x-scheme-handler/rdp;application/x-krdc; +Categories=Qt;KDE;Network;RemoteAccess; +X-KDE-Wayland-Interfaces=org_kde_kwin_keystate +X-Flatpak=org.kde.krdc +X-SteamFrameNix-Source=@SOURCE@ diff --git a/modules/launchers/fixtures/expected/org.mozilla.firefox.desktop b/modules/launchers/fixtures/expected/org.mozilla.firefox.desktop new file mode 100644 index 0000000..974c938 --- /dev/null +++ b/modules/launchers/fixtures/expected/org.mozilla.firefox.desktop @@ -0,0 +1,848 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Exec=/nix/store/00000000000000000000000000000000-firefox-wrapper /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox @@u %u @@ +Terminal=false +X-MultipleArgs=false +Icon=org.mozilla.firefox +StartupWMClass=firefox +DBusActivatable=false +Categories=GNOME;GTK;Network;WebBrowser; +MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto; +StartupNotify=true +Actions=new-window;new-private-window;open-profile-manager; +Name=Firefox +Name[ach]=Firefox +Name[af]=Firefox +Name[an]=Firefox +Name[ar]=Firefox +Name[ast]=Firefox +Name[az]=Firefox +Name[be]=Firefox +Name[bg]=Firefox +Name[bn]=Firefox +Name[br]=Firefox +Name[bs]=Firefox +Name[ca]=Firefox +Name[ca_valencia]=Firefox +Name[cak]=Firefox +Name[cs]=Firefox +Name[cy]=Firefox +Name[da]=Firefox +Name[de]=Firefox +Name[dsb]=Firefox +Name[el]=Firefox +Name[en_CA]=Firefox +Name[en_GB]=Firefox +Name[eo]=Firefox +Name[es_AR]=Firefox +Name[es_CL]=Firefox +Name[es_ES]=Firefox +Name[es_MX]=Firefox +Name[et]=Firefox +Name[eu]=Firefox +Name[fa]=Firefox +Name[ff]=Firefox +Name[fi]=Firefox +Name[fr]=Firefox +Name[fur]=Firefox +Name[fy_NL]=Firefox +Name[ga_IE]=Firefox +Name[gd]=Firefox +Name[gl]=Firefox +Name[gn]=Firefox +Name[gu_IN]=Firefox +Name[he]=Firefox +Name[hi_IN]=Firefox +Name[hr]=Firefox +Name[hsb]=Firefox +Name[hu]=Firefox +Name[hy_AM]=Firefox +Name[ia]=Firefox +Name[id]=Firefox +Name[is]=Firefox +Name[it]=Firefox +Name[ja]=Firefox +Name[ka]=Firefox +Name[kab]=Firefox +Name[kk]=Firefox +Name[km]=Firefox +Name[kn]=Firefox +Name[ko]=Firefox +Name[lij]=Firefox +Name[lt]=Firefox +Name[lv]=Firefox +Name[mk]=Firefox +Name[mr]=Firefox +Name[ms]=Firefox +Name[my]=Firefox +Name[nb_NO]=Firefox +Name[ne_NP]=Firefox +Name[nl]=Firefox +Name[nn_NO]=Firefox +Name[oc]=Firefox +Name[pa_IN]=Firefox +Name[pl]=Firefox +Name[pt_BR]=Firefox +Name[pt_PT]=Firefox +Name[rm]=Firefox +Name[ro]=Firefox +Name[ru]=Firefox +Name[sat]=Firefox +Name[sc]=Firefox +Name[sco]=Firefox +Name[si]=Firefox +Name[sk]=Firefox +Name[skr]=Firefox +Name[sl]=Firefox +Name[son]=Firefox +Name[sq]=Firefox +Name[sr]=Firefox +Name[sv_SE]=Firefox +Name[szl]=Firefox +Name[ta]=Firefox +Name[te]=Firefox +Name[tg]=Firefox +Name[th]=Firefox +Name[tl]=Firefox +Name[tr]=Firefox +Name[trs]=Firefox +Name[uk]=Firefox +Name[ur]=Firefox +Name[uz]=Firefox +Name[vi]=Firefox +Name[xh]=Firefox +Name[zh_CN]=Firefox +Name[zh_TW]=Firefox +Comment=Fast and private browser +Comment[ach]=Fast and private browser +Comment[af]=Fast and private browser +Comment[an]=Fast and private browser +Comment[ar]=متصفح سريع وخاص +Comment[ast]=Fast and private browser +Comment[az]=Fast and private browser +Comment[be]=Хуткі і прыватны браўзер +Comment[bg]=Fast and private browser +Comment[bn]=Fast and private browser +Comment[br]=Fast and private browser +Comment[bs]=Fast and private browser +Comment[ca]=Navegador ràpid i privat +Comment[ca_valencia]=Fast and private browser +Comment[cak]=Fast and private browser +Comment[cs]=Rychlý a soukromý prohlížeč +Comment[cy]=Porwr cyflym a phreifat +Comment[da]=Hurtig og privat browser +Comment[de]=Schneller und privater Browser +Comment[dsb]=Malsny a priwatny wobglědowak +Comment[el]=Γρήγορο και ιδιωτικό πρόγραμμα περιήγησης +Comment[en_CA]=Fast and private browser +Comment[en_GB]=Fast and private browser +Comment[eo]=Rapida kaj privata retumilo +Comment[es_AR]=Navegador rápido y privado +Comment[es_CL]=Navegador rápido y privado +Comment[es_ES]=Navegador rápido y privado +Comment[es_MX]=Navegación web rápida y privada +Comment[et]=Fast and private browser +Comment[eu]=Nabigatzaile bizkor eta pribatua +Comment[fa]=Fast and private browser +Comment[ff]=Fast and private browser +Comment[fi]=Nopea ja yksityinen selain +Comment[fr]=Navigateur rapide et privé +Comment[fur]=Navigadôr svelt e riservât +Comment[fy_NL]=Flugge en priveebrowser +Comment[ga_IE]=Fast and private browser +Comment[gd]=Fast and private browser +Comment[gl]=Fast and private browser +Comment[gn]=Kundahára ipya’e ha hekoñemíva +Comment[gu_IN]=Fast and private browser +Comment[he]=דפדפן מהיר ופרטי +Comment[hi_IN]=Fast and private browser +Comment[hr]=Brz i privatan preglednik +Comment[hsb]=Spěšny a priwatny wobhladowak +Comment[hu]=Gyors és privát böngésző +Comment[hy_AM]=Արագ և գաղտնի դիտարկիչ +Comment[ia]=Navigator veloce e private +Comment[id]=Fast and private browser +Comment[is]=Hraður og persónulegur vafri +Comment[it]=Browser veloce e riservato +Comment[ja]=高速プライベートブラウザー +Comment[ka]=სწრაფი და პირადი ბრაუზერი +Comment[kab]=Iminig arurad, uslig +Comment[kk]=Жылдам және жеке браузер +Comment[km]=Fast and private browser +Comment[kn]=Fast and private browser +Comment[ko]=빠르고 사생활을 보호하는 브라우저 +Comment[lij]=Fast and private browser +Comment[lt]=Fast and private browser +Comment[lv]=Fast and private browser +Comment[mk]=Fast and private browser +Comment[mr]=Fast and private browser +Comment[ms]=Fast and private browser +Comment[my]=Fast and private browser +Comment[nb_NO]=Rask og privat nettleser +Comment[ne_NP]=Fast and private browser +Comment[nl]=Snelle en privébrowser +Comment[nn_NO]=Rask og privat nettlesar +Comment[oc]=Fast and private browser +Comment[pa_IN]=ਤੇਜ਼ ਅਤੇ ਪ੍ਰਾਈਵੇਟ ਬਰਾਊਜ਼ਰ +Comment[pl]=Szybka i prywatna przeglądarka +Comment[pt_BR]=Navegador rápido e privativo +Comment[pt_PT]=Navegador rápido e privado +Comment[rm]=Navigatur svelt e privat +Comment[ro]=Browser rapid și privat +Comment[ru]=Быстрый и приватный браузер +Comment[sat]=Fast and private browser +Comment[sc]=Navigadore lestru e privadu +Comment[sco]=Fast and private browser +Comment[si]=Fast and private browser +Comment[sk]=Rýchly a súkromný prehliadač +Comment[skr]=Fast and private browser +Comment[sl]=Hiter in zaseben brskalnik +Comment[son]=Fast and private browser +Comment[sq]=Shfletues i shpejtë dhe privat +Comment[sr]=Брз и приватан веб прегледач +Comment[sv_SE]=Snabb och privat webbläsare +Comment[szl]=Fast and private browser +Comment[ta]=Fast and private browser +Comment[te]=Fast and private browser +Comment[tg]=Браузери тезкор ва хусусӣ +Comment[th]=เบราว์เซอร์ที่รวดเร็วและเป็นส่วนตัว +Comment[tl]=Fast and private browser +Comment[tr]=Hızlı ve gizli tarayıcı +Comment[trs]=Fast and private browser +Comment[uk]=Швидкий і приватний браузер +Comment[ur]=Fast and private browser +Comment[uz]=Fast and private browser +Comment[vi]=Trình duyệt nhanh và riêng tư +Comment[xh]=Fast and private browser +Comment[zh_CN]=快速且私密的浏览器 +Comment[zh_TW]=快速又有隱私的瀏覽器 +GenericName=Web Browser +GenericName[ach]=Web Browser +GenericName[af]=Web Browser +GenericName[an]=Web Browser +GenericName[ar]=متصفح الوِب +GenericName[ast]=Web Browser +GenericName[az]=Web Browser +GenericName[be]=Вэб-браўзер +GenericName[bg]=Уеб браузър +GenericName[bn]=ওয়েব ব্রাউজার +GenericName[br]=Merdeer Web +GenericName[bs]=Web pretraživač +GenericName[ca]=Navegador web +GenericName[ca_valencia]=Web Browser +GenericName[cak]=Web Okik'amaya'l +GenericName[cs]=Webový prohlížeč +GenericName[cy]=Porwr Gwe +GenericName[da]=Webbrowser +GenericName[de]=Internet-Browser +GenericName[dsb]=Webwobglědowak +GenericName[el]=Πρόγραμμα περιήγησης +GenericName[en_CA]=Web Browser +GenericName[en_GB]=Web Browser +GenericName[eo]=Retumilo +GenericName[es_AR]=Navegador web +GenericName[es_CL]=Navegador Web +GenericName[es_ES]=Navegador web +GenericName[es_MX]=Navegador Web +GenericName[et]=Web Browser +GenericName[eu]=Web nabigatzailea +GenericName[fa]=مرورگر وب +GenericName[ff]=Web Browser +GenericName[fi]=Verkkoselain +GenericName[fr]=Navigateur web +GenericName[fur]=Navigadôr Web +GenericName[fy_NL]=Webbrowser +GenericName[ga_IE]=Web Browser +GenericName[gd]=Brabhsair-lìn +GenericName[gl]=Navegador web +GenericName[gn]=Ñanduti Kundahára +GenericName[gu_IN]=Web Browser +GenericName[he]=דפדפן אינטרנט +GenericName[hi_IN]=Web Browser +GenericName[hr]=Web preglednik +GenericName[hsb]=Webwobhladowak +GenericName[hu]=Webböngésző +GenericName[hy_AM]=Վեբ դիտարկիչ +GenericName[ia]=Navigator web +GenericName[id]=Peramban Web +GenericName[is]=Vafri +GenericName[it]=Browser web +GenericName[ja]=ウェブブラウザー +GenericName[ka]=ბრაუზერი +GenericName[kab]=Iminig web +GenericName[kk]=Веб-браузері +GenericName[km]=Web Browser +GenericName[kn]=Web Browser +GenericName[ko]=웹 브라우저 +GenericName[lij]=Navegatô Web +GenericName[lt]=Web Browser +GenericName[lv]=Tīmekļa pārlūks +GenericName[mk]=Web Browser +GenericName[mr]=Web Browser +GenericName[ms]=Web Browser +GenericName[my]=Web Browser +GenericName[nb_NO]=Nettleser +GenericName[ne_NP]=वेब ब्राउजर +GenericName[nl]=Webbrowser +GenericName[nn_NO]=Nettlesar +GenericName[oc]=Navegador Web +GenericName[pa_IN]=ਵੈੱਬ ਬਰਾਊਜ਼ਰ +GenericName[pl]=Przeglądarka internetowa +GenericName[pt_BR]=Navegador web +GenericName[pt_PT]=Navegador Web +GenericName[rm]=Navigatur web +GenericName[ro]=Browser web +GenericName[ru]=Веб-браузер +GenericName[sat]=ᱣᱮᱵᱽ ᱵᱽᱨᱟᱣᱡᱚᱨ +GenericName[sc]=Navigadore web +GenericName[sco]=Web Browser +GenericName[si]=වියමන අතිරික්සුව +GenericName[sk]=Webový prehliadač +GenericName[skr]=ویب براؤزر +GenericName[sl]=Spletni brskalnik +GenericName[son]=Web Browser +GenericName[sq]=Shfletues +GenericName[sr]=Веб прегледач +GenericName[sv_SE]=Webbläsare +GenericName[szl]=Web Browser +GenericName[ta]=Web Browser +GenericName[te]=జాల విహారిణి +GenericName[tg]=Браузери веб +GenericName[th]=เว็บเบราว์เซอร์ +GenericName[tl]=Web Browser +GenericName[tr]=Web Tarayıcısı +GenericName[trs]=Web riña gāchē nu’ +GenericName[uk]=Браузер +GenericName[ur]=Web Browser +GenericName[uz]=Web Browser +GenericName[vi]=Trình duyệt web +GenericName[xh]=Web Browser +GenericName[zh_CN]=Web 浏览器 +GenericName[zh_TW]=網頁瀏覽器 +Keywords=Internet;WWW;Browser;Web;Explorer; +Keywords[ach]=Internet;WWW;Browser;Web;Explorer; +Keywords[af]=Internet;WWW;Browser;Web;Explorer; +Keywords[an]=Internet;WWW;Browser;Web;Explorer; +Keywords[ar]=إنترنت;WWW;متصفح;ويب;مستكشف; +Keywords[ast]=Internet;WWW;Browser;Web;Explorer; +Keywords[az]=Internet;WWW;Browser;Web;Explorer; +Keywords[be]=Internet;WWW;Browser;Web;Explorer; +Keywords[bg]=Internet;WWW;Browser;Web;Explorer; +Keywords[bn]=ইন্টারনেট;WWW;ব্রাউজার;ওয়েব;এক্সপ্লোরার; +Keywords[br]=Internet;WWW;Merdeer;Web;Ergerzhout; +Keywords[bs]=Internet;WWW;Pretraživač;Web;Istraživač; +Keywords[ca]=Internet;WWW;Browser;Web;Explorador;Navegador; +Keywords[ca_valencia]=Internet;WWW;Browser;Web;Explorer; +Keywords[cak]=K'amaya'l;WWW;Okik'amaya'l;Kanob'äl; +Keywords[cs]=internet;WWW;prohlížeč;web; +Keywords[cy]=Rhyngrwyd;WWW;Porwr;Gwe;Archwiliwr; +Keywords[da]=Internet;WWW;Browser;Nettet;Explorer; +Keywords[de]=Internet;WWW;Browser;Web;Explorer; +Keywords[dsb]=Internet;WWW;wobglědowak;Web;Explorer; +Keywords[el]=Internet;WWW;Browser;Web;Explorer;Διαδίκτυο;Ιστός;Ίντερνετ; +Keywords[en_CA]=Internet;WWW;Browser;Web;Explorer; +Keywords[en_GB]=Internet;WWW;Browser;Web;Explorer; +Keywords[eo]=Interreto;Retumilo;TTT;Teksaĵo;Reto;Internet;WWW;Browser;Web;Explorer; +Keywords[es_AR]=Internet;WWW;Navegador;Web;Explorador; +Keywords[es_CL]=Internet;WWW;Navegador;Web;Explorador; +Keywords[es_ES]=Internet;WWW;Navegador;Web;Explorador; +Keywords[es_MX]=Internet;WWW;Navegador;Web;Explorador; +Keywords[et]=Internet;WWW;Browser;Web;Explorer; +Keywords[eu]=Internet;WWW;Nabigatzailea;Web;Arakatzailea; +Keywords[fa]=Internet;WWW;Browser;Web;Explorer; +Keywords[ff]=Internet;WWW;Browser;Web;Explorer; +Keywords[fi]=Internet;WWW;Browser;Web;Explorer;netti;webbi;selain; +Keywords[fr]=Internet;WWW;Navigateur;Web;Explorer; +Keywords[fur]=Internet;WWW;Browser;Navigadôr;Web;Esploradôr;Explorer; +Keywords[fy_NL]=Ynternet;WWW;Browser;Web;Ferkenner; +Keywords[ga_IE]=Internet;WWW;Browser;Web;Explorer; +Keywords[gd]=Internet;WWW;Browser;Web;Explorer;eadar-lìon;brabhsair;brobhsair;lìon;taisgealaiche; +Keywords[gl]=Internet;WWW;Navegador;Web;Explorador; +Keywords[gn]=Internet;WWW;Browser;Web;Explorer; +Keywords[gu_IN]=Internet;WWW;Browser;Web;Explorer; +Keywords[he]=אינטרנט;WWW;דפדפן;רשת;סייר;מרשתת; +Keywords[hi_IN]=Internet;WWW;Browser;Web;Explorer; +Keywords[hr]=Internet;WWW;Preglednik;Web;Istraživač; +Keywords[hsb]=Internet;WWW;wobhladowak;Web;Explorer; +Keywords[hu]=Internet;WWW;Böngésző;Web;Világháló; +Keywords[hy_AM]=Համացանց,WWW,Զննիչ,Վեբ,Ցանցախույզ: +Keywords[ia]=Internet;WWW;Navigator;Web;Explorator; +Keywords[id]=Internet;WWW;Browser;Web;Explorer; +Keywords[is]=Internet;WWW; Vafri; Vefur; Explorer; +Keywords[it]=Internet;WWW;Browser;Web;Explorer;Navigatore; +Keywords[ja]=Internet;WWW;Browser;Web;Explorer;インターネット;ブラウザー;ウェブ; +Keywords[ka]=ინტერნეტი;WWW;ბრაუზერი;ქსელი;ქსელთან წვდომა; +Keywords[kab]=Internet;WWW;Browser;Web;Explorer; +Keywords[kk]=Internet;WWW;Browser;Web;Explorer;Интернет;Ғаламтор;Браузер;Желі;Шолғыш; +Keywords[km]=Internet;WWW;Browser;Web;Explorer; +Keywords[kn]=Internet;WWW;Browser;Web;Explorer; +Keywords[ko]=인터넷;브라우저;웹;탐색기;Internet;WWW;Browser;Web;Explorer; +Keywords[lij]=Internet;WWW;Browser;Web;Explorer;Navegatô; +Keywords[lt]=Internet;WWW;Browser;Web;Explorer; +Keywords[lv]=Internets;WWW;Pārlūkprogramma;Tīmeklis; +Keywords[mk]=Internet;WWW;Browser;Web;Explorer; +Keywords[mr]=Internet;WWW;Browser;Web;Explorer; +Keywords[ms]=Internet;WWW;Browser;Web;Explorer; +Keywords[my]=Internet;WWW;Browser;Web;Explorer; +Keywords[nb_NO]=Internett;WWW;Nettleser;Web;Utforsker; +Keywords[ne_NP]=Internet;WWW;Browser;Web;Explorer; +Keywords[nl]=Internet;WWW;Browser;Web;Verkenner; +Keywords[nn_NO]=Internett;WWW;Nettlesar;Web;Utforskar; +Keywords[oc]=Internet;WWW;Navegador;Navigador;Navegator;Navigator;Web;Explorer; +Keywords[pa_IN]=ਇੰਟਰਨੈੱਟ;WWW;ਬਰਾਊਜ਼ਰ;ਵੈੱਬ;ਐਕਸਪਲਰੋਰ;ਵੈਬ;ਇੰਟਰਨੈਟ; +Keywords[pl]=Internet;WWW;Przeglądarka;Browser;Wyszukiwarka;Web;Sieć;Explorer;Eksplorer;Strony;Witryny;internetowe; +Keywords[pt_BR]=Internet;WWW;Browser;Web;Explorer;Navegador; +Keywords[pt_PT]=Internet;WWW;Navegador;Web;Explorador; +Keywords[rm]=Internet;WWW;Browser;Web;Explorer;navigatur; +Keywords[ro]=Internet; WWW; Browser; Web; Explorer; +Keywords[ru]=Сеть;Интернет;Браузер;Доступ в Интернет; +Keywords[sat]=Internet;WWW;Browser;Web;Explorer; +Keywords[sc]=Internet;WWW;Navigadore;Web;Explorer; +Keywords[sco]=Internet;WWW;Browser;Web;Explorer; +Keywords[si]=අන්තර්ජාලය;අතිරික්සුව;පිරික්සන්න;ගවේශකය;Internet;WWW;Browser;Web;Explorer; +Keywords[sk]=Internet;WWW;Prehliadač;Web;Prieskumník; +Keywords[skr]=Internet;WWW;Browser;Web;Explorer; +Keywords[sl]=internet;www;brskalnik;splet; +Keywords[son]=Internet;WWW;Browser;Web;Explorer; +Keywords[sq]=Internet;WWW;Shfletues;Web;Eksplorues; +Keywords[sr]=Internet;WWW;Browser;Web;Explorer;интернет;њњњ;веб;мрежа;прегледач;експлорер;internet;pregledač;veb;mreža;pregledač;eksplorer; +Keywords[sv_SE]=Internet;WWW;Webbläsare;Webb;Utforskare; +Keywords[szl]=Internet;WWW;Browser;Web;Explorer; +Keywords[ta]=Internet;WWW;Browser;Web;Explorer; +Keywords[te]=Internet;WWW;Browser;Web;Explorer; +Keywords[tg]=Интернет;WWW;Браузер;Сомона;Ҷустуҷӯгар; +Keywords[th]=อินเทอร์เน็ต;เบราว์เซอร์;เว็บ;Internet;WWW;Browser;Web;Explorer; +Keywords[tl]=Internet;WWW;Browser;Web;Explorer; +Keywords[tr]=Internet;WWW;Browser;Web;Explorer;İnternet;Tarayıcı; +Keywords[trs]=Internet;WWW;Browser;Web;Explorer; +Keywords[uk]=Інтернет;WWW;Браузер;Веб;Переглядач; +Keywords[ur]=Internet;WWW;Browser;Web;Explorer; +Keywords[uz]=Internet;WWW;Browser;Web;Explorer; +Keywords[vi]=Internet;WWW;Trình duyệt;Web;Duyệt web; +Keywords[xh]=Internet;WWW;Browser;Web;Explorer; +Keywords[zh_CN]=Internet;WWW;Browser;Web;Explorer; +Keywords[zh_TW]=網際網路;網路;瀏覽器;網頁;上網;Internet;WWW;Browser;Web;Explorer; +X-GNOME-FullName=Mozilla Firefox +X-GNOME-FullName[ach]=Mozilla Firefox +X-GNOME-FullName[af]=Mozilla Firefox +X-GNOME-FullName[an]=Mozilla Firefox +X-GNOME-FullName[ar]=Mozilla Firefox +X-GNOME-FullName[ast]=Mozilla Firefox +X-GNOME-FullName[az]=Mozilla Firefox +X-GNOME-FullName[be]=Mozilla Firefox +X-GNOME-FullName[bg]=Mozilla Firefox +X-GNOME-FullName[bn]=Mozilla Firefox +X-GNOME-FullName[br]=Mozilla Firefox +X-GNOME-FullName[bs]=Mozilla Firefox +X-GNOME-FullName[ca]=Mozilla Firefox +X-GNOME-FullName[ca_valencia]=Mozilla Firefox +X-GNOME-FullName[cak]=Mozilla Firefox +X-GNOME-FullName[cs]=Mozilla Firefox +X-GNOME-FullName[cy]=Mozilla Firefox +X-GNOME-FullName[da]=Mozilla Firefox +X-GNOME-FullName[de]=Mozilla Firefox +X-GNOME-FullName[dsb]=Mozilla Firefox +X-GNOME-FullName[el]=Mozilla Firefox +X-GNOME-FullName[en_CA]=Mozilla Firefox +X-GNOME-FullName[en_GB]=Mozilla Firefox +X-GNOME-FullName[eo]=Mozilla Firefox +X-GNOME-FullName[es_AR]=Mozilla Firefox +X-GNOME-FullName[es_CL]=Mozilla Firefox +X-GNOME-FullName[es_ES]=Mozilla Firefox +X-GNOME-FullName[es_MX]=Mozilla Firefox +X-GNOME-FullName[et]=Mozilla Firefox +X-GNOME-FullName[eu]=Mozilla Firefox +X-GNOME-FullName[fa]=Mozilla Firefox +X-GNOME-FullName[ff]=Mozilla Firefox +X-GNOME-FullName[fi]=Mozilla Firefox +X-GNOME-FullName[fr]=Mozilla Firefox +X-GNOME-FullName[fur]=Mozilla Firefox +X-GNOME-FullName[fy_NL]=Mozilla Firefox +X-GNOME-FullName[ga_IE]=Mozilla Firefox +X-GNOME-FullName[gd]=Mozilla Firefox +X-GNOME-FullName[gl]=Mozilla Firefox +X-GNOME-FullName[gn]=Mozilla Firefox +X-GNOME-FullName[gu_IN]=Mozilla Firefox +X-GNOME-FullName[he]=Mozilla Firefox +X-GNOME-FullName[hi_IN]=Mozilla Firefox +X-GNOME-FullName[hr]=Mozilla Firefox +X-GNOME-FullName[hsb]=Mozilla Firefox +X-GNOME-FullName[hu]=Mozilla Firefox +X-GNOME-FullName[hy_AM]=Mozilla Firefox +X-GNOME-FullName[ia]=Mozilla Firefox +X-GNOME-FullName[id]=Mozilla Firefox +X-GNOME-FullName[is]=Mozilla Firefox +X-GNOME-FullName[it]=Mozilla Firefox +X-GNOME-FullName[ja]=Mozilla Firefox +X-GNOME-FullName[ka]=Mozilla Firefox +X-GNOME-FullName[kab]=Mozilla Firefox +X-GNOME-FullName[kk]=Mozilla Firefox +X-GNOME-FullName[km]=Mozilla Firefox +X-GNOME-FullName[kn]=Mozilla Firefox +X-GNOME-FullName[ko]=Mozilla Firefox +X-GNOME-FullName[lij]=Mozilla Firefox +X-GNOME-FullName[lt]=Mozilla Firefox +X-GNOME-FullName[lv]=Mozilla Firefox +X-GNOME-FullName[mk]=Mozilla Firefox +X-GNOME-FullName[mr]=Mozilla Firefox +X-GNOME-FullName[ms]=Mozilla Firefox +X-GNOME-FullName[my]=Mozilla Firefox +X-GNOME-FullName[nb_NO]=Mozilla Firefox +X-GNOME-FullName[ne_NP]=Mozilla Firefox +X-GNOME-FullName[nl]=Mozilla Firefox +X-GNOME-FullName[nn_NO]=Mozilla Firefox +X-GNOME-FullName[oc]=Mozilla Firefox +X-GNOME-FullName[pa_IN]=Mozilla Firefox +X-GNOME-FullName[pl]=Mozilla Firefox +X-GNOME-FullName[pt_BR]=Mozilla Firefox +X-GNOME-FullName[pt_PT]=Mozilla Firefox +X-GNOME-FullName[rm]=Mozilla Firefox +X-GNOME-FullName[ro]=Mozilla Firefox +X-GNOME-FullName[ru]=Mozilla Firefox +X-GNOME-FullName[sat]=Mozilla Firefox +X-GNOME-FullName[sc]=Mozilla Firefox +X-GNOME-FullName[sco]=Mozilla Firefox +X-GNOME-FullName[si]=Mozilla Firefox +X-GNOME-FullName[sk]=Mozilla Firefox +X-GNOME-FullName[skr]=Mozilla Firefox +X-GNOME-FullName[sl]=Mozilla Firefox +X-GNOME-FullName[son]=Mozilla Firefox +X-GNOME-FullName[sq]=Mozilla Firefox +X-GNOME-FullName[sr]=Mozilla Firefox +X-GNOME-FullName[sv_SE]=Mozilla Firefox +X-GNOME-FullName[szl]=Mozilla Firefox +X-GNOME-FullName[ta]=Mozilla Firefox +X-GNOME-FullName[te]=Mozilla Firefox +X-GNOME-FullName[tg]=Mozilla Firefox +X-GNOME-FullName[th]=Mozilla Firefox +X-GNOME-FullName[tl]=Mozilla Firefox +X-GNOME-FullName[tr]=Mozilla Firefox +X-GNOME-FullName[trs]=Mozilla Firefox +X-GNOME-FullName[uk]=Mozilla Firefox +X-GNOME-FullName[ur]=Mozilla Firefox +X-GNOME-FullName[uz]=Mozilla Firefox +X-GNOME-FullName[vi]=Mozilla Firefox +X-GNOME-FullName[xh]=Mozilla Firefox +X-GNOME-FullName[zh_CN]=Mozilla Firefox +X-GNOME-FullName[zh_TW]=Mozilla Firefox +X-Flatpak=org.mozilla.firefox +X-SteamFrameNix-Source=@SOURCE@ + +[Desktop Action new-window] +Exec=/nix/store/00000000000000000000000000000000-firefox-wrapper /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox --new-window @@u %u @@ +Name=New Window +Name[ach]=New Window +Name[af]=New Window +Name[an]=New Window +Name[ar]=نافذة جديدة +Name[ast]=New Window +Name[az]=New Window +Name[be]=Новае акно +Name[bg]=Нов прозорец +Name[bn]=নতুন উইন্ডো +Name[br]=Prenestr nevez +Name[bs]=Novi prozor +Name[ca]=Finestra nova +Name[ca_valencia]=New Window +Name[cak]=K'ak'a' Tzuwäch +Name[cs]=Nové okno +Name[cy]=Ffenestr Newydd +Name[da]=Nyt vindue +Name[de]=Neues Fenster +Name[dsb]=Nowe wokno +Name[el]=Νέο παράθυρο +Name[en_CA]=New Window +Name[en_GB]=New Window +Name[eo]=Nova fenestro +Name[es_AR]=Nueva ventana +Name[es_CL]=Nueva ventana +Name[es_ES]=Nueva ventana +Name[es_MX]=Nueva ventana +Name[et]=New Window +Name[eu]=Leiho berria +Name[fa]=پنجره جدید‌ +Name[ff]=New Window +Name[fi]=Uusi ikkuna +Name[fr]=Nouvelle fenêtre +Name[fur]=Gnûf barcon +Name[fy_NL]=Nij finster +Name[ga_IE]=New Window +Name[gd]=Uinneag ùr +Name[gl]=Nova xanela +Name[gn]=Ovetã pyahu +Name[gu_IN]=New Window +Name[he]=חלון חדש +Name[hi_IN]=New Window +Name[hr]=Novi prozor +Name[hsb]=Nowe wokno +Name[hu]=Új ablak +Name[hy_AM]=Նոր պատուհան +Name[ia]=Nove fenestra +Name[id]=Jendela Baru +Name[is]=Nýr gluggi +Name[it]=Nuova finestra +Name[ja]=新しいウィンドウ +Name[ka]=ახალი ფანჯარა +Name[kab]=Asfaylu amaynut +Name[kk]=Жаңа терезе +Name[km]=បង្អួច​​​ថ្មី +Name[kn]=New Window +Name[ko]=새 창 +Name[lij]=Neuvo Barcon +Name[lt]=New Window +Name[lv]=Jauns logs +Name[mk]=New Window +Name[mr]=New Window +Name[ms]=New Window +Name[my]=New Window +Name[nb_NO]=Nytt vindu +Name[ne_NP]=नयाँ सञ्झ्याल +Name[nl]=Nieuw venster +Name[nn_NO]=Nytt vindauge +Name[oc]=Fenèstra novèla +Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ +Name[pl]=Nowe okno +Name[pt_BR]=Nova janela +Name[pt_PT]=Nova janela +Name[rm]=Nova fanestra +Name[ro]=Fereastră nouă +Name[ru]=Новое окно +Name[sat]=ᱱᱟᱶᱟ ᱣᱤᱱᱰᱳ +Name[sc]=Ventana noa +Name[sco]=New Window +Name[si]=නව කවුළුව +Name[sk]=Nové okno +Name[skr]=نویں ونڈو +Name[sl]=Novo okno +Name[son]=New Window +Name[sq]=Dritare e Re +Name[sr]=Нови прозор +Name[sv_SE]=Nytt fönster +Name[szl]=New Window +Name[ta]=New Window +Name[te]=కొత్త కిటికీ +Name[tg]=Равзанаи нав +Name[th]=หน้าต่างใหม่ +Name[tl]=New Window +Name[tr]=Yeni pencere +Name[trs]=Bēntanâ nākàa +Name[uk]=Нове вікно +Name[ur]=New Window +Name[uz]=New Window +Name[vi]=Cửa sổ mới +Name[xh]=New Window +Name[zh_CN]=新建窗口 +Name[zh_TW]=開新視窗 + +[Desktop Action new-private-window] +Exec=/nix/store/00000000000000000000000000000000-firefox-wrapper /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox --private-window @@u %u @@ +Name=New Private Window +Name[ach]=New Private Window +Name[af]=New Private Window +Name[an]=New Private Window +Name[ar]=نافذة خاصة جديدة +Name[ast]=New Private Window +Name[az]=New Private Window +Name[be]=Новае прыватнае акно +Name[bg]=Нов личен прозорец +Name[bn]=নতুন ব্যক্তিগত উইন্ডো +Name[br]=Prenestr prevez nevez +Name[bs]=Novi privatni prozor +Name[ca]=Finestra privada nova +Name[ca_valencia]=New Private Window +Name[cak]=K'ak'a' Ichinan Tzuwäch +Name[cs]=Nové anonymní okno +Name[cy]=Ffenestr Breifat Newydd +Name[da]=Nyt privat vindue +Name[de]=Neues privates Fenster +Name[dsb]=Nowe priwatne wokno +Name[el]=Νέο ιδιωτικό παράθυρο +Name[en_CA]=New Private Window +Name[en_GB]=New Private Window +Name[eo]=Nova privata fenestro +Name[es_AR]=Nueva ventana privada +Name[es_CL]=Nueva ventana privada +Name[es_ES]=Nueva ventana privada +Name[es_MX]=Nueva ventana privada +Name[et]=New Private Window +Name[eu]=Leiho pribatu berria +Name[fa]=پنجره ناشناس جدید +Name[ff]=New Private Window +Name[fi]=Uusi yksityinen ikkuna +Name[fr]=Nouvelle fenêtre privée +Name[fur]=Gnûf barcon privât +Name[fy_NL]=Nij priveefinster +Name[ga_IE]=New Private Window +Name[gd]=Uinneag phrìobhaideach ùr +Name[gl]=Nova xanela privada +Name[gn]=Ovetã ñemi pyahu +Name[gu_IN]=New Private Window +Name[he]=חלון פרטי חדש +Name[hi_IN]=New Private Window +Name[hr]=Novi privatni prozor +Name[hsb]=Nowe priwatne wokno +Name[hu]=Új privát ablak +Name[hy_AM]=Նոր գաղտնի պատուհան +Name[ia]=Nove fenestra private +Name[id]=Jendela Mode Pribadi Baru +Name[is]=Nýr huliðsgluggi +Name[it]=Nuova finestra anonima +Name[ja]=新しいプライベートウィンドウ +Name[ka]=ახალი პირადი ფანჯარა +Name[kab]=Asfaylu amaynut n tunigin tusligt +Name[kk]=Жаңа жекелік терезе +Name[km]=បង្អួច​ឯកជន​ថ្មី +Name[kn]=New Private Window +Name[ko]=새 사생활 보호 창 +Name[lij]=Neuvo Barcon Privòu +Name[lt]=New Private Window +Name[lv]=Jauns privātais logs +Name[mk]=New Private Window +Name[mr]=New Private Window +Name[ms]=New Private Window +Name[my]=New Private Window +Name[nb_NO]=Nytt privat vindu +Name[ne_NP]=नयाँ निजी सञ्झ्याल +Name[nl]=Nieuw privévenster +Name[nn_NO]=Nytt privat vindauge +Name[oc]=Fenèstra privada novèla +Name[pa_IN]=ਨਵੀਂ ਪ੍ਰਾਈਵੇਟ ਵਿੰਡੋ +Name[pl]=Nowe okno prywatne +Name[pt_BR]=Nova janela privativa +Name[pt_PT]=Nova janela privada +Name[rm]=Nova fanestra privata +Name[ro]=Fereastră privată nouă +Name[ru]=Новое приватное окно +Name[sat]=ᱱᱟᱶᱟ ᱱᱤᱡᱮᱨᱟᱜ ᱣᱤᱱᱰᱳ +Name[sc]=Ventana privada noa +Name[sco]=New Private Window +Name[si]=නව පෞද්. කවුළුව +Name[sk]=Nové súkromné okno +Name[skr]=نویں نجی ونڈو +Name[sl]=Novo zasebno okno +Name[son]=New Private Window +Name[sq]=Dritare e Re Private +Name[sr]=Нови приватни прозор +Name[sv_SE]=Nytt privat fönster +Name[szl]=New Private Window +Name[ta]=New Private Window +Name[te]=కొత్త ఆంతరంగిక కిటికీ +Name[tg]=Равзанаи хусусии нав +Name[th]=หน้าต่างส่วนตัวใหม่ +Name[tl]=New Private Window +Name[tr]=Yeni gizli pencere +Name[trs]=Bēntanâ huì nākàa +Name[uk]=Приватне вікно +Name[ur]=New Private Window +Name[uz]=New Private Window +Name[vi]=Cửa sổ riêng tư mới +Name[xh]=New Private Window +Name[zh_CN]=新建隐私窗口 +Name[zh_TW]=開新隱私視窗 + +[Desktop Action open-profile-manager] +Exec=/nix/store/00000000000000000000000000000000-firefox-wrapper /usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox org.mozilla.firefox --ProfileManager +Name=Open Profile Manager +Name[ach]=Open Profile Manager +Name[af]=Open Profile Manager +Name[an]=Open Profile Manager +Name[ar]=افتح مدير الملف الشخصي +Name[ast]=Open Profile Manager +Name[az]=Open Profile Manager +Name[be]=Адкрыць менеджар профіляў +Name[bg]=Отваряне на мениджъра на профили +Name[bn]=Open Profile Manager +Name[br]=Digeriñ an ardoer aeladoù +Name[bs]=Otvori Menadžera profila +Name[ca]=Obre el gestor de perfils +Name[ca_valencia]=Open Profile Manager +Name[cak]=Open Profile Manager +Name[cs]=Otevřít Správce profilů +Name[cy]=Agorwch y Rheolwr Proffil +Name[da]=Åbn profilhåndtering +Name[de]=Profilverwaltung öffnen +Name[dsb]=Profilowy zastojnik wócyniś +Name[el]=Άνοιγμα Διαχείρισης προφίλ +Name[en_CA]=Open Profile Manager +Name[en_GB]=Open Profile Manager +Name[eo]=Malfermi administranton de profiloj +Name[es_AR]=Abrir administrador de perfiles +Name[es_CL]=Abrir administrador de perfiles +Name[es_ES]=Abrir administrador de perfiles +Name[es_MX]=Abrir administrador de perfiles +Name[et]=Open Profile Manager +Name[eu]=Ireki profilen kudeatzailea +Name[fa]=گشودن مدیر نمایه +Name[ff]=Open Profile Manager +Name[fi]=Avaa profiilien hallinta +Name[fr]=Ouvrir le gestionnaire de profils +Name[fur]=Vierç gjestôr profîi +Name[fy_NL]=Profylbehearder iepenje +Name[ga_IE]=Open Profile Manager +Name[gd]=Fosgail manaidsear nam pròifilean +Name[gl]=Abrir o xestor de perfís +Name[gn]=Embojuruja mba’ete ñangarekoha +Name[gu_IN]=Open Profile Manager +Name[he]=פתיחת מנהל הפרופילים +Name[hi_IN]=Open Profile Manager +Name[hr]=Otvori upravljač profila +Name[hsb]=Zrjadowak profilow wočinić +Name[hu]=Profilkezelő megnyitása +Name[hy_AM]=Բացեք պրոֆիլի կառավարիչը +Name[ia]=Aperir le gestor de profilo +Name[id]=Buka Pengelola Profil +Name[is]=Opna umsýslu notandasniða +Name[it]=Apri gestore profili +Name[ja]=プロファイルマネージャーを開く +Name[ka]=პროფილის მმართველის გახსნა +Name[kab]=Ldi amsefrak n umaɣnu +Name[kk]=Профильдер бақарушысын ашу +Name[km]=Open Profile Manager +Name[kn]=Open Profile Manager +Name[ko]=프로필 관리자 열기 +Name[lij]=Open Profile Manager +Name[lt]=Open Profile Manager +Name[lv]=Atvērt profilu pārvaldnieku +Name[mk]=Open Profile Manager +Name[mr]=Open Profile Manager +Name[ms]=Open Profile Manager +Name[my]=Open Profile Manager +Name[nb_NO]=Åpne profilbehandler +Name[ne_NP]=Open Profile Manager +Name[nl]=Profielbeheerder openen +Name[nn_NO]=Opne profilhandsaming +Name[oc]=Dobrir lo gestionari de perfils +Name[pa_IN]=ਪਰੋਫ਼ਾਈਲ ਮੈਨੇਜਰ ਖੋਲ੍ਹੋ +Name[pl]=Menedżer profili +Name[pt_BR]=Abrir gerenciador de perfis +Name[pt_PT]=Abrir o Gestor de Perfis +Name[rm]=Avrir l'administraziun da profils +Name[ro]=Deschide managerul de profiluri +Name[ru]=Открыть менеджер профилей +Name[sat]=ᱢᱮᱫᱦᱟᱸ ᱢᱮᱱᱮᱡᱚᱨ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ +Name[sc]=Aberi su gestore de profilos +Name[sco]=Open Profile Manager +Name[si]=පැතිකඩ කළමනාකරු අරින්න +Name[sk]=Otvoriť Správcu profilov +Name[skr]=پروفائل منیجر کھولو +Name[sl]=Odpri upravitelja profilov +Name[son]=Open Profile Manager +Name[sq]=Hapni Përgjegjës Profilesh +Name[sr]=Отворите управљач профила +Name[sv_SE]=Öppna Profilhanteraren +Name[szl]=Open Profile Manager +Name[ta]=Open Profile Manager +Name[te]=Open Profile Manager +Name[tg]=Кушодани мудири профилҳо +Name[th]=เปิดตัวจัดการโปรไฟล์ +Name[tl]=Open Profile Manager +Name[tr]=Profil yöneticisini aç +Name[trs]=Sa nīkāj ñu’ūnj nej perfî huā nì’nï̀nj ïn +Name[uk]=Відкрити менеджер профілів +Name[ur]=Open Profile Manager +Name[uz]=Open Profile Manager +Name[vi]=Mở trình quản lý hồ sơ +Name[xh]=Open Profile Manager +Name[zh_CN]=打开配置文件管理器 +Name[zh_TW]=開啟設定檔管理員 diff --git a/modules/launchers/fixtures/sources/com.anthropic.Claude.desktop b/modules/launchers/fixtures/sources/com.anthropic.Claude.desktop new file mode 100644 index 0000000..5cc5dfb --- /dev/null +++ b/modules/launchers/fixtures/sources/com.anthropic.Claude.desktop @@ -0,0 +1,25 @@ +[Desktop Entry] +Name=Claude +Comment=Desktop application for Claude.ai +GenericName=AI Assistant +Keywords=AI;Chat;Assistant;Claude;Code;LLM; +Exec=/nix/store/8cc1cplpbdlsqy4x0v1c9w66w3dq80w4-claude-desktop-2.7032.0/bin/claude-desktop %U +Icon=claude-desktop +Type=Application +StartupNotify=true +# Matches the Wayland app_id / X11 WM_CLASS Chromium derives from +# package.json desktopName, so docks group windows under this entry. +StartupWMClass=com.anthropic.Claude +# second-instance just focuses mainWindow; suppress GNOME's default "New Window" item +SingleMainWindow=true +Categories=Utility;Development; +MimeType=x-scheme-handler/claude; +Actions=NewChat;NewCode; + +[Desktop Action NewChat] +Name=New Chat +Exec=/nix/store/8cc1cplpbdlsqy4x0v1c9w66w3dq80w4-claude-desktop-2.7032.0/bin/claude-desktop "claude://claude.ai/new?surface=chat&source=desktop_action" + +[Desktop Action NewCode] +Name=New Claude Code Session +Exec=/nix/store/8cc1cplpbdlsqy4x0v1c9w66w3dq80w4-claude-desktop-2.7032.0/bin/claude-desktop "claude://code/new?source=desktop_action" diff --git a/modules/launchers/fixtures/sources/im.riot.Riot.desktop b/modules/launchers/fixtures/sources/im.riot.Riot.desktop new file mode 100644 index 0000000..0f8a33e --- /dev/null +++ b/modules/launchers/fixtures/sources/im.riot.Riot.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Name=Element +Comment=A Matrix-based instant messaging client +Icon=im.riot.Riot +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=/app/bin/element --file-forwarding im.riot.Riot @@u %U @@ +Categories=Network;InstantMessaging;Chat;VideoConference; +MimeType=x-scheme-handler/element;x-scheme-handler/io.element.desktop; +StartupWMClass=element +Keywords=Matrix;matrix.org;chat;irc;communications;talk;riot;vector; +X-Flatpak=im.riot.Riot diff --git a/modules/launchers/fixtures/sources/org.example.NoAnchor.desktop b/modules/launchers/fixtures/sources/org.example.NoAnchor.desktop new file mode 100644 index 0000000..9d1de55 --- /dev/null +++ b/modules/launchers/fixtures/sources/org.example.NoAnchor.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Application +Name=No anchor +Exec=/app/bin/noanchor %U +X-Flatpak=org.example.NoAnchor diff --git a/modules/launchers/fixtures/sources/org.example.Quoting.desktop b/modules/launchers/fixtures/sources/org.example.Quoting.desktop new file mode 100644 index 0000000..3aa9efd --- /dev/null +++ b/modules/launchers/fixtures/sources/org.example.Quoting.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Type=Application +Name=Quoting test +Name[de]=Zitiertest +# a comment +Exec=env FOO=1 "/opt/My App/bin/app" --flag "with \\"quote\\"" %F +Icon=app +Actions=second; +Keywords=a;b; +Keywords[de]=c;d; +X-Unknown-Key=kept\sas is + +[Desktop Action second] +Name=Second +Exec="/opt/My App/bin/app" --second + +[X-Other Group] +Exec=untouched diff --git a/modules/launchers/fixtures/sources/org.gnome.gedit.desktop b/modules/launchers/fixtures/sources/org.gnome.gedit.desktop new file mode 100644 index 0000000..7820586 --- /dev/null +++ b/modules/launchers/fixtures/sources/org.gnome.gedit.desktop @@ -0,0 +1,373 @@ +[Desktop Entry] +Name[af]=gedit +Name[am]=gedit +Name[an]=gedit +Name[as]=gedit +Name[ast]=gedit +Name[az]=gedit +Name[be]=gedit +Name[bg]=gedit +Name[bn_IN]=gedit +Name[br]=gedit +Name[bs]=gedit +Name[ca]=gedit +Name[ckb]=‫gedit +Name[crh]=gedit +Name[cs]=gedit +Name[da]=gedit +Name[de]=gedit +Name[el]=gedit +Name[en_CA]=gedit +Name[en_GB]=gedit +Name[eo]=gedit +Name[es]=gedit +Name[et]=gedit +Name[eu]=gedit +Name[fa]=جی‌ادیت +Name[fi]=gedit +Name[fr]=gedit +Name[fur]=gedit +Name[ga]=gedit +Name[gl]=gedit +Name[he]=gedit +Name[hr]=gedit +Name[hu]=gedit +Name[id]=gedit +Name[is]=gedit +Name[it]=gedit +Name[ja]=gedit +Name[ka]=gedit +Name[kab]=gedit +Name[kk]=gedit +Name[km]=gedit +Name[kn]=gedit +Name[ko]=지에디트 +Name[lt]=gedit +Name[lv]=gedit +Name[mjw]=gedit +Name[mk]=gedit +Name[ms]=gedit +Name[my]=gedit +Name[nb]=gedit +Name[nds]=gedit +Name[ne]=जिइडिट +Name[nl]=gedit +Name[nn]=gedit +Name[oc]=gedit +Name[or]=gedit +Name[pa]=ਜੀ-ਸੰਪਾਦਕ +Name[pl]=gedit +Name[pt]=gedit +Name[pt_BR]=gedit +Name[ro]=gedit +Name[ru]=gedit +Name[sk]=gedit +Name[sl]=gedit +Name[sr]=Вилењакова бележница +Name[sv]=gedit +Name[ta]=gedit +Name[th]=gedit +Name[tk]=gedit +Name[tr]=gedit +Name[ug]=gedit +Name[uk]=gedit +Name[vi]=gedit +Name[wa]=gedit +Name[zh_CN]=gedit +Name[zh_HK]=gedit +Name[zh_TW]=gedit +Name=gedit +Comment[af]=Redigeer tekslêers +Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ +Comment[an]=Edita fichers de texto +Comment[ar]=حرّر الملفات النصية +Comment[as]=লিখনি ফাইল সম্পাদনা +Comment[ast]=Editar ficheros de testu +Comment[az]=Mətn fayllarını redaktə edin +Comment[be]=Рэдагаванне тэкставых файлаў +Comment[be@latin]=Redahuj tekstavyja fajły +Comment[bg]=Редактиране на текстови файлове +Comment[bn]=টেক্সট ফাইল সম্পাদনা করুন +Comment[bn_IN]=টেক্সট ফাইল সম্পাদনা +Comment[br]=Embann restroù testenn +Comment[bs]=Izmijeni tekstualne datoteke +Comment[ca]=Editeu fitxers de text +Comment[ca@valencia]=Editeu fitxers de text +Comment[ckb]=دەق دەستکاریبکە +Comment[crh]=Metin dosyelerini tahrir et +Comment[cs]=Upravujte textové soubory +Comment[cy]=Golygu ffeiliau testun +Comment[da]=Redigér tekstfiler +Comment[de]=Textdateien bearbeiten +Comment[dz]=ཚིག་ཡིག་ཡིག་སྣོད་ཚུ་ ཞུན་དག་རྐྱབས་ +Comment[el]=Επεξεργασία αρχείων κειμένου +Comment[en_CA]=Edit text files +Comment[en_GB]=Edit text files +Comment[en@shaw]=𐑧𐑛𐑦𐑑 𐑑𐑧𐑒𐑕𐑑 𐑓𐑲𐑤𐑟 +Comment[eo]=Redakti tekstdosierojn +Comment[es]=Edite archivos de texto +Comment[et]=Tekstifailide redigeerimine +Comment[eu]=Editatu testu-fitxategiak +Comment[fa]=ویرایش پرونده‌های متنی +Comment[fi]=Muokkaa tekstitiedostoja +Comment[fr]=Éditer des fichiers texte +Comment[fur]=Modifiche file di test +Comment[ga]=Cuir comhaid téacs in eagar +Comment[gd]=Deasaich faidhlichean teacsa +Comment[gl]=Editar ficheiros de texto +Comment[gu]=લખાણ ફાઇલોમાં ફેરફાર કરો +Comment[he]=עריכת קובצי טקסט +Comment[hi]=पाठ फ़ाइलें संपादित करें +Comment[hr]=Uređivanje tekstovnih datoteka +Comment[hu]=Szövegfájlok szerkesztése +Comment[ia]=Rediger files de texto +Comment[id]=Menyunting berkas teks +Comment[ie]=Redacte files textual +Comment[is]=Sýsla með textaskrár +Comment[it]=Modifica file di testo +Comment[ja]=テキストファイルを編集します +Comment[ka]=ფაილების რედაქტირება +Comment[kab]=Ẓreg ifuyla n uḍris +Comment[kk]=Мәтіндік файлдарды түзету +Comment[km]=កែ​សម្រួល​ឯកសារ​អត្ថបទ +Comment[kn]=ಪಠ್ಯ ಕಡತಗಳನ್ನು ಸಂಪಾದಿಸು +Comment[ko]=텍스트 파일을 편집합니다 +Comment[ku]=Pelên nivîsê sererast bike +Comment[ln]=Bɔngisa kásá ya nkomá +Comment[lt]=Redaguoti tekstinius failus +Comment[lv]=Rediģēt teksta datnes +Comment[mai]=पाठ फाइलकेँ संपादित करू +Comment[mg]=Hanova raki-dahabolana +Comment[mk]=Уредувај текстуални датотеки +Comment[ml]=പദാവലി ഫയലുകളില്‍ മാറ്റം വരുത്തുക +Comment[mn]=Текст файл боловсруулах +Comment[mr]=मजकूर फाइल्स् संपादीत करा +Comment[ms]=Sunting fail teks +Comment[my]=စာသားဖိုင်များပြင်ဆင်ရန် +Comment[nb]=Rediger tekstfiler +Comment[nds]=Textdateien bewarken +Comment[ne]=पाठ फाइल सम्पादन गर्नुहोस् +Comment[nl]=Bewerk tekstbestanden +Comment[nn]=Rediger tekstfiler +Comment[oc]=Editar de fichièrs tèxte +Comment[or]=ପାଠ୍ଯ ଫାଇଲଗୁଡ଼ିକୁ ସମ୍ପାଦନ କରନ୍ତୁ +Comment[pa]=ਲਿਖਤ ਫਾਇਲਾਂ ਸੋਧੋ +Comment[pl]=Edytor plików tekstowych +Comment[ps]=ليکن دوتنې سمول +Comment[pt]=Editar ficheiros de texto +Comment[pt_BR]=Edite arquivos de texto +Comment[ro]=Editare fișiere text +Comment[ru]=Редактор текстовых файлов +Comment[si]=පෙළ ගොනු සකසන්න +Comment[sk]=Úprava textových súborov +Comment[sl]=Enostavni urejevalnik besedilnih datotek +Comment[sq]=Ndrysho file teksti +Comment[sr]=Уређујте текстуалне документе +Comment[sr@latin]=Uređujte tekstualne dokumente +Comment[sv]=Redigera textfiler +Comment[ta]=உரை கோப்புகளை திருத்தவும் +Comment[te]=పాఠ్య ఫైళ్ళను సవరించు +Comment[th]=แก้ไขไฟล์ข้อความ +Comment[tk]=Metin faýllary editle +Comment[tr]=Metin dosyalarını düzenle +Comment[ug]=تېكىست ھۆججەتلىرىنى تەھرىرلەش +Comment[uk]=Редактор текстових файлів +Comment[vi]=Soạn thảo tập tin văn bản +Comment[wa]=Asspougnî des fitchîs tecses +Comment[xh]=Hlela iifayili zombhalo +Comment[zh_CN]=编辑文本文件 +Comment[zh_HK]=編輯文字檔 +Comment[zh_TW]=編輯文字檔 +Comment=Edit text files +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=gedit --file-forwarding org.gnome.gedit @@u %U @@ +Terminal=false +Type=Application +StartupNotify=true +MimeType=text/plain;application/x-zerosize; +Icon=org.gnome.gedit +Categories=GNOME;GTK;Utility;TextEditor; +Actions=new-window;new-document; +Keywords[be]=Text;Editor;Plaintext;Write;gedit;;Тэкст;Рэдактар;Тэкставы;Запіс; +Keywords[bg]=текст;редактор;обикновен;текст;text;editor;plaintext;write;gedit; +Keywords[bn_IN]=পাঠ্য;সম্পাদক;প্লেটেক্সট;লিখুন;gedit; +Keywords[ca]=Text;Editor;text simple;escriure;gedit; +Keywords[cs]=text;editor;prostý text;psát;psaní;gedit; +Keywords[da]=Tekst;Editor;Redigering;Tekstredigering;Skrive;Skriv;gedit; +Keywords[de]=Text;Editor;Klartext;Schreiben;gedit; +Keywords[el]=Text;Editor;Plaintext;Write;gedit;κείμενο;επεξεργαστής;εγγραφή;γράψιμο;gedit; +Keywords[en_GB]=Text;Editor;Plaintext;Write;gedit; +Keywords[eo]=Teksto;Redaktilo;Plata teksto;Skribi;gedit; +Keywords[es]=texto;editor;plano;escribir;gedit; +Keywords[eu]=Testua;Editorea;Testu soila;Idatzi;gedit; +Keywords[fa]=Text;Editor;Plaintext;Write;gedit;ویرایشگر متن;متن;ویرایشگر;نوشتار; +Keywords[fi]=Text;Editor;Plaintext;Write;gedit;teksti;muokkain;editori;tekstieditori;tekstimuokkain; +Keywords[fr]=texte brut;éditeur;écrire;gedit; +Keywords[fur]=Text;Editor;Test sempliç;Scrivi;gedit; +Keywords[gl]=Texto;Editor;Texto plano;Escribir;gedit; +Keywords[he]=טקסט;עורך;עריכה;טקסט פשוט;כתיבה;gedit; +Keywords[hr]=Tekst;Uređivač;Izvoran tekst;Piši;gedit; +Keywords[hu]=Szöveg;Szerkesztő;Egyszerű szöveg;Írás;gedit; +Keywords[id]=Teks;Penyunting;Teks Polos;Tulis;gedit; +Keywords[is]=Texti;Ritill;Hreintexti;Skrifa;gedit; +Keywords[it]=Editor;Testo;Text;Scrivere;gedit; +Keywords[ja]=Text;Editor;Plaintext;Write;gedit;テキスト;エディター;プレーンテキスト;書く; +Keywords[ka]=Text;Editor;Plaintext;Write;gedit; +Keywords[kab]=Aḍris;Amaẓrag;Plaintext;Aru;gedit; +Keywords[kk]=Text;Editor;Plaintext;Write;gedit;Мәтін;Түзеткіш;Ашық мәтін;Жазу; +Keywords[ko]=Text;텍스트;Editor;편집기;Plaintext;일반텍스트;Write;글쓰기;작성;gedit;지에디트; +Keywords[lt]=Tekstas;Redaktorius;Grynasis tekstas;Rašymas;gedit; +Keywords[lv]=Teksts;Redaktors;Vienkāršs teksts;Rakstīt;gedit; +Keywords[mjw]=Text;Editor;Plaintext;Write;gedit; +Keywords[ms]=Teks;Penyunting;Plaintext;Menulis;gedit; +Keywords[nb]=Tekst;Redigering;Vanlig tekst;Skriv;gedit; +Keywords[ne]=पाठ;सम्पादक;सादापाठ;लेखाई;जिइडिट; +Keywords[nl]=Text;tekst;Editor;teksteditor;tekstverwerker;Plaintext;platte tekst;Write;schrijven;gedit; +Keywords[pa]=ਸ਼ਬਦ;ਟੈਕਸਟ;ਸੰਪਾਦਕ;ਐਡੀਟਰ;ਪਲੇਨਟੈਕਸਟ;ਜੀਸੰਪਾਦਕ; +Keywords[pl]=Tekst;Text;Edytor;Zwykły tekst;Pisanie;Pisz;Napisz;Write;gedit; +Keywords[pt]=Texto;Editor;Texto simples;Escrever;gedit; +Keywords[pt_BR]=Texto;Editor;Texto simples;Plaintext;Escrever;Write;gedit; +Keywords[ro]=Text;Editor;Plaintext;Write;Text simplu;Scrie;gedit; +Keywords[ru]=Text;Editor;Plaintext;Write;gedit;Текст;Редактор;Запись;Текстовый; +Keywords[sk]=Text;Editor;Čistý text;Písať;Napísať;Písanie;gedit; +Keywords[sl]=Besedilo;Urejevalnik;tekst;Write;gedit;Pisanje; +Keywords[sr]=Text;Editor;Plaintext;Write;текст;уређивач;обичан текст;писање;гедит;tekst;uređivač;običan tekst;pisanje;gedit; +Keywords[sv]=Text;Redigerare;Vanlig text;Skriv;gedit; +Keywords[th]=ข้อความ;เครื่องมือแก้ไข;ข้อความเปล่า;เขียน;gedit; +Keywords[tr]=Metin;Düzenleyici;Düzyazı;Yaz;gedit; +Keywords[uk]=Текст;Редактор;Запис;Написання;ґедіт;Text;Editor;Plaintext;Write;gedit; +Keywords[vi]=Text;văn;bản;van;ban;Editor;sửa;sua;soạn;thảo;soan;thao;chữ;chu;Plaintext;Chữ thường;Chu thuong;Write;Ghi;Lưu;Luu;gedit; +Keywords[zh_CN]=Text;Editor;Plaintext;Write;gedit;文本;文字;编辑器;纯文本;编写; +Keywords[zh_TW]=Text;Editor;Plaintext;Write;gedit;文字;編輯器;純文字;撰寫;編寫; +Keywords=Text;Editor;Plaintext;Write;gedit; +DBusActivatable=true +X-Flatpak=org.gnome.gedit + +[Desktop Action new-window] +Name[af]=Nuwe venster +Name[ar]=نافذة جديدة +Name[be]=Новае акно +Name[bg]=Нов прозорец +Name[bn_IN]=নতুন উইন্ডো +Name[ca]=Finestra nova +Name[ca@valencia]=_Finestra nova +Name[ckb]=پەنجەریەکی نوێ +Name[cs]=Nové okno +Name[da]=Nyt vindue +Name[de]=Neues Fenster +Name[el]=Νέο παράθυρο +Name[en_GB]=New Window +Name[eo]=Nova fenestro +Name[es]=Ventana nueva +Name[eu]=Leiho berria +Name[fa]=پنجره جدید +Name[fi]=Uusi ikkuna +Name[fr]=Nouvelle fenêtre +Name[fur]=Gnûf barcon +Name[gd]=Uinneag ùr +Name[gl]=_Xanela nova +Name[he]=חלון חדש +Name[hr]=Novi prozor +Name[hu]=Új ablak +Name[ia]=Nove fenestra +Name[id]=Jendela Baru +Name[ie]=Nov fenestre +Name[is]=Nýr gluggi +Name[it]=Nuova finestra +Name[ja]=新しいウィンドウ +Name[ka]=ახალი ფანჯარა +Name[kab]=Asfaylu amaynut +Name[kk]=Жаңа терезе +Name[ko]=새 창 +Name[lt]=Naujas langas +Name[lv]=Jauns logs +Name[mjw]=Kimi Window +Name[ml]=പുതിയ ജാലകം +Name[ms]=Tetingkap Baharu +Name[nb]=Nytt vindu +Name[ne]=नयाँ सञ्झ्याल +Name[nl]=Nieuw venster +Name[oc]=Novèla fenèstra +Name[pa]=ਨਵੀਂ ਵਿੰਡੋ +Name[pl]=Nowe okno +Name[pt]=Nova janela +Name[pt_BR]=Nova janela +Name[ro]=Fereastră nouă +Name[ru]=Создать окно +Name[sk]=Nové okno +Name[sl]=Novo okno +Name[sr]=Нови прозор +Name[sr@latin]=Novi prozor +Name[sv]=Nytt fönster +Name[th]=หน้าต่างใหม่ +Name[tr]=Yeni Pencere +Name[uk]=Створити вікно +Name[vi]=Cửa sổ mới +Name[zh_CN]=新建窗口 +Name[zh_TW]=新增視窗 +Name=New Window +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=gedit org.gnome.gedit --new-window + +[Desktop Action new-document] +Name[af]=Nuwe dokument +Name[ar]=مستند جديد +Name[be]=Новы дакумент +Name[bg]=Нов документ +Name[bn_IN]=নতুন ডকুমেন্ট +Name[ca]=Crea un document nou +Name[ca@valencia]=Crea un document nou +Name[ckb]=بەڵگەنامەی نوێ +Name[cs]=Nový dokument +Name[da]=Nyt dokument +Name[de]=Neues Dokument +Name[el]=Νέο έγγραφο +Name[en_GB]=New Document +Name[eo]=Nova dokumento +Name[es]=Documento nuevo +Name[eu]=Dokumentu berria +Name[fa]=سند جدید +Name[fi]=Uusi asiakirja +Name[fr]=Nouveau document +Name[fur]=Gnûf document +Name[gd]=Sgrìobhainn ùr +Name[gl]=Novo documento +Name[he]=מסמך חדש +Name[hr]=Novi dokument +Name[hu]=Új dokumentum +Name[ia]=Nove documento +Name[id]=Dokumen Baru +Name[ie]=Nov document +Name[is]=Nýtt skjal +Name[it]=Nuovo documento +Name[ja]=新しいドキュメント +Name[ka]=ახალი დოკუმენტი +Name[kab]=Isemli amaynut +Name[kk]=Жаңа құжат +Name[ko]=새 문서 +Name[lt]=Naujas dokumentas +Name[lv]=Jauns dokuments +Name[mjw]=Kimi Document +Name[ml]=പൂതിയ രേഖ +Name[ms]=Dokumen Baharu +Name[nb]=Nytt dokument +Name[ne]=नयाँ कागजात +Name[nl]=Nieuw document +Name[oc]=Novèl document +Name[pa]=ਨਵਾਂ ਡੌਕੂਮੈਂਟ +Name[pl]=Nowy dokument +Name[pt]=Novo documento +Name[pt_BR]=Novo documento +Name[ro]=Document nou +Name[ru]=Создать документ +Name[sk]=Nový dokument +Name[sl]=Nov dokument +Name[sr]=Нови документ +Name[sr@latin]=Novi dokument +Name[sv]=Nytt dokument +Name[th]=เอกสารใหม่ +Name[tr]=Yeni Belge +Name[uk]=Створити документ +Name[vi]=Tài liệu mới +Name[zh_CN]=新建文档 +Name[zh_TW]=新增文件 +Name=New Document +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=gedit org.gnome.gedit --new-document diff --git a/modules/launchers/fixtures/sources/org.jellyfin.JellyfinDesktop.desktop b/modules/launchers/fixtures/sources/org.jellyfin.JellyfinDesktop.desktop new file mode 100644 index 0000000..67eca6c --- /dev/null +++ b/modules/launchers/fixtures/sources/org.jellyfin.JellyfinDesktop.desktop @@ -0,0 +1,28 @@ +[Desktop Entry] +Version=1.0 +Name=Jellyfin +Comment=Desktop client for Jellyfin +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop org.jellyfin.JellyfinDesktop +Icon=org.jellyfin.JellyfinDesktop +Terminal=false +Type=Application +StartupWMClass=org.jellyfin.JellyfinDesktop +Categories=AudioVideo;Video;Player;TV; +Actions=DesktopF;DesktopW;TVF;TVW +X-Flatpak=org.jellyfin.JellyfinDesktop + +[Desktop Action DesktopF] +Name=Desktop [Fullscreen] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop org.jellyfin.JellyfinDesktop --fullscreen --desktop + +[Desktop Action DesktopW] +Name=Desktop [Windowed] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop org.jellyfin.JellyfinDesktop --windowed --desktop + +[Desktop Action TVF] +Name=TV [Fullscreen] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop org.jellyfin.JellyfinDesktop --fullscreen --tv + +[Desktop Action TVW] +Name=TV [Windowed] +Exec=flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop org.jellyfin.JellyfinDesktop --windowed --tv diff --git a/modules/launchers/fixtures/sources/org.kde.krdc.desktop b/modules/launchers/fixtures/sources/org.kde.krdc.desktop new file mode 100644 index 0000000..38a01bc --- /dev/null +++ b/modules/launchers/fixtures/sources/org.kde.krdc.desktop @@ -0,0 +1,181 @@ +[Desktop Entry] +Type=Application +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=/app/bin/krdc --file-forwarding org.kde.krdc -qwindowtitle %c @@u %u @@ +Icon=org.kde.krdc +Terminal=false +Name=KRDC +Name[ar]=كردس +Name[az]=KRDC +Name[bg]=KRDC +Name[bs]=KRDC +Name[ca]=KRDC +Name[ca@valencia]=KRDC +Name[cs]=KRDC +Name[da]=KRDC +Name[de]=KRDC +Name[el]=KRDC +Name[en_GB]=KRDC +Name[eo]=KRDC +Name[es]=KRDC +Name[et]=KRDC +Name[eu]=KRDC +Name[fi]=KRDC +Name[fr]=KRDC +Name[ga]=KRDC +Name[gl]=KRDC +Name[he]=KRDC +Name[hne]=केआरडीसी +Name[hr]=KRDC +Name[hu]=KRDC +Name[ia]=KRDC +Name[is]=KRDC +Name[it]=KRDC +Name[ja]=KRDC +Name[ka]=KRDC +Name[kk]=KRDC +Name[km]=KRDC +Name[ko]=KRDC +Name[lt]=KRDC +Name[lv]=KRDC +Name[ml]=കെആര്‍ഡിസി +Name[mr]=के-आर-डी-सी +Name[nb]=KRDC +Name[nds]=KRDC +Name[nl]=KRDC +Name[nn]=KRDC +Name[pa]=KRDC +Name[pl]=KRDC +Name[pt]=KRDC +Name[pt_BR]=KRDC +Name[ro]=KRDC +Name[ru]=KRDC +Name[sa]=केआरडीसी +Name[si]=KRDC +Name[sk]=KRDC +Name[sl]=KRDC +Name[sq]=KRDC +Name[sr]=КРДЦ +Name[sr@ijekavian]=КРДЦ +Name[sr@ijekavianlatin]=KRDC +Name[sr@latin]=KRDC +Name[sv]=KRDC +Name[tr]=KRDC +Name[ug]=KRDC +Name[uk]=KRDC +Name[wa]=KRDC +Name[zh_CN]=KRDC +Name[zh_TW]=遠端桌面_KRDC +GenericName=Remote Desktop Client +GenericName[ar]=عميل سطح المكتب البعيد +GenericName[az]=İş masasının uzaqdan idarə olunması +GenericName[bg]=Отдалечен работен плот +GenericName[bs]=Klijent udaljene radne površine +GenericName[ca]=Client d'escriptori remot +GenericName[ca@valencia]=Client d'escriptori remot +GenericName[cs]=Klient vzdálené pracovní plochy +GenericName[da]=Klient til fjernskrivebord +GenericName[de]=Verbindung zu entferntem Rechner +GenericName[el]=Πελάτης απομακρυσμένης επιφάνειας εργασίας +GenericName[en_GB]=Remote Desktop Client +GenericName[eo]=Kliento por fora labortablo +GenericName[es]=Cliente de escritorio remoto +GenericName[et]=Kaugtöölaua klient +GenericName[eu]=Urruneko mahaigainaren bezeroa +GenericName[fi]=Etätyöpöytäasiakas +GenericName[fr]=Connexion à un bureau distant +GenericName[ga]=Cliant Deisce Cianda +GenericName[gl]=Cliente de escritorio remoto +GenericName[he]=לקוח שולחן עבודה מרוחק +GenericName[hne]=रिमोट डेस्कटाप क्लाएंट +GenericName[hr]=Klijent za udaljeno povezivanje +GenericName[hu]=Távoli asztal kliens +GenericName[ia]=Cliente de scriptorio remote +GenericName[is]=Tengingar við fjarlæg skjáborð +GenericName[it]=Client per connessione a desktop remoto +GenericName[ja]=リモートデスクトップクライアント +GenericName[ka]=დისტანციური სამუშაო მაგიდის კლიენტი +GenericName[kk]=Қашықтан үстелге қосылу клиенті +GenericName[km]=កម្មវិធី​មើល​ផ្ទៃតុ​ពី​ចម្ងាយ +GenericName[ko]=원격 데스크톱 클라이언트 +GenericName[lt]=Nuotolinio darbalaukio kliento programa +GenericName[lv]=Attālās darbvirsmas klients +GenericName[ml]=വിദൂര പണിയിട ക്ലയന്റ് +GenericName[mr]=दूरस्थ डेस्कटॉप ग्राहक +GenericName[nb]=Tilkobling til annet skrivebord +GenericName[nds]=Schriefdisch-Feernverbinnen +GenericName[nl]=Client voor verbinding met extern bureaublad +GenericName[nn]=Tilkopling til eksternt skrivebord +GenericName[pa]=ਰਿਮੋਟ ਡੈਸਕਟਾਪ ਕਲਾਈਟ +GenericName[pl]=Klient zdalnego dostępu do pulpitu +GenericName[pt]=Cliente de Ecrãs Remotos +GenericName[pt_BR]=Cliente de área de trabalho remota +GenericName[ro]=Client pentru birou la distanță +GenericName[ru]=Удалённый доступ к рабочему столу +GenericName[sa]=दूरस्थ डेस्कटॉप क्लायन्ट +GenericName[si]=දුරස්ථ වැඩතල යෙදුම +GenericName[sk]=Klient vzdialenej pracovnej plochy +GenericName[sl]=Odjemalec za oddaljena namizja +GenericName[sr]=Клијент удаљене површи +GenericName[sr@ijekavian]=Клијент удаљене површи +GenericName[sr@ijekavianlatin]=Klijent udaljene površi +GenericName[sr@latin]=Klijent udaljene površi +GenericName[sv]=Fjärranslutning till skrivbord +GenericName[th]=ไคลเอนต์เชื่อมต่อพื้นที่ทำงานทางไกล +GenericName[tr]=Uzak Masaüstü İstemcisi +GenericName[ug]=يىراقتىكى ئۈستەلئۈستى خېرىدارى +GenericName[uk]=Клієнт з'єднання з віддаленою стільницею +GenericName[zh_CN]=远程桌面客户端 +GenericName[zh_TW]=遠端桌面用戶端 +Comment=Connect with RDP or VNC to another computer +Comment[ar]=اتصل حاسوب آخر بواسطة RDP أو VNC +Comment[az]=RDP və ya VNC ilə başqa kompüterə qoşulun +Comment[bg]=Свързване чрез RDP or VNC с друг компютър +Comment[ca]=Connecta amb RDP o VNC a un altre ordinador +Comment[ca@valencia]=Connecta amb RDP o VNC a un altre ordinador +Comment[cs]=Připojte se k jinému počítači pomocí RDP nebo VNC +Comment[da]=Opret forbindelse til en anden computer med RDP eller VNC +Comment[de]=Verbindung mit RDP oder VNC zu einem anderen Rechner +Comment[el]=Σύνδεση με RDP ή VNC σε άλλον υπολογιστή +Comment[en_GB]=Connect with RDP or VNC to another computer +Comment[eo]=Konektiĝi per RDP aŭ VNC al alia komputilo +Comment[es]=Conectar con RDP o VNC a otro equipo +Comment[et]=Ühendumine RDP või VNC vahendusel teise arvutiga +Comment[eu]=Konektatu RDP edo VNC bidez beste ordenagailu batera +Comment[fi]=Yhdistä RDP:llä tai VNC:llä toiseen tietokoneeseen +Comment[fr]=Se connecter à un autre ordinateur avec RDP ou VNC +Comment[gl]=Conectarse por RDP ou VNC a outro computador. +Comment[he]=התחברות ל־RDP או ל־VNC במחשב אחר +Comment[hu]=Csatlakozás másik számítógéphez RDP-vel vagy VNC-vel +Comment[ia]=Connecte con RDP o VNC a un altere computator +Comment[is]=Tengstu með RDP eða VNC við aðrar tölvur +Comment[it]=Connessione via RDP o VNC ad un altro computer +Comment[ka]=მიუერთდით მოშორებულ კომპიუტერზე RDP ან VNC პროტოკოლით +Comment[ko]=다른 컴퓨터에 RDP나 VNC로 연결 +Comment[lt]=Prisijungti prie kito kompiuterio naudojant RDP ar VNC +Comment[lv]=Izmantojiet RDP vai VNC, lai pievienotos citam datoram +Comment[nl]=Met RDP of VNC naar een andere computer verbinden +Comment[nn]=Kopla til ei anna datamaskin via RDP eller VNC +Comment[pl]=Połącz się przy użyciu RDP lub VNC do innego komputera +Comment[pt]=Ligar-se com RDP ou VNC a outro computador +Comment[pt_BR]=Conecte com RDP ou VNC em outro computador +Comment[ro]=Conectare la alt calculator prin RDP sau VNC +Comment[ru]=Подключение к другому компьютеру по RDP или VNC +Comment[sa]=RDP अथवा VNC इत्यनेन सह अन्यसङ्गणकेन सह सम्बद्धं कुर्वन्तु +Comment[sk]=Pripojiť pomocou RDP alebo VNC k inému počítaču +Comment[sl]=Povežite se preko RDP-ja ali VNC-ja z drugim računalnikom +Comment[sq]=Lidhu me një kompjuter tjetër përmes RDP ose VNC +Comment[sr]=Повежите се РДП‑ом или ВНЦ‑ом са другим рачунаром +Comment[sr@ijekavian]=Повежите се РДП‑ом или ВНЦ‑ом са другим рачунаром +Comment[sr@ijekavianlatin]=Povežite se RDP‑om ili VNC‑om sa drugim računarom +Comment[sr@latin]=Povežite se RDP‑om ili VNC‑om sa drugim računarom +Comment[sv]=Anslut till en annan dator med RDP eller VNC +Comment[tr]=Başka bir bilgisayara RDP veya VNC ile bağlan +Comment[uk]=З'єднання з іншим комп'ютером за допомогою RDP або VNC +Comment[zh_CN]=通过 RDP 或 VNC 连接到另一台计算机 +Comment[zh_TW]=以 RDP 或 VNC 連線到其他電腦 +X-DocPath=krdc/index.html +StartupWMClass=krdc +MimeType=x-scheme-handler/vnc;x-scheme-handler/rdp;application/x-krdc; +Categories=Qt;KDE;Network;RemoteAccess; +X-KDE-Wayland-Interfaces=org_kde_kwin_keystate +X-Flatpak=org.kde.krdc diff --git a/modules/launchers/fixtures/sources/org.mozilla.firefox.desktop b/modules/launchers/fixtures/sources/org.mozilla.firefox.desktop new file mode 100644 index 0000000..45fad93 --- /dev/null +++ b/modules/launchers/fixtures/sources/org.mozilla.firefox.desktop @@ -0,0 +1,847 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox @@u %u @@ +Terminal=false +X-MultipleArgs=false +Icon=org.mozilla.firefox +StartupWMClass=firefox +DBusActivatable=false +Categories=GNOME;GTK;Network;WebBrowser; +MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto; +StartupNotify=true +Actions=new-window;new-private-window;open-profile-manager; +Name=Firefox +Name[ach]=Firefox +Name[af]=Firefox +Name[an]=Firefox +Name[ar]=Firefox +Name[ast]=Firefox +Name[az]=Firefox +Name[be]=Firefox +Name[bg]=Firefox +Name[bn]=Firefox +Name[br]=Firefox +Name[bs]=Firefox +Name[ca]=Firefox +Name[ca_valencia]=Firefox +Name[cak]=Firefox +Name[cs]=Firefox +Name[cy]=Firefox +Name[da]=Firefox +Name[de]=Firefox +Name[dsb]=Firefox +Name[el]=Firefox +Name[en_CA]=Firefox +Name[en_GB]=Firefox +Name[eo]=Firefox +Name[es_AR]=Firefox +Name[es_CL]=Firefox +Name[es_ES]=Firefox +Name[es_MX]=Firefox +Name[et]=Firefox +Name[eu]=Firefox +Name[fa]=Firefox +Name[ff]=Firefox +Name[fi]=Firefox +Name[fr]=Firefox +Name[fur]=Firefox +Name[fy_NL]=Firefox +Name[ga_IE]=Firefox +Name[gd]=Firefox +Name[gl]=Firefox +Name[gn]=Firefox +Name[gu_IN]=Firefox +Name[he]=Firefox +Name[hi_IN]=Firefox +Name[hr]=Firefox +Name[hsb]=Firefox +Name[hu]=Firefox +Name[hy_AM]=Firefox +Name[ia]=Firefox +Name[id]=Firefox +Name[is]=Firefox +Name[it]=Firefox +Name[ja]=Firefox +Name[ka]=Firefox +Name[kab]=Firefox +Name[kk]=Firefox +Name[km]=Firefox +Name[kn]=Firefox +Name[ko]=Firefox +Name[lij]=Firefox +Name[lt]=Firefox +Name[lv]=Firefox +Name[mk]=Firefox +Name[mr]=Firefox +Name[ms]=Firefox +Name[my]=Firefox +Name[nb_NO]=Firefox +Name[ne_NP]=Firefox +Name[nl]=Firefox +Name[nn_NO]=Firefox +Name[oc]=Firefox +Name[pa_IN]=Firefox +Name[pl]=Firefox +Name[pt_BR]=Firefox +Name[pt_PT]=Firefox +Name[rm]=Firefox +Name[ro]=Firefox +Name[ru]=Firefox +Name[sat]=Firefox +Name[sc]=Firefox +Name[sco]=Firefox +Name[si]=Firefox +Name[sk]=Firefox +Name[skr]=Firefox +Name[sl]=Firefox +Name[son]=Firefox +Name[sq]=Firefox +Name[sr]=Firefox +Name[sv_SE]=Firefox +Name[szl]=Firefox +Name[ta]=Firefox +Name[te]=Firefox +Name[tg]=Firefox +Name[th]=Firefox +Name[tl]=Firefox +Name[tr]=Firefox +Name[trs]=Firefox +Name[uk]=Firefox +Name[ur]=Firefox +Name[uz]=Firefox +Name[vi]=Firefox +Name[xh]=Firefox +Name[zh_CN]=Firefox +Name[zh_TW]=Firefox +Comment=Fast and private browser +Comment[ach]=Fast and private browser +Comment[af]=Fast and private browser +Comment[an]=Fast and private browser +Comment[ar]=متصفح سريع وخاص +Comment[ast]=Fast and private browser +Comment[az]=Fast and private browser +Comment[be]=Хуткі і прыватны браўзер +Comment[bg]=Fast and private browser +Comment[bn]=Fast and private browser +Comment[br]=Fast and private browser +Comment[bs]=Fast and private browser +Comment[ca]=Navegador ràpid i privat +Comment[ca_valencia]=Fast and private browser +Comment[cak]=Fast and private browser +Comment[cs]=Rychlý a soukromý prohlížeč +Comment[cy]=Porwr cyflym a phreifat +Comment[da]=Hurtig og privat browser +Comment[de]=Schneller und privater Browser +Comment[dsb]=Malsny a priwatny wobglědowak +Comment[el]=Γρήγορο και ιδιωτικό πρόγραμμα περιήγησης +Comment[en_CA]=Fast and private browser +Comment[en_GB]=Fast and private browser +Comment[eo]=Rapida kaj privata retumilo +Comment[es_AR]=Navegador rápido y privado +Comment[es_CL]=Navegador rápido y privado +Comment[es_ES]=Navegador rápido y privado +Comment[es_MX]=Navegación web rápida y privada +Comment[et]=Fast and private browser +Comment[eu]=Nabigatzaile bizkor eta pribatua +Comment[fa]=Fast and private browser +Comment[ff]=Fast and private browser +Comment[fi]=Nopea ja yksityinen selain +Comment[fr]=Navigateur rapide et privé +Comment[fur]=Navigadôr svelt e riservât +Comment[fy_NL]=Flugge en priveebrowser +Comment[ga_IE]=Fast and private browser +Comment[gd]=Fast and private browser +Comment[gl]=Fast and private browser +Comment[gn]=Kundahára ipya’e ha hekoñemíva +Comment[gu_IN]=Fast and private browser +Comment[he]=דפדפן מהיר ופרטי +Comment[hi_IN]=Fast and private browser +Comment[hr]=Brz i privatan preglednik +Comment[hsb]=Spěšny a priwatny wobhladowak +Comment[hu]=Gyors és privát böngésző +Comment[hy_AM]=Արագ և գաղտնի դիտարկիչ +Comment[ia]=Navigator veloce e private +Comment[id]=Fast and private browser +Comment[is]=Hraður og persónulegur vafri +Comment[it]=Browser veloce e riservato +Comment[ja]=高速プライベートブラウザー +Comment[ka]=სწრაფი და პირადი ბრაუზერი +Comment[kab]=Iminig arurad, uslig +Comment[kk]=Жылдам және жеке браузер +Comment[km]=Fast and private browser +Comment[kn]=Fast and private browser +Comment[ko]=빠르고 사생활을 보호하는 브라우저 +Comment[lij]=Fast and private browser +Comment[lt]=Fast and private browser +Comment[lv]=Fast and private browser +Comment[mk]=Fast and private browser +Comment[mr]=Fast and private browser +Comment[ms]=Fast and private browser +Comment[my]=Fast and private browser +Comment[nb_NO]=Rask og privat nettleser +Comment[ne_NP]=Fast and private browser +Comment[nl]=Snelle en privébrowser +Comment[nn_NO]=Rask og privat nettlesar +Comment[oc]=Fast and private browser +Comment[pa_IN]=ਤੇਜ਼ ਅਤੇ ਪ੍ਰਾਈਵੇਟ ਬਰਾਊਜ਼ਰ +Comment[pl]=Szybka i prywatna przeglądarka +Comment[pt_BR]=Navegador rápido e privativo +Comment[pt_PT]=Navegador rápido e privado +Comment[rm]=Navigatur svelt e privat +Comment[ro]=Browser rapid și privat +Comment[ru]=Быстрый и приватный браузер +Comment[sat]=Fast and private browser +Comment[sc]=Navigadore lestru e privadu +Comment[sco]=Fast and private browser +Comment[si]=Fast and private browser +Comment[sk]=Rýchly a súkromný prehliadač +Comment[skr]=Fast and private browser +Comment[sl]=Hiter in zaseben brskalnik +Comment[son]=Fast and private browser +Comment[sq]=Shfletues i shpejtë dhe privat +Comment[sr]=Брз и приватан веб прегледач +Comment[sv_SE]=Snabb och privat webbläsare +Comment[szl]=Fast and private browser +Comment[ta]=Fast and private browser +Comment[te]=Fast and private browser +Comment[tg]=Браузери тезкор ва хусусӣ +Comment[th]=เบราว์เซอร์ที่รวดเร็วและเป็นส่วนตัว +Comment[tl]=Fast and private browser +Comment[tr]=Hızlı ve gizli tarayıcı +Comment[trs]=Fast and private browser +Comment[uk]=Швидкий і приватний браузер +Comment[ur]=Fast and private browser +Comment[uz]=Fast and private browser +Comment[vi]=Trình duyệt nhanh và riêng tư +Comment[xh]=Fast and private browser +Comment[zh_CN]=快速且私密的浏览器 +Comment[zh_TW]=快速又有隱私的瀏覽器 +GenericName=Web Browser +GenericName[ach]=Web Browser +GenericName[af]=Web Browser +GenericName[an]=Web Browser +GenericName[ar]=متصفح الوِب +GenericName[ast]=Web Browser +GenericName[az]=Web Browser +GenericName[be]=Вэб-браўзер +GenericName[bg]=Уеб браузър +GenericName[bn]=ওয়েব ব্রাউজার +GenericName[br]=Merdeer Web +GenericName[bs]=Web pretraživač +GenericName[ca]=Navegador web +GenericName[ca_valencia]=Web Browser +GenericName[cak]=Web Okik'amaya'l +GenericName[cs]=Webový prohlížeč +GenericName[cy]=Porwr Gwe +GenericName[da]=Webbrowser +GenericName[de]=Internet-Browser +GenericName[dsb]=Webwobglědowak +GenericName[el]=Πρόγραμμα περιήγησης +GenericName[en_CA]=Web Browser +GenericName[en_GB]=Web Browser +GenericName[eo]=Retumilo +GenericName[es_AR]=Navegador web +GenericName[es_CL]=Navegador Web +GenericName[es_ES]=Navegador web +GenericName[es_MX]=Navegador Web +GenericName[et]=Web Browser +GenericName[eu]=Web nabigatzailea +GenericName[fa]=مرورگر وب +GenericName[ff]=Web Browser +GenericName[fi]=Verkkoselain +GenericName[fr]=Navigateur web +GenericName[fur]=Navigadôr Web +GenericName[fy_NL]=Webbrowser +GenericName[ga_IE]=Web Browser +GenericName[gd]=Brabhsair-lìn +GenericName[gl]=Navegador web +GenericName[gn]=Ñanduti Kundahára +GenericName[gu_IN]=Web Browser +GenericName[he]=דפדפן אינטרנט +GenericName[hi_IN]=Web Browser +GenericName[hr]=Web preglednik +GenericName[hsb]=Webwobhladowak +GenericName[hu]=Webböngésző +GenericName[hy_AM]=Վեբ դիտարկիչ +GenericName[ia]=Navigator web +GenericName[id]=Peramban Web +GenericName[is]=Vafri +GenericName[it]=Browser web +GenericName[ja]=ウェブブラウザー +GenericName[ka]=ბრაუზერი +GenericName[kab]=Iminig web +GenericName[kk]=Веб-браузері +GenericName[km]=Web Browser +GenericName[kn]=Web Browser +GenericName[ko]=웹 브라우저 +GenericName[lij]=Navegatô Web +GenericName[lt]=Web Browser +GenericName[lv]=Tīmekļa pārlūks +GenericName[mk]=Web Browser +GenericName[mr]=Web Browser +GenericName[ms]=Web Browser +GenericName[my]=Web Browser +GenericName[nb_NO]=Nettleser +GenericName[ne_NP]=वेब ब्राउजर +GenericName[nl]=Webbrowser +GenericName[nn_NO]=Nettlesar +GenericName[oc]=Navegador Web +GenericName[pa_IN]=ਵੈੱਬ ਬਰਾਊਜ਼ਰ +GenericName[pl]=Przeglądarka internetowa +GenericName[pt_BR]=Navegador web +GenericName[pt_PT]=Navegador Web +GenericName[rm]=Navigatur web +GenericName[ro]=Browser web +GenericName[ru]=Веб-браузер +GenericName[sat]=ᱣᱮᱵᱽ ᱵᱽᱨᱟᱣᱡᱚᱨ +GenericName[sc]=Navigadore web +GenericName[sco]=Web Browser +GenericName[si]=වියමන අතිරික්සුව +GenericName[sk]=Webový prehliadač +GenericName[skr]=ویب براؤزر +GenericName[sl]=Spletni brskalnik +GenericName[son]=Web Browser +GenericName[sq]=Shfletues +GenericName[sr]=Веб прегледач +GenericName[sv_SE]=Webbläsare +GenericName[szl]=Web Browser +GenericName[ta]=Web Browser +GenericName[te]=జాల విహారిణి +GenericName[tg]=Браузери веб +GenericName[th]=เว็บเบราว์เซอร์ +GenericName[tl]=Web Browser +GenericName[tr]=Web Tarayıcısı +GenericName[trs]=Web riña gāchē nu’ +GenericName[uk]=Браузер +GenericName[ur]=Web Browser +GenericName[uz]=Web Browser +GenericName[vi]=Trình duyệt web +GenericName[xh]=Web Browser +GenericName[zh_CN]=Web 浏览器 +GenericName[zh_TW]=網頁瀏覽器 +Keywords=Internet;WWW;Browser;Web;Explorer; +Keywords[ach]=Internet;WWW;Browser;Web;Explorer; +Keywords[af]=Internet;WWW;Browser;Web;Explorer; +Keywords[an]=Internet;WWW;Browser;Web;Explorer; +Keywords[ar]=إنترنت;WWW;متصفح;ويب;مستكشف; +Keywords[ast]=Internet;WWW;Browser;Web;Explorer; +Keywords[az]=Internet;WWW;Browser;Web;Explorer; +Keywords[be]=Internet;WWW;Browser;Web;Explorer; +Keywords[bg]=Internet;WWW;Browser;Web;Explorer; +Keywords[bn]=ইন্টারনেট;WWW;ব্রাউজার;ওয়েব;এক্সপ্লোরার; +Keywords[br]=Internet;WWW;Merdeer;Web;Ergerzhout; +Keywords[bs]=Internet;WWW;Pretraživač;Web;Istraživač; +Keywords[ca]=Internet;WWW;Browser;Web;Explorador;Navegador; +Keywords[ca_valencia]=Internet;WWW;Browser;Web;Explorer; +Keywords[cak]=K'amaya'l;WWW;Okik'amaya'l;Kanob'äl; +Keywords[cs]=internet;WWW;prohlížeč;web; +Keywords[cy]=Rhyngrwyd;WWW;Porwr;Gwe;Archwiliwr; +Keywords[da]=Internet;WWW;Browser;Nettet;Explorer; +Keywords[de]=Internet;WWW;Browser;Web;Explorer; +Keywords[dsb]=Internet;WWW;wobglědowak;Web;Explorer; +Keywords[el]=Internet;WWW;Browser;Web;Explorer;Διαδίκτυο;Ιστός;Ίντερνετ; +Keywords[en_CA]=Internet;WWW;Browser;Web;Explorer; +Keywords[en_GB]=Internet;WWW;Browser;Web;Explorer; +Keywords[eo]=Interreto;Retumilo;TTT;Teksaĵo;Reto;Internet;WWW;Browser;Web;Explorer; +Keywords[es_AR]=Internet;WWW;Navegador;Web;Explorador; +Keywords[es_CL]=Internet;WWW;Navegador;Web;Explorador; +Keywords[es_ES]=Internet;WWW;Navegador;Web;Explorador; +Keywords[es_MX]=Internet;WWW;Navegador;Web;Explorador; +Keywords[et]=Internet;WWW;Browser;Web;Explorer; +Keywords[eu]=Internet;WWW;Nabigatzailea;Web;Arakatzailea; +Keywords[fa]=Internet;WWW;Browser;Web;Explorer; +Keywords[ff]=Internet;WWW;Browser;Web;Explorer; +Keywords[fi]=Internet;WWW;Browser;Web;Explorer;netti;webbi;selain; +Keywords[fr]=Internet;WWW;Navigateur;Web;Explorer; +Keywords[fur]=Internet;WWW;Browser;Navigadôr;Web;Esploradôr;Explorer; +Keywords[fy_NL]=Ynternet;WWW;Browser;Web;Ferkenner; +Keywords[ga_IE]=Internet;WWW;Browser;Web;Explorer; +Keywords[gd]=Internet;WWW;Browser;Web;Explorer;eadar-lìon;brabhsair;brobhsair;lìon;taisgealaiche; +Keywords[gl]=Internet;WWW;Navegador;Web;Explorador; +Keywords[gn]=Internet;WWW;Browser;Web;Explorer; +Keywords[gu_IN]=Internet;WWW;Browser;Web;Explorer; +Keywords[he]=אינטרנט;WWW;דפדפן;רשת;סייר;מרשתת; +Keywords[hi_IN]=Internet;WWW;Browser;Web;Explorer; +Keywords[hr]=Internet;WWW;Preglednik;Web;Istraživač; +Keywords[hsb]=Internet;WWW;wobhladowak;Web;Explorer; +Keywords[hu]=Internet;WWW;Böngésző;Web;Világháló; +Keywords[hy_AM]=Համացանց,WWW,Զննիչ,Վեբ,Ցանցախույզ: +Keywords[ia]=Internet;WWW;Navigator;Web;Explorator; +Keywords[id]=Internet;WWW;Browser;Web;Explorer; +Keywords[is]=Internet;WWW; Vafri; Vefur; Explorer; +Keywords[it]=Internet;WWW;Browser;Web;Explorer;Navigatore; +Keywords[ja]=Internet;WWW;Browser;Web;Explorer;インターネット;ブラウザー;ウェブ; +Keywords[ka]=ინტერნეტი;WWW;ბრაუზერი;ქსელი;ქსელთან წვდომა; +Keywords[kab]=Internet;WWW;Browser;Web;Explorer; +Keywords[kk]=Internet;WWW;Browser;Web;Explorer;Интернет;Ғаламтор;Браузер;Желі;Шолғыш; +Keywords[km]=Internet;WWW;Browser;Web;Explorer; +Keywords[kn]=Internet;WWW;Browser;Web;Explorer; +Keywords[ko]=인터넷;브라우저;웹;탐색기;Internet;WWW;Browser;Web;Explorer; +Keywords[lij]=Internet;WWW;Browser;Web;Explorer;Navegatô; +Keywords[lt]=Internet;WWW;Browser;Web;Explorer; +Keywords[lv]=Internets;WWW;Pārlūkprogramma;Tīmeklis; +Keywords[mk]=Internet;WWW;Browser;Web;Explorer; +Keywords[mr]=Internet;WWW;Browser;Web;Explorer; +Keywords[ms]=Internet;WWW;Browser;Web;Explorer; +Keywords[my]=Internet;WWW;Browser;Web;Explorer; +Keywords[nb_NO]=Internett;WWW;Nettleser;Web;Utforsker; +Keywords[ne_NP]=Internet;WWW;Browser;Web;Explorer; +Keywords[nl]=Internet;WWW;Browser;Web;Verkenner; +Keywords[nn_NO]=Internett;WWW;Nettlesar;Web;Utforskar; +Keywords[oc]=Internet;WWW;Navegador;Navigador;Navegator;Navigator;Web;Explorer; +Keywords[pa_IN]=ਇੰਟਰਨੈੱਟ;WWW;ਬਰਾਊਜ਼ਰ;ਵੈੱਬ;ਐਕਸਪਲਰੋਰ;ਵੈਬ;ਇੰਟਰਨੈਟ; +Keywords[pl]=Internet;WWW;Przeglądarka;Browser;Wyszukiwarka;Web;Sieć;Explorer;Eksplorer;Strony;Witryny;internetowe; +Keywords[pt_BR]=Internet;WWW;Browser;Web;Explorer;Navegador; +Keywords[pt_PT]=Internet;WWW;Navegador;Web;Explorador; +Keywords[rm]=Internet;WWW;Browser;Web;Explorer;navigatur; +Keywords[ro]=Internet; WWW; Browser; Web; Explorer; +Keywords[ru]=Сеть;Интернет;Браузер;Доступ в Интернет; +Keywords[sat]=Internet;WWW;Browser;Web;Explorer; +Keywords[sc]=Internet;WWW;Navigadore;Web;Explorer; +Keywords[sco]=Internet;WWW;Browser;Web;Explorer; +Keywords[si]=අන්තර්ජාලය;අතිරික්සුව;පිරික්සන්න;ගවේශකය;Internet;WWW;Browser;Web;Explorer; +Keywords[sk]=Internet;WWW;Prehliadač;Web;Prieskumník; +Keywords[skr]=Internet;WWW;Browser;Web;Explorer; +Keywords[sl]=internet;www;brskalnik;splet; +Keywords[son]=Internet;WWW;Browser;Web;Explorer; +Keywords[sq]=Internet;WWW;Shfletues;Web;Eksplorues; +Keywords[sr]=Internet;WWW;Browser;Web;Explorer;интернет;њњњ;веб;мрежа;прегледач;експлорер;internet;pregledač;veb;mreža;pregledač;eksplorer; +Keywords[sv_SE]=Internet;WWW;Webbläsare;Webb;Utforskare; +Keywords[szl]=Internet;WWW;Browser;Web;Explorer; +Keywords[ta]=Internet;WWW;Browser;Web;Explorer; +Keywords[te]=Internet;WWW;Browser;Web;Explorer; +Keywords[tg]=Интернет;WWW;Браузер;Сомона;Ҷустуҷӯгар; +Keywords[th]=อินเทอร์เน็ต;เบราว์เซอร์;เว็บ;Internet;WWW;Browser;Web;Explorer; +Keywords[tl]=Internet;WWW;Browser;Web;Explorer; +Keywords[tr]=Internet;WWW;Browser;Web;Explorer;İnternet;Tarayıcı; +Keywords[trs]=Internet;WWW;Browser;Web;Explorer; +Keywords[uk]=Інтернет;WWW;Браузер;Веб;Переглядач; +Keywords[ur]=Internet;WWW;Browser;Web;Explorer; +Keywords[uz]=Internet;WWW;Browser;Web;Explorer; +Keywords[vi]=Internet;WWW;Trình duyệt;Web;Duyệt web; +Keywords[xh]=Internet;WWW;Browser;Web;Explorer; +Keywords[zh_CN]=Internet;WWW;Browser;Web;Explorer; +Keywords[zh_TW]=網際網路;網路;瀏覽器;網頁;上網;Internet;WWW;Browser;Web;Explorer; +X-GNOME-FullName=Mozilla Firefox +X-GNOME-FullName[ach]=Mozilla Firefox +X-GNOME-FullName[af]=Mozilla Firefox +X-GNOME-FullName[an]=Mozilla Firefox +X-GNOME-FullName[ar]=Mozilla Firefox +X-GNOME-FullName[ast]=Mozilla Firefox +X-GNOME-FullName[az]=Mozilla Firefox +X-GNOME-FullName[be]=Mozilla Firefox +X-GNOME-FullName[bg]=Mozilla Firefox +X-GNOME-FullName[bn]=Mozilla Firefox +X-GNOME-FullName[br]=Mozilla Firefox +X-GNOME-FullName[bs]=Mozilla Firefox +X-GNOME-FullName[ca]=Mozilla Firefox +X-GNOME-FullName[ca_valencia]=Mozilla Firefox +X-GNOME-FullName[cak]=Mozilla Firefox +X-GNOME-FullName[cs]=Mozilla Firefox +X-GNOME-FullName[cy]=Mozilla Firefox +X-GNOME-FullName[da]=Mozilla Firefox +X-GNOME-FullName[de]=Mozilla Firefox +X-GNOME-FullName[dsb]=Mozilla Firefox +X-GNOME-FullName[el]=Mozilla Firefox +X-GNOME-FullName[en_CA]=Mozilla Firefox +X-GNOME-FullName[en_GB]=Mozilla Firefox +X-GNOME-FullName[eo]=Mozilla Firefox +X-GNOME-FullName[es_AR]=Mozilla Firefox +X-GNOME-FullName[es_CL]=Mozilla Firefox +X-GNOME-FullName[es_ES]=Mozilla Firefox +X-GNOME-FullName[es_MX]=Mozilla Firefox +X-GNOME-FullName[et]=Mozilla Firefox +X-GNOME-FullName[eu]=Mozilla Firefox +X-GNOME-FullName[fa]=Mozilla Firefox +X-GNOME-FullName[ff]=Mozilla Firefox +X-GNOME-FullName[fi]=Mozilla Firefox +X-GNOME-FullName[fr]=Mozilla Firefox +X-GNOME-FullName[fur]=Mozilla Firefox +X-GNOME-FullName[fy_NL]=Mozilla Firefox +X-GNOME-FullName[ga_IE]=Mozilla Firefox +X-GNOME-FullName[gd]=Mozilla Firefox +X-GNOME-FullName[gl]=Mozilla Firefox +X-GNOME-FullName[gn]=Mozilla Firefox +X-GNOME-FullName[gu_IN]=Mozilla Firefox +X-GNOME-FullName[he]=Mozilla Firefox +X-GNOME-FullName[hi_IN]=Mozilla Firefox +X-GNOME-FullName[hr]=Mozilla Firefox +X-GNOME-FullName[hsb]=Mozilla Firefox +X-GNOME-FullName[hu]=Mozilla Firefox +X-GNOME-FullName[hy_AM]=Mozilla Firefox +X-GNOME-FullName[ia]=Mozilla Firefox +X-GNOME-FullName[id]=Mozilla Firefox +X-GNOME-FullName[is]=Mozilla Firefox +X-GNOME-FullName[it]=Mozilla Firefox +X-GNOME-FullName[ja]=Mozilla Firefox +X-GNOME-FullName[ka]=Mozilla Firefox +X-GNOME-FullName[kab]=Mozilla Firefox +X-GNOME-FullName[kk]=Mozilla Firefox +X-GNOME-FullName[km]=Mozilla Firefox +X-GNOME-FullName[kn]=Mozilla Firefox +X-GNOME-FullName[ko]=Mozilla Firefox +X-GNOME-FullName[lij]=Mozilla Firefox +X-GNOME-FullName[lt]=Mozilla Firefox +X-GNOME-FullName[lv]=Mozilla Firefox +X-GNOME-FullName[mk]=Mozilla Firefox +X-GNOME-FullName[mr]=Mozilla Firefox +X-GNOME-FullName[ms]=Mozilla Firefox +X-GNOME-FullName[my]=Mozilla Firefox +X-GNOME-FullName[nb_NO]=Mozilla Firefox +X-GNOME-FullName[ne_NP]=Mozilla Firefox +X-GNOME-FullName[nl]=Mozilla Firefox +X-GNOME-FullName[nn_NO]=Mozilla Firefox +X-GNOME-FullName[oc]=Mozilla Firefox +X-GNOME-FullName[pa_IN]=Mozilla Firefox +X-GNOME-FullName[pl]=Mozilla Firefox +X-GNOME-FullName[pt_BR]=Mozilla Firefox +X-GNOME-FullName[pt_PT]=Mozilla Firefox +X-GNOME-FullName[rm]=Mozilla Firefox +X-GNOME-FullName[ro]=Mozilla Firefox +X-GNOME-FullName[ru]=Mozilla Firefox +X-GNOME-FullName[sat]=Mozilla Firefox +X-GNOME-FullName[sc]=Mozilla Firefox +X-GNOME-FullName[sco]=Mozilla Firefox +X-GNOME-FullName[si]=Mozilla Firefox +X-GNOME-FullName[sk]=Mozilla Firefox +X-GNOME-FullName[skr]=Mozilla Firefox +X-GNOME-FullName[sl]=Mozilla Firefox +X-GNOME-FullName[son]=Mozilla Firefox +X-GNOME-FullName[sq]=Mozilla Firefox +X-GNOME-FullName[sr]=Mozilla Firefox +X-GNOME-FullName[sv_SE]=Mozilla Firefox +X-GNOME-FullName[szl]=Mozilla Firefox +X-GNOME-FullName[ta]=Mozilla Firefox +X-GNOME-FullName[te]=Mozilla Firefox +X-GNOME-FullName[tg]=Mozilla Firefox +X-GNOME-FullName[th]=Mozilla Firefox +X-GNOME-FullName[tl]=Mozilla Firefox +X-GNOME-FullName[tr]=Mozilla Firefox +X-GNOME-FullName[trs]=Mozilla Firefox +X-GNOME-FullName[uk]=Mozilla Firefox +X-GNOME-FullName[ur]=Mozilla Firefox +X-GNOME-FullName[uz]=Mozilla Firefox +X-GNOME-FullName[vi]=Mozilla Firefox +X-GNOME-FullName[xh]=Mozilla Firefox +X-GNOME-FullName[zh_CN]=Mozilla Firefox +X-GNOME-FullName[zh_TW]=Mozilla Firefox +X-Flatpak=org.mozilla.firefox + +[Desktop Action new-window] +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox --new-window @@u %u @@ +Name=New Window +Name[ach]=New Window +Name[af]=New Window +Name[an]=New Window +Name[ar]=نافذة جديدة +Name[ast]=New Window +Name[az]=New Window +Name[be]=Новае акно +Name[bg]=Нов прозорец +Name[bn]=নতুন উইন্ডো +Name[br]=Prenestr nevez +Name[bs]=Novi prozor +Name[ca]=Finestra nova +Name[ca_valencia]=New Window +Name[cak]=K'ak'a' Tzuwäch +Name[cs]=Nové okno +Name[cy]=Ffenestr Newydd +Name[da]=Nyt vindue +Name[de]=Neues Fenster +Name[dsb]=Nowe wokno +Name[el]=Νέο παράθυρο +Name[en_CA]=New Window +Name[en_GB]=New Window +Name[eo]=Nova fenestro +Name[es_AR]=Nueva ventana +Name[es_CL]=Nueva ventana +Name[es_ES]=Nueva ventana +Name[es_MX]=Nueva ventana +Name[et]=New Window +Name[eu]=Leiho berria +Name[fa]=پنجره جدید‌ +Name[ff]=New Window +Name[fi]=Uusi ikkuna +Name[fr]=Nouvelle fenêtre +Name[fur]=Gnûf barcon +Name[fy_NL]=Nij finster +Name[ga_IE]=New Window +Name[gd]=Uinneag ùr +Name[gl]=Nova xanela +Name[gn]=Ovetã pyahu +Name[gu_IN]=New Window +Name[he]=חלון חדש +Name[hi_IN]=New Window +Name[hr]=Novi prozor +Name[hsb]=Nowe wokno +Name[hu]=Új ablak +Name[hy_AM]=Նոր պատուհան +Name[ia]=Nove fenestra +Name[id]=Jendela Baru +Name[is]=Nýr gluggi +Name[it]=Nuova finestra +Name[ja]=新しいウィンドウ +Name[ka]=ახალი ფანჯარა +Name[kab]=Asfaylu amaynut +Name[kk]=Жаңа терезе +Name[km]=បង្អួច​​​ថ្មី +Name[kn]=New Window +Name[ko]=새 창 +Name[lij]=Neuvo Barcon +Name[lt]=New Window +Name[lv]=Jauns logs +Name[mk]=New Window +Name[mr]=New Window +Name[ms]=New Window +Name[my]=New Window +Name[nb_NO]=Nytt vindu +Name[ne_NP]=नयाँ सञ्झ्याल +Name[nl]=Nieuw venster +Name[nn_NO]=Nytt vindauge +Name[oc]=Fenèstra novèla +Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ +Name[pl]=Nowe okno +Name[pt_BR]=Nova janela +Name[pt_PT]=Nova janela +Name[rm]=Nova fanestra +Name[ro]=Fereastră nouă +Name[ru]=Новое окно +Name[sat]=ᱱᱟᱶᱟ ᱣᱤᱱᱰᱳ +Name[sc]=Ventana noa +Name[sco]=New Window +Name[si]=නව කවුළුව +Name[sk]=Nové okno +Name[skr]=نویں ونڈو +Name[sl]=Novo okno +Name[son]=New Window +Name[sq]=Dritare e Re +Name[sr]=Нови прозор +Name[sv_SE]=Nytt fönster +Name[szl]=New Window +Name[ta]=New Window +Name[te]=కొత్త కిటికీ +Name[tg]=Равзанаи нав +Name[th]=หน้าต่างใหม่ +Name[tl]=New Window +Name[tr]=Yeni pencere +Name[trs]=Bēntanâ nākàa +Name[uk]=Нове вікно +Name[ur]=New Window +Name[uz]=New Window +Name[vi]=Cửa sổ mới +Name[xh]=New Window +Name[zh_CN]=新建窗口 +Name[zh_TW]=開新視窗 + +[Desktop Action new-private-window] +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox --file-forwarding org.mozilla.firefox --private-window @@u %u @@ +Name=New Private Window +Name[ach]=New Private Window +Name[af]=New Private Window +Name[an]=New Private Window +Name[ar]=نافذة خاصة جديدة +Name[ast]=New Private Window +Name[az]=New Private Window +Name[be]=Новае прыватнае акно +Name[bg]=Нов личен прозорец +Name[bn]=নতুন ব্যক্তিগত উইন্ডো +Name[br]=Prenestr prevez nevez +Name[bs]=Novi privatni prozor +Name[ca]=Finestra privada nova +Name[ca_valencia]=New Private Window +Name[cak]=K'ak'a' Ichinan Tzuwäch +Name[cs]=Nové anonymní okno +Name[cy]=Ffenestr Breifat Newydd +Name[da]=Nyt privat vindue +Name[de]=Neues privates Fenster +Name[dsb]=Nowe priwatne wokno +Name[el]=Νέο ιδιωτικό παράθυρο +Name[en_CA]=New Private Window +Name[en_GB]=New Private Window +Name[eo]=Nova privata fenestro +Name[es_AR]=Nueva ventana privada +Name[es_CL]=Nueva ventana privada +Name[es_ES]=Nueva ventana privada +Name[es_MX]=Nueva ventana privada +Name[et]=New Private Window +Name[eu]=Leiho pribatu berria +Name[fa]=پنجره ناشناس جدید +Name[ff]=New Private Window +Name[fi]=Uusi yksityinen ikkuna +Name[fr]=Nouvelle fenêtre privée +Name[fur]=Gnûf barcon privât +Name[fy_NL]=Nij priveefinster +Name[ga_IE]=New Private Window +Name[gd]=Uinneag phrìobhaideach ùr +Name[gl]=Nova xanela privada +Name[gn]=Ovetã ñemi pyahu +Name[gu_IN]=New Private Window +Name[he]=חלון פרטי חדש +Name[hi_IN]=New Private Window +Name[hr]=Novi privatni prozor +Name[hsb]=Nowe priwatne wokno +Name[hu]=Új privát ablak +Name[hy_AM]=Նոր գաղտնի պատուհան +Name[ia]=Nove fenestra private +Name[id]=Jendela Mode Pribadi Baru +Name[is]=Nýr huliðsgluggi +Name[it]=Nuova finestra anonima +Name[ja]=新しいプライベートウィンドウ +Name[ka]=ახალი პირადი ფანჯარა +Name[kab]=Asfaylu amaynut n tunigin tusligt +Name[kk]=Жаңа жекелік терезе +Name[km]=បង្អួច​ឯកជន​ថ្មី +Name[kn]=New Private Window +Name[ko]=새 사생활 보호 창 +Name[lij]=Neuvo Barcon Privòu +Name[lt]=New Private Window +Name[lv]=Jauns privātais logs +Name[mk]=New Private Window +Name[mr]=New Private Window +Name[ms]=New Private Window +Name[my]=New Private Window +Name[nb_NO]=Nytt privat vindu +Name[ne_NP]=नयाँ निजी सञ्झ्याल +Name[nl]=Nieuw privévenster +Name[nn_NO]=Nytt privat vindauge +Name[oc]=Fenèstra privada novèla +Name[pa_IN]=ਨਵੀਂ ਪ੍ਰਾਈਵੇਟ ਵਿੰਡੋ +Name[pl]=Nowe okno prywatne +Name[pt_BR]=Nova janela privativa +Name[pt_PT]=Nova janela privada +Name[rm]=Nova fanestra privata +Name[ro]=Fereastră privată nouă +Name[ru]=Новое приватное окно +Name[sat]=ᱱᱟᱶᱟ ᱱᱤᱡᱮᱨᱟᱜ ᱣᱤᱱᱰᱳ +Name[sc]=Ventana privada noa +Name[sco]=New Private Window +Name[si]=නව පෞද්. කවුළුව +Name[sk]=Nové súkromné okno +Name[skr]=نویں نجی ونڈو +Name[sl]=Novo zasebno okno +Name[son]=New Private Window +Name[sq]=Dritare e Re Private +Name[sr]=Нови приватни прозор +Name[sv_SE]=Nytt privat fönster +Name[szl]=New Private Window +Name[ta]=New Private Window +Name[te]=కొత్త ఆంతరంగిక కిటికీ +Name[tg]=Равзанаи хусусии нав +Name[th]=หน้าต่างส่วนตัวใหม่ +Name[tl]=New Private Window +Name[tr]=Yeni gizli pencere +Name[trs]=Bēntanâ huì nākàa +Name[uk]=Приватне вікно +Name[ur]=New Private Window +Name[uz]=New Private Window +Name[vi]=Cửa sổ riêng tư mới +Name[xh]=New Private Window +Name[zh_CN]=新建隐私窗口 +Name[zh_TW]=開新隱私視窗 + +[Desktop Action open-profile-manager] +Exec=/usr/bin/flatpak run --branch=stable --arch=aarch64 --command=firefox org.mozilla.firefox --ProfileManager +Name=Open Profile Manager +Name[ach]=Open Profile Manager +Name[af]=Open Profile Manager +Name[an]=Open Profile Manager +Name[ar]=افتح مدير الملف الشخصي +Name[ast]=Open Profile Manager +Name[az]=Open Profile Manager +Name[be]=Адкрыць менеджар профіляў +Name[bg]=Отваряне на мениджъра на профили +Name[bn]=Open Profile Manager +Name[br]=Digeriñ an ardoer aeladoù +Name[bs]=Otvori Menadžera profila +Name[ca]=Obre el gestor de perfils +Name[ca_valencia]=Open Profile Manager +Name[cak]=Open Profile Manager +Name[cs]=Otevřít Správce profilů +Name[cy]=Agorwch y Rheolwr Proffil +Name[da]=Åbn profilhåndtering +Name[de]=Profilverwaltung öffnen +Name[dsb]=Profilowy zastojnik wócyniś +Name[el]=Άνοιγμα Διαχείρισης προφίλ +Name[en_CA]=Open Profile Manager +Name[en_GB]=Open Profile Manager +Name[eo]=Malfermi administranton de profiloj +Name[es_AR]=Abrir administrador de perfiles +Name[es_CL]=Abrir administrador de perfiles +Name[es_ES]=Abrir administrador de perfiles +Name[es_MX]=Abrir administrador de perfiles +Name[et]=Open Profile Manager +Name[eu]=Ireki profilen kudeatzailea +Name[fa]=گشودن مدیر نمایه +Name[ff]=Open Profile Manager +Name[fi]=Avaa profiilien hallinta +Name[fr]=Ouvrir le gestionnaire de profils +Name[fur]=Vierç gjestôr profîi +Name[fy_NL]=Profylbehearder iepenje +Name[ga_IE]=Open Profile Manager +Name[gd]=Fosgail manaidsear nam pròifilean +Name[gl]=Abrir o xestor de perfís +Name[gn]=Embojuruja mba’ete ñangarekoha +Name[gu_IN]=Open Profile Manager +Name[he]=פתיחת מנהל הפרופילים +Name[hi_IN]=Open Profile Manager +Name[hr]=Otvori upravljač profila +Name[hsb]=Zrjadowak profilow wočinić +Name[hu]=Profilkezelő megnyitása +Name[hy_AM]=Բացեք պրոֆիլի կառավարիչը +Name[ia]=Aperir le gestor de profilo +Name[id]=Buka Pengelola Profil +Name[is]=Opna umsýslu notandasniða +Name[it]=Apri gestore profili +Name[ja]=プロファイルマネージャーを開く +Name[ka]=პროფილის მმართველის გახსნა +Name[kab]=Ldi amsefrak n umaɣnu +Name[kk]=Профильдер бақарушысын ашу +Name[km]=Open Profile Manager +Name[kn]=Open Profile Manager +Name[ko]=프로필 관리자 열기 +Name[lij]=Open Profile Manager +Name[lt]=Open Profile Manager +Name[lv]=Atvērt profilu pārvaldnieku +Name[mk]=Open Profile Manager +Name[mr]=Open Profile Manager +Name[ms]=Open Profile Manager +Name[my]=Open Profile Manager +Name[nb_NO]=Åpne profilbehandler +Name[ne_NP]=Open Profile Manager +Name[nl]=Profielbeheerder openen +Name[nn_NO]=Opne profilhandsaming +Name[oc]=Dobrir lo gestionari de perfils +Name[pa_IN]=ਪਰੋਫ਼ਾਈਲ ਮੈਨੇਜਰ ਖੋਲ੍ਹੋ +Name[pl]=Menedżer profili +Name[pt_BR]=Abrir gerenciador de perfis +Name[pt_PT]=Abrir o Gestor de Perfis +Name[rm]=Avrir l'administraziun da profils +Name[ro]=Deschide managerul de profiluri +Name[ru]=Открыть менеджер профилей +Name[sat]=ᱢᱮᱫᱦᱟᱸ ᱢᱮᱱᱮᱡᱚᱨ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ +Name[sc]=Aberi su gestore de profilos +Name[sco]=Open Profile Manager +Name[si]=පැතිකඩ කළමනාකරු අරින්න +Name[sk]=Otvoriť Správcu profilov +Name[skr]=پروفائل منیجر کھولو +Name[sl]=Odpri upravitelja profilov +Name[son]=Open Profile Manager +Name[sq]=Hapni Përgjegjës Profilesh +Name[sr]=Отворите управљач профила +Name[sv_SE]=Öppna Profilhanteraren +Name[szl]=Open Profile Manager +Name[ta]=Open Profile Manager +Name[te]=Open Profile Manager +Name[tg]=Кушодани мудири профилҳо +Name[th]=เปิดตัวจัดการโปรไฟล์ +Name[tl]=Open Profile Manager +Name[tr]=Profil yöneticisini aç +Name[trs]=Sa nīkāj ñu’ūnj nej perfî huā nì’nï̀nj ïn +Name[uk]=Відкрити менеджер профілів +Name[ur]=Open Profile Manager +Name[uz]=Open Profile Manager +Name[vi]=Mở trình quản lý hồ sơ +Name[xh]=Open Profile Manager +Name[zh_CN]=打开配置文件管理器 +Name[zh_TW]=開啟設定檔管理員 diff --git a/modules/launchers/generate.sh b/modules/launchers/generate.sh new file mode 100644 index 0000000..4866b31 --- /dev/null +++ b/modules/launchers/generate.sh @@ -0,0 +1,106 @@ +# steam-frame-nix-launchers (lib.nix prepends spec, awk_script, runtime, +# apps, exports): writes the Flatpak/file launchers of steamFrame.launchers +# to /steam-frame-nix/applications (tmpfs), where the links in +# ~/.local/share/applications point. +# - one spec file per launcher (desktop ID = file name): rewrite.awk's +# environment, SFN_FILE for a host file; +# - an entry is replaced (temp file + rename) only when its content changed; +# entries without a source (app not installed) or of launchers no longer +# configured are removed; with an unknown Exec format the launcher is +# skipped: the entry is a copy of the original (a missing file behind the +# link would hide the original from the menus); +# - ..desktop.sum holds the hash of what was written last: a mismatch +# means someone edited the entry (KDE's "Edit Application"), which is +# overwritten with a warning; +# - if anything changed, the mtime of ~/.local/share/applications is updated +# (a running Steam rescans the "+" menu). +out=$runtime/steam-frame-nix/applications +log() { echo "steam-frame-nix-launchers: $*" >&2; } + +if [[ ! -d $runtime ]]; then + log "$runtime doesn't exist (outer session not running?); skipped" + exit 0 +fi +ids=() +for f in "$spec"/*; do [[ -e $f ]] && ids+=("${f##*/}"); done +if (( ${#ids[@]} == 0 )) && [[ ! -d $out ]]; then exit 0; fi +mkdir -p "$out" +exec 9>"$out/.lock" +flock 9 +rm -f "$out"/.*.tmp.* +IFS=: read -ra export_dirs <<< "$exports" +changed=0 + +remove() { # id why + if [[ -e $out/$1.desktop ]]; then + rm -f "$out/$1.desktop" + changed=1 + log "$1: entry removed ($2)" + fi + rm -f "$out/.$1.desktop.sum" +} + +# The link in ~/.local/share/applications is ours unless something replaced it. +check_link() { # id + local l=$apps/$1.desktop + if [[ -e $l || -L $l ]] && [[ $(readlink -m "$l") != "$(readlink -m "$out/$1.desktop")" ]]; then + log "warning: $l no longer leads to $out/$1.desktop (edited, e.g. with KDE's \"Edit Application\"?); set steamFrame.launchers.\"$1\".settings instead and remove it" + fi +} + +generate() { # id + local id=$1 dst=$out/$1.desktop sum=$out/.$1.desktop.sum src='' d tmp rc + unset "${!SFN_@}" + # shellcheck source=/dev/null + source "$spec/$id" + if [[ -n $SFN_FILE ]]; then + [[ -f $SFN_FILE ]] && src=$SFN_FILE + else + for d in "${export_dirs[@]}"; do + if [[ -f $d/$SFN_FLATPAK_ID.desktop ]]; then src=$d/$SFN_FLATPAK_ID.desktop; break; fi + done + fi + if [[ -z $src ]]; then + remove "$id" "no source entry: ${SFN_FILE:-Flatpak $SFN_FLATPAK_ID not installed}" + return 0 + fi + export "${!SFN_@}" + export SFN_SOURCE=$src + tmp=$(mktemp "$out/.$id.desktop.tmp.XXXXXX") + rc=0 + LC_ALL=C gawk -f "$awk_script" "$src" "$src" > "$tmp" 2> "$tmp.err" || rc=$? + if (( rc == 3 )); then + log "warning: $id: $(tr -s "\n" " " < "$tmp.err"); launcher not applied, the original entry is used" + cat "$src" > "$tmp" + elif (( rc != 0 )); then + log "error: $id: rewriting $src failed (status $rc): $(tr -s "\n" " " < "$tmp.err")" + rm -f "$tmp" "$tmp.err" + return 0 + fi + rm -f "$tmp.err" + if [[ -f $dst && -f $sum ]] && [[ $(sha256sum < "$dst") != "$(cat "$sum")" ]]; then + log "warning: $dst was edited (e.g. with KDE's \"Edit Application\"); overwritten, set steamFrame.launchers.\"$id\".settings instead" + fi + if [[ -f $dst ]] && cmp -s "$tmp" "$dst"; then + rm -f "$tmp" + else + chmod 644 "$tmp" + mv -f "$tmp" "$dst" + changed=1 + log "$id: entry written from $src" + fi + sha256sum < "$dst" > "$sum.tmp.$$" + mv -f "$sum.tmp.$$" "$sum" + check_link "$id" +} + +for id in "${ids[@]}"; do generate "$id"; done + +# Launchers no longer configured. +for f in "$out"/*.desktop "$out"/.*.desktop.sum; do + [[ -e $f ]] || continue + id=${f##*/}; id=${id#.}; id=${id%.sum}; id=${id%.desktop} + [[ -e $spec/$id ]] || remove "$id" "launcher no longer configured" +done + +if (( changed )) && [[ -d $apps ]]; then touch -c "$apps"; fi diff --git a/modules/launchers/lib.nix b/modules/launchers/lib.nix new file mode 100644 index 0000000..991121e --- /dev/null +++ b/modules/launchers/lib.nix @@ -0,0 +1,241 @@ +# Pieces of steamFrame.launchers (launchers.nix) that the check +# (check.nix) uses too: the per-launcher option type, Exec quoting, the +# rewrite environment, the build-time entry of a package and the runtime +# generator. +{ lib, pkgs }: +let + walletNames = [ "org.kde.kwalletd6" "org.freedesktop.secrets" ]; + + # One argument in desktop entry Exec syntax (key-file escaping of the whole + # value is rewrite.awk's): quoted when it has characters outside a safe + # set, with " ` $ \ escaped; % doubled (field codes). + quoteExec = s: + let + q = if builtins.match "[A-Za-z0-9_/.:,=+@-]+" s != null then s + else "\"" + lib.replaceStrings [ "\\" "\"" "`" "$" ] [ "\\\\" "\\\"" "\\`" "\\$" ] s + "\""; + in lib.replaceStrings [ "%" ] [ "%%" ] q; + execLine = args: lib.concatMapStringsSep " " quoteExec args; + + envArgs = env: lib.mapAttrsToList (k: v: "${k}=${v}") env; + envPrefix = env: lib.optionals (env != { }) ([ "env" ] ++ envArgs env); + + # "flatpak" | "package" | "file" + kindOf = l: if l.source.package != null then "package" + else if l.source.file != null then "file" else "flatpak"; +in rec { + inherit quoteExec execLine walletNames kindOf; + + # The option type of one launcher; `bus` is session.bus. + launcherModule = { bus }: { name, config, ... }: let + flatpak = kindOf config == "flatpak"; + in { + options = { + source = { + flatpak = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + defaultText = lib.literalMD "the desktop ID, when neither `package` nor `file` is set"; + example = "org.mozilla.firefox"; + description = '' + Flatpak app ID whose exported entry + (`~/.local/share/flatpak/exports/share/applications/.desktop`, + then `/var/lib/flatpak/...`) is rewritten at runtime. + ''; + }; + package = lib.mkOption { + type = lib.types.nullOr lib.types.package; + default = null; + example = lib.literalExpression "pkgs.claude-desktop"; + description = '' + Nix package whose `share/applications/.desktop` is + rewritten at build time (the build fails if it has none). + ''; + }; + file = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/usr/share/applications/org.kde.konsole.desktop"; + description = "Desktop entry on the host, rewritten at runtime."; + }; + }; + hostEnv = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + example = { DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/user/1000/bus"; }; + description = '' + Environment of the process the entry starts (`env K=V` before the + command): for a Flatpak, of the `flatpak` client, so e.g. its + sandbox's D-Bus proxy and portals use that bus. + ''; + }; + env = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + example = { MOZ_ENABLE_WAYLAND = "0"; }; + description = '' + Environment of the app: `flatpak run --env=K=V` for a Flatpak, like + `hostEnv` otherwise. + ''; + }; + flatpakArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "--device=all" ]; + description = "Further `flatpak run` options, before the app ID (Flatpaks only)."; + }; + args = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "--ozone-platform=x11" ]; + description = '' + Arguments right after the app ID / program, before the entry's own + (plain strings, quoted for you). + ''; + }; + wrappers = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = lib.literalExpression ''[ "''${pkgs.writeShellScript "wrap" "exec \"$@\""}" ]''; + description = '' + Commands the entry's command line is passed to, outermost first: + `Exec= ` (each gets the full command + line as its arguments). + ''; + }; + mimeTypes = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "x-scheme-handler/rdp" ]; + description = "Added to the entry's `MimeType=`, without making the app their default."; + }; + defaultFor = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "x-scheme-handler/element" "x-scheme-handler/io.element.desktop" ]; + description = '' + MIME types / `x-scheme-handler/` the app becomes the default + and a recommended handler for (`xdg.mimeApps`, Added Associations: + the portal then opens them without an app chooser, which VR doesn't + show); added to `MimeType=` too. + ''; + }; + settings = lib.mkOption { + type = lib.types.attrsOf (lib.types.nullOr lib.types.str); + default = { }; + example = { Name = "Element (wallet)"; Keywords = null; }; + description = '' + `[Desktop Entry]` keys to set or override (value in key-file syntax, + as given); `null` removes a key. Either way the key's localized + variants (`Name[de]=`) are dropped. + ''; + }; + keyring = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Keep the app's secrets in the one KDE wallet of both sessions: run + it on the outer bus (`hostEnv.DBUS_SESSION_BUS_ADDRESS`); a + Flatpak also gets `--talk-name=org.kde.kwalletd6 + --talk-name=org.freedesktop.secrets`. + ''; + }; + electron = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Pass `--password-store=kwallet6` (Electron apps: in the Steam + session they'd use an unencrypted local store). Needs + `keyring.enable`. + ''; + }; + }; + command = lib.mkOption { + type = lib.types.str; + readOnly = true; + description = '' + Approximately the command the entry runs, for a terminal (without + the entry's own options such as `--branch` or `--command`). + ''; + }; + }; + + config = { + hostEnv = lib.mkIf config.keyring.enable { DBUS_SESSION_BUS_ADDRESS = bus; }; + flatpakArgs = lib.mkIf (config.keyring.enable && flatpak) + (lib.mkBefore (map (n: "--talk-name=${n}") walletNames)); + args = lib.mkIf config.keyring.electron (lib.mkBefore [ "--password-store=kwallet6" ]); + command = command (config // { id = name; }); + }; + }; + + flatpakIdOf = l: if l.source.flatpak != null then l.source.flatpak else l.id; + + # Approximate shell command of a launcher (attrs as the option, plus `id`). + command = l: let + kind = kindOf l; + pkg = l.source.package; + in lib.escapeShellArgs ( + envPrefix (l.hostEnv // lib.optionalAttrs (kind != "flatpak") l.env) + ++ l.wrappers + ++ (if kind == "flatpak" then + [ "flatpak" "run" ] ++ l.flatpakArgs ++ map (e: "--env=${e}") (envArgs l.env) ++ [ (flatpakIdOf l) ] + else if kind == "package" then + [ "${pkg}/bin/${pkg.meta.mainProgram or (lib.getName pkg)}" ] + else [ "" ]) + ++ l.args); + + # The environment of rewrite.awk for a launcher (without SFN_SOURCE). + rewriteEnv = l: let flatpak = kindOf l == "flatpak"; in { + SFN_MODE = if flatpak then "flatpak" else "program"; + SFN_FLATPAK_ID = if flatpak then flatpakIdOf l else ""; + SFN_PREFIX = execLine (envPrefix (l.hostEnv // lib.optionalAttrs (!flatpak) l.env) ++ l.wrappers); + SFN_FLATPAK_ARGS = lib.optionalString flatpak + (execLine (l.flatpakArgs ++ map (e: "--env=${e}") (envArgs l.env))); + SFN_ARGS = execLine l.args; + SFN_MIME = lib.concatMapStrings (m: "${m};") (lib.unique (l.mimeTypes ++ l.defaultFor)); + SFN_SETTINGS = lib.concatStrings (lib.mapAttrsToList + (k: v: if v == null then "${k}\n" else "${k}=${v}\n") l.settings); + }; + + # Build time: the package's entry, rewritten. + packageEntry = l: let + src = "${l.source.package}/share/applications/${l.id}.desktop"; + in pkgs.runCommand "${l.id}.desktop" ({ + nativeBuildInputs = [ pkgs.gawk ]; + preferLocalBuild = true; + allowSubstitutes = false; + SFN_SOURCE = src; + } // rewriteEnv l) '' + if [ ! -f ${lib.escapeShellArg src} ]; then + echo "steamFrame.launchers.\"${l.id}\": ${l.source.package} has no share/applications/${l.id}.desktop" >&2 + exit 1 + fi + LC_ALL=C gawk -f ${./rewrite.awk} "$SFN_SOURCE" "$SFN_SOURCE" > $out || { + echo "steamFrame.launchers.\"${l.id}\": can't rewrite $SFN_SOURCE" >&2 + exit 1 + } + ''; + + # Runtime: the steam-frame-nix-launchers command for the Flatpak/file + # `launchers` (attrs of launchers with `id`); the dirs can be overridden + # with STEAM_FRAME_NIX_{RUNTIME_DIR,APPLICATIONS_DIR,FLATPAK_EXPORTS}. + generator = { launchers, runtimeDir, exportDirs, applicationsDir }: let + spec = pkgs.linkFarm "steam-frame-nix-launchers-spec" (lib.mapAttrsToList (id: l: { + name = id; + path = pkgs.writeText "steam-frame-nix-launcher-${id}" (lib.toShellVars (rewriteEnv l // { + SFN_FILE = if kindOf l == "file" then l.source.file else ""; + })); + }) launchers); + in pkgs.writeShellApplication { + name = "steam-frame-nix-launchers"; + runtimeInputs = [ pkgs.coreutils pkgs.gawk pkgs.util-linux ]; + text = '' + spec=${spec} + awk_script=${./rewrite.awk} + runtime=''${STEAM_FRAME_NIX_RUNTIME_DIR:-${lib.escapeShellArg runtimeDir}} + apps=''${STEAM_FRAME_NIX_APPLICATIONS_DIR:-${lib.escapeShellArg applicationsDir}} + exports=''${STEAM_FRAME_NIX_FLATPAK_EXPORTS:-${lib.escapeShellArg (lib.concatStringsSep ":" exportDirs)}} + '' + builtins.readFile ./generate.sh; + }; +} diff --git a/modules/launchers/rewrite.awk b/modules/launchers/rewrite.awk new file mode 100644 index 0000000..556df6c --- /dev/null +++ b/modules/launchers/rewrite.awk @@ -0,0 +1,192 @@ +# Rewrites a desktop entry for steamFrame.launchers (launchers.nix): run as +# LC_ALL=C gawk -f rewrite.awk FILE FILE (two passes over the same file) +# with the launcher in the environment (ENVIRON, never -v: no escape +# processing): +# SFN_MODE flatpak | program +# SFN_FLATPAK_ID app ID, the anchor when the entry has no X-Flatpak= +# SFN_PREFIX Exec text before the command (env K=V..., wrappers) +# SFN_FLATPAK_ARGS Exec text before the app ID (flatpak) +# SFN_ARGS Exec text after the app ID / program +# SFN_MIME MIME types to add to MimeType= ("a;b;") +# SFN_SETTINGS lines "Key=value" (set) or "Key" (remove) +# SFN_SOURCE path written as X-SteamFrameNix-Source= +# The SFN_*ARGS/PREFIX values are in Exec syntax (quoted, % doubled); the +# key-file escaping of the whole Exec value is done here. +# +# Line based: comments, unknown and localized keys stay as they are. Exec= of +# [Desktop Entry] and every [Desktop Action *] is rewritten: +# flatpak: +# (the first token equal to the ID after `run`, whose preceding +# token's basename is `flatpak`) +# program: +# (the first token after an optional leading `env A=B ...`) +# An Exec= without that anchor: nothing is printed, exit 3. No [Desktop +# Entry]: exit 4. + +function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s } + +# Key of a "Key=value" line ("" for other lines), localized keys included. +function keyof(line, k) { + if (line ~ /^[ \t]*#/ || line !~ /=/) return "" + k = line; sub(/=.*/, "", k); return trim(k) +} +function valueof(line, v) { v = line; sub(/^[^=]*=[ \t]*/, "", v); return v } +function base(k) { sub(/\[.*/, "", k); return k } + +# Key-file string escapes (GLib): \s \n \t \r \\. +function kf_unescape(s, out, i, c, n) { + out = "" + for (i = 1; i <= length(s); i++) { + c = substr(s, i, 1) + if (c == "\\" && i < length(s)) { + n = substr(s, ++i, 1) + if (n == "s") c = " "; else if (n == "n") c = "\n"; else if (n == "t") c = "\t" + else if (n == "r") c = "\r"; else if (n == "\\") c = "\\"; else c = "\\" n + } + out = out c + } + return out +} +function kf_escape(s) { + gsub(/\\/, "\\\\", s); gsub(/\n/, "\\n", s); gsub(/\t/, "\\t", s); gsub(/\r/, "\\r", s) + return s +} + +# Splits an Exec value (Exec syntax) into tokens: TS/TE start/end offsets in +# s, TV the unquoted value. Returns the count, -1 on an unclosed quote. +function tokenize(s, n, i, c, q, v, len) { + delete TS; delete TE; delete TV + n = 0; i = 1; len = length(s) + while (i <= len) { + c = substr(s, i, 1) + if (c == " " || c == "\t") { i++; continue } + TS[++n] = i; v = ""; q = 0 + while (i <= len) { + c = substr(s, i, 1) + if (!q && (c == " " || c == "\t")) break + if (c == "\"") { q = !q; i++; continue } + if (q && c == "\\" && i < len) { v = v substr(s, i + 1, 1); i += 2; continue } + v = v c; i++ + } + if (q) return -1 + TE[n] = i; TV[n] = v + } + return n +} + +function join3(a, b, c, r) { + r = a + if (b != "") r = (r == "" ? b : r " " b) + if (c != "") r = (r == "" ? c : r " " c) + return r +} + +# The rewritten Exec value (raw key-file text), or "\001" without anchor. +function rewrite_exec(raw, s, n, k, j, b, head, tail, pre, v) { + s = kf_unescape(raw) + n = tokenize(s) + if (n <= 0) return "\001" + k = 0 + if (ENVIRON["SFN_MODE"] == "flatpak") { + for (j = 2; j < n && !k; j++) { + b = TV[j - 1]; sub(/.*\//, "", b) + if (b == "flatpak" && TV[j] == "run") + for (v = j + 1; v <= n; v++) if (TV[v] == ANCHOR) { k = v; break } + } + if (!k) return "\001" + head = trim(substr(s, 1, TS[k] - 1)) + pre = join3(ENVIRON["SFN_PREFIX"], head, ENVIRON["SFN_FLATPAK_ARGS"]) + pre = join3(pre, substr(s, TS[k], TE[k] - TS[k]), "") + } else { + k = 1 + if (TV[1] == "env") { + for (k = 2; k <= n && TV[k] ~ /^[A-Za-z_][A-Za-z0-9_]*=/; k++) ; + if (k > n || TV[k] ~ /^-/) return "\001" + } + pre = join3(ENVIRON["SFN_PREFIX"], trim(substr(s, 1, TE[k] - 1)), "") + } + tail = substr(s, TE[k]) + return kf_escape(join3(pre, ENVIRON["SFN_ARGS"], "") tail) +} + +function in_entry() { return GROUP == "[Desktop Entry]" } + +# Pass 1: the X-Flatpak= of [Desktop Entry]. +FNR == NR { + if ($0 ~ /^\[/) GROUP = trim($0) + else if (in_entry() && keyof($0) == "X-Flatpak" && XFLATPAK == "") XFLATPAK = trim(valueof($0)) + next +} + +FNR == 1 { + ANCHOR = XFLATPAK != "" ? XFLATPAK : ENVIRON["SFN_FLATPAK_ID"] + GROUP = ""; N = 0; ENTRY_END = 0; FAIL = "" + # settings: SET[key] = value, DEL[key] + ns = split(ENVIRON["SFN_SETTINGS"], sl, "\n") + for (i = 1; i <= ns; i++) { + if (sl[i] == "") continue + if (index(sl[i], "=")) { k = substr(sl[i], 1, index(sl[i], "=") - 1); SET[k] = substr(sl[i], index(sl[i], "=") + 1); ORDER[++NSET] = k } + else DEL[sl[i]] = 1 + } + nm = split(ENVIRON["SFN_MIME"], ml, ";") +} + +{ + line = $0 + if (line ~ /^\[/) { + GROUP = trim(line); L[++N] = line + if (in_entry()) { SEEN_ENTRY = 1; ENTRY_END = N } + next + } + key = keyof(line) + if (key != "" && (in_entry() || GROUP ~ /^\[Desktop Action /)) { + if (key == "Exec" && !(in_entry() && (key in SET || key in DEL))) { + v = rewrite_exec(valueof(line)) + if (v == "\001") { FAIL = FAIL "\n " GROUP " Exec=" valueof(line); next } + line = "Exec=" v + } + } + if (key != "" && in_entry()) { + b = base(key) + if (b in SET || b in DEL) { + if (key != b || b in DEL || b in DONE) next # localized variant, removed, or duplicate + line = b "=" SET[b]; DONE[b] = 1 + } else if (key == "DBusActivatable" && tolower(trim(valueof(line))) == "true") { + line = "DBusActivatable=false" + } else if (key == "X-SteamFrameNix-Source") { + next + } else if (key == "MimeType" && !MIMEDONE) { + line = "MimeType=" merge_mime(valueof(line)); MIMEDONE = 1 + } + } + L[++N] = line + if (in_entry() && trim(line) != "") ENTRY_END = N +} + +function merge_mime(v, have, cur, i, out) { + out = v + if (out != "" && out !~ /;$/) out = out ";" + split(v, cur, ";") + for (i in cur) if (cur[i] != "") have[trim(cur[i])] = 1 + for (i = 1; i <= nm; i++) if (ml[i] != "" && !(ml[i] in have)) { out = out ml[i] ";"; have[ml[i]] = 1 } + return out +} + +END { + if (FAIL != "") { + print "unknown Exec format (no " (ENVIRON["SFN_MODE"] == "flatpak" ? "`flatpak run ... " ANCHOR "`" : "program") "):" FAIL > "/dev/stderr" + exit 3 + } + if (!SEEN_ENTRY) { print "no [Desktop Entry] group" > "/dev/stderr"; exit 4 } + for (i = 1; i <= N; i++) { + print L[i] + if (i == ENTRY_END) { + for (j = 1; j <= NSET; j++) if (!(ORDER[j] in DONE)) print ORDER[j] "=" SET[ORDER[j]] + if (!MIMEDONE && !("MimeType" in SET) && !("MimeType" in DEL) && nm > 0) { + m = merge_mime("") + if (m != "") print "MimeType=" m + } + print "X-SteamFrameNix-Source=" ENVIRON["SFN_SOURCE"] + } + } +} diff --git a/template/home.nix b/template/home.nix index c5f5045..f6a0e99 100644 --- a/template/home.nix +++ b/template/home.nix @@ -62,10 +62,10 @@ # jellyfin.hardwareDecoding.enable = true; # # Apps that keep logins in the KDE wallet: one wallet for both # # sessions (Element: install im.riot.Riot yourself): - # keyring.flatpaks."im.riot.Riot" = { - # name = "Element"; - # electron = true; - # schemeHandlers = [ "element" "io.element.desktop" ]; # SSO callback + # launchers."im.riot.Riot" = { + # keyring = { enable = true; electron = true; }; + # # SSO callback: + # defaultFor = [ "x-scheme-handler/element" "x-scheme-handler/io.element.desktop" ]; # }; # docker.enable = true; # rootless Docker, user service # };