mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 03:00:13 +02:00
Keyring launchers, Firefox default browser, rootless Docker
- keyring.flatpaks / keyring.programs: desktop entries shadowing an app's own that run it on the outer bus (one kwalletd6 for both sessions), with the wallet's D-Bus names as `flatpak run --talk-name` options (no Flatpak overrides), --password-store=kwallet6 for Electron, and login callback schemes as default + recommended handlers. - firefox.defaultBrowser: the launcher as default for http, https and text/html. - docker: rootless dockerd as a user service, socket in the outer runtime dir, CLI with DOCKER_HOST for both sessions.
This commit is contained in:
1 parent
aa9ca183f1
commit
b0131338de
10 files changed
+617
-14
No files matched your search
@@ -3,8 +3,8 @@
|
|||||||
[Home Manager](https://github.com/nix-community/home-manager) modules for the
|
[Home Manager](https://github.com/nix-community/home-manager) modules for the
|
||||||
Valve Steam Frame (SteamOS, `aarch64-linux`, standalone home-manager). They
|
Valve Steam Frame (SteamOS, `aarch64-linux`, standalone home-manager). They
|
||||||
work around quirks of the Frame's [two graphical sessions](#two-sessions)
|
work around quirks of the Frame's [two graphical sessions](#two-sessions)
|
||||||
(portal, keyboard layout, clipboard, Firefox), enable hardware video decoding
|
(portal, keyboard layout, clipboard, KDE wallet, Firefox), enable hardware
|
||||||
in Jellyfin, and extend Steam's and SteamVR's UIs at runtime (VR keyboard,
|
video decoding in Jellyfin, run rootless Docker, and extend Steam's and SteamVR's UIs at runtime (VR keyboard,
|
||||||
"+" menu, dashboard windows, Steam close button, window curvature, window
|
"+" menu, dashboard windows, Steam close button, window curvature, window
|
||||||
controls).
|
controls).
|
||||||
|
|
||||||
@@ -50,8 +50,10 @@ configuration, limitations and how it works.
|
|||||||
|
|
||||||
**Apps:**
|
**Apps:**
|
||||||
|
|
||||||
- [Firefox](docs/firefox.md) (`firefox`): launcher for the Flatpak with a VR fullscreen fix, optional AV1 off, a separate desktop profile.
|
- [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.
|
- [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.
|
||||||
|
- [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).
|
**Your own patches** of Steam's UI, and fixing patches after a Steam update: [UI patches](docs/ui-patches.md).
|
||||||
|
|
||||||
@@ -111,8 +113,7 @@ What this means for you:
|
|||||||
([session settings](docs/session.md#session-settings-and-services)).
|
([session settings](docs/session.md#session-settings-and-services)).
|
||||||
- **Wallet:** there should be one `kwalletd6`, on the outer bus; apps started
|
- **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
|
from the desktop would otherwise start a second one whose secrets VR can't
|
||||||
see. Prefix such launchers' `Exec=` with `steamFrame.session.busEnv`
|
see. List such apps in [keyring](docs/keyring.md).
|
||||||
([example](docs/session.md#session-settings-and-services)).
|
|
||||||
- **Launchers:** the "+" menu only sees `~/.local/share/applications` (not
|
- **Launchers:** the "+" menu only sees `~/.local/share/applications` (not
|
||||||
`~/.nix-profile/share`), so entries are written there, shadowing
|
`~/.nix-profile/share`), so entries are written there, shadowing
|
||||||
Flatpak/package entries with the same ID.
|
Flatpak/package entries with the same ID.
|
||||||
@@ -190,7 +191,14 @@ these two files ([`template/`](template), with more comments):
|
|||||||
};
|
};
|
||||||
firefox.enable = true;
|
firefox.enable = true;
|
||||||
firefox.disableAv1 = true;
|
firefox.disableAv1 = true;
|
||||||
|
firefox.defaultBrowser = true;
|
||||||
jellyfin.hardwareDecoding.enable = true; # install the Flatpak yourself
|
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" ];
|
||||||
|
};
|
||||||
|
docker.enable = true; # rootless
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -205,7 +213,7 @@ home-manager switch --flake .#steamos # manual setup, from the flake's director
|
|||||||
In your own flake, add the input as above and
|
In your own flake, add the input as above and
|
||||||
`steam-frame-nix.homeManagerModules.default` to the modules. `default`
|
`steam-frame-nix.homeManagerModules.default` to the modules. `default`
|
||||||
imports all modules; single ones:
|
imports all modules; single ones:
|
||||||
`homeManagerModules.{session,portal,keyboard-layout,steam-keyboard-patch,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}`.
|
`homeManagerModules.{session,portal,keyboard-layout,steam-keyboard-patch,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}`.
|
||||||
Every module imports `cleanup` (see
|
Every module imports `cleanup` (see
|
||||||
[Changes outside Nix](#changes-outside-nix-exceptions)).
|
[Changes outside Nix](#changes-outside-nix-exceptions)).
|
||||||
|
|
||||||
@@ -284,9 +292,29 @@ Every module imports `cleanup` (see
|
|||||||
| `steamFrame.firefox.disableAv1` | bool | `false` | Default `media.av1.enabled` to `false`: the Frame's decoder driver has no AV1, so sites send VP9/H.264, decoded in hardware. |
|
| `steamFrame.firefox.disableAv1` | bool | `false` | Default `media.av1.enabled` to `false`: the Frame's decoder driver has no AV1, so sites send VP9/H.264, decoded in hardware. |
|
||||||
| `steamFrame.firefox.prefs` | attrs of bool, int or str | `{ }` | Further `about:config` default values for every profile (override the fixes too). |
|
| `steamFrame.firefox.prefs` | attrs of bool, int or str | `{ }` | Further `about:config` default values for every profile (override the fixes too). |
|
||||||
| `steamFrame.firefox.desktopProfile` | null or str | `"desktop"` | Separate profile (directory name) for the nested desktop; `null`: the default profile in both sessions. |
|
| `steamFrame.firefox.desktopProfile` | null or str | `"desktop"` | Separate profile (directory name) for the nested desktop; `null`: the default profile in both sessions. |
|
||||||
|
| `steamFrame.firefox.defaultBrowser` | bool | `false` | Default for `http`, `https`, `text/html` (`xdg.mimeApps`). |
|
||||||
| `steamFrame.jellyfin.hardwareDecoding.enable` | bool | `false` | Hardware video decoding in the Jellyfin Desktop Flatpak, see [Jellyfin](docs/jellyfin.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.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.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}.<id>.name` | str | required | `Name=`. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.genericName`, `.comment`, `.startupWMClass` | null or str | `null` | `GenericName=`, `Comment=`, `StartupWMClass=`. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.icon` | null or str | flatpaks: the app ID; programs: `null` | `Icon=`. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.categories` | list of str | `[ ]` | `Categories=`. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.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}.<id>.mimeTypes` | list of str | `[ ]` | Further `MimeType=` entries, not made default. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.electron` | bool | `false` | Pass `--password-store=kwallet6` (Electron apps). |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.args` | list of str | `[ ]` | Further app arguments (desktop entry syntax). |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.fieldCode` | `"%U"`, `"%u"`, `"%F"`, `"%f"`, `""` | `"%U"` | How the entry passes URLs/files. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.actions` | attrs of `{ name; args; }` | `{ }` | Desktop actions, each running the command with its args. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.settings` | attrs of str | `{ }` | Further `[Desktop Entry]` keys. |
|
||||||
|
| `steamFrame.keyring.{flatpaks,programs}.<id>.command` | str, read-only | | The command line without args, for a terminal. |
|
||||||
|
| `steamFrame.keyring.flatpaks.<id>.flatpakArgs` | list of str | `[ ]` | Further `flatpak run` options. |
|
||||||
|
| `steamFrame.keyring.programs.<id>.executable` | str | required | The program to run, e.g. `"${pkgs.claude-desktop}/bin/claude-desktop"`. |
|
||||||
|
| `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). |
|
||||||
| `steamFrame.cleanup.package` | package, read-only | | `steam-frame-nix-cleanup` (on `PATH` too), see [Changes outside Nix](#changes-outside-nix-exceptions). |
|
| `steamFrame.cleanup.package` | package, read-only | | `steam-frame-nix-cleanup` (on `PATH` too), see [Changes outside Nix](#changes-outside-nix-exceptions). |
|
||||||
|
|
||||||
Renamed options still work under their old names, with a warning:
|
Renamed options still work under their old names, with a warning:
|
||||||
@@ -341,8 +369,10 @@ versions left: [docs/cleanup.md](docs/cleanup.md).
|
|||||||
- clipboard-sync runs from KDE autostart (a Home Manager link).
|
- clipboard-sync runs from KDE autostart (a Home Manager link).
|
||||||
- Firefox: the desktop profile's `user.js` link exists only while its
|
- Firefox: the desktop profile's `user.js` link exists only while its
|
||||||
Firefox runs (see [Firefox](docs/firefox.md#how-it-works)).
|
Firefox runs (see [Firefox](docs/firefox.md#how-it-works)).
|
||||||
- Jellyfin: the hardware decoding permissions are `flatpak run` options of
|
- Jellyfin, keyring launchers: the permissions are `flatpak run` options of
|
||||||
the desktop entry, not a Flatpak override.
|
the desktop entries, not Flatpak overrides.
|
||||||
|
- Docker: the daemon's socket in `/run/user/1000` (tmpfs) exists while
|
||||||
|
`docker.service` runs.
|
||||||
|
|
||||||
### Set up by install.sh
|
### Set up by install.sh
|
||||||
|
|
||||||
@@ -369,8 +399,18 @@ versions left: [docs/cleanup.md](docs/cleanup.md).
|
|||||||
|
|
||||||
Not steam-frame-nix's to remove: the Firefox desktop profile
|
Not steam-frame-nix's to remove: the Firefox desktop profile
|
||||||
(`~/.var/app/org.mozilla.firefox/config/mozilla/firefox/desktop`, browser
|
(`~/.var/app/org.mozilla.firefox/config/mozilla/firefox/desktop`, browser
|
||||||
data), and whatever apps keep in `~/.var/app/*`, Flatpak apps and their
|
data), secrets apps stored in the KDE wallet, and whatever apps keep in
|
||||||
runtimes.
|
`~/.var/app/*`, Flatpak apps and their runtimes.
|
||||||
|
|
||||||
|
Docker's images, containers and volumes (`~/.local/share/docker`) are partly
|
||||||
|
owned by the subordinate UIDs of your containers, so a plain `rm` fails.
|
||||||
|
With the daemon running, then stopped:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker system prune -a --volumes
|
||||||
|
systemctl --user stop docker
|
||||||
|
nix shell nixpkgs#rootlesskit -c rootlesskit rm -rf ~/.local/share/docker
|
||||||
|
```
|
||||||
|
|
||||||
## Rollback
|
## Rollback
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Rootless Docker
|
||||||
|
|
||||||
|
`docker.*`, module `docker`. Options:
|
||||||
|
[README, Options](../README.md#options).
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
SteamOS has no Docker, and Home Manager can't install the usual root
|
||||||
|
daemon. Rootless Docker's default socket is in `$XDG_RUNTIME_DIR`, which
|
||||||
|
differs between the Frame's [two sessions](../README.md#two-sessions), and
|
||||||
|
the nested desktop can't reach the user service manager.
|
||||||
|
|
||||||
|
## What you get
|
||||||
|
|
||||||
|
- `dockerd` in rootless mode as the systemd user service `docker.service`
|
||||||
|
of the Steam session's user manager, started at login and on switch
|
||||||
|
(never restarted by a switch, which would stop running containers).
|
||||||
|
- The `docker` CLI on `PATH`, whose default `DOCKER_HOST` is the socket in
|
||||||
|
the outer runtime dir (`docker.host`,
|
||||||
|
`unix:///run/user/1000/docker.sock`): it works in both sessions. A
|
||||||
|
`DOCKER_HOST` you set yourself wins.
|
||||||
|
|
||||||
|
SteamOS already has what rootless Docker needs: `newuidmap`/`newgidmap`
|
||||||
|
with their capabilities, `/etc/subuid` and `/etc/subgid` entries for the
|
||||||
|
user, user namespaces and cgroup v2 delegation.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```nix
|
||||||
|
steamFrame.docker.enable = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `docker run --rm hello-world`. `docker.package` replaces the Docker
|
||||||
|
package (daemon and CLI).
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
- Rootless limits apply: no ports below 1024 without extra setup,
|
||||||
|
`--network host` is the rootless network namespace, containers can't
|
||||||
|
gain real root.
|
||||||
|
- Images, containers and volumes are app data in `~/.local/share/docker`,
|
||||||
|
see [App data you create](../README.md#app-data-you-create) for how to
|
||||||
|
remove them.
|
||||||
|
- Disabling the module removes the unit but doesn't stop a running daemon:
|
||||||
|
run `systemctl --user stop docker` first (or add `docker.service` to
|
||||||
|
`session.services.stop` for that switch).
|
||||||
|
- Another `docker` package in `home.packages` collides with the wrapped
|
||||||
|
CLI.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
The user unit runs `dockerd-rootless` (RootlessKit) with `PATH=/usr/bin`,
|
||||||
|
for SteamOS's `newuidmap`/`newgidmap`, and `Delegate=yes`. The unit is
|
||||||
|
added to `session.services.start`, so each switch starts it if it isn't
|
||||||
|
running. The CLI is the package's `docker` wrapped with a default
|
||||||
|
`DOCKER_HOST`; nothing is written outside the store (no `daemon.json`, no
|
||||||
|
Docker context).
|
||||||
+8
-2
@@ -7,8 +7,10 @@
|
|||||||
|
|
||||||
In the Steam session gamescope never shows fullscreen windows, so Firefox
|
In the Steam session gamescope never shows fullscreen windows, so Firefox
|
||||||
looks frozen when a page goes fullscreen; sites send AV1, which the Frame
|
looks frozen when a page goes fullscreen; sites send AV1, which the Frame
|
||||||
decodes in software; and the two sessions can't see each other's Firefox,
|
decodes in software; the two sessions can't see each other's Firefox,
|
||||||
so a second instance stops at the locked profile.
|
so a second instance stops at the locked profile; and without a default
|
||||||
|
browser the portal opens links with the first installed `https` handler
|
||||||
|
(e.g. Chromium), in both sessions.
|
||||||
|
|
||||||
## What you get
|
## What you get
|
||||||
|
|
||||||
@@ -26,6 +28,9 @@ ID), so default-browser associations keep working.
|
|||||||
- **`desktopProfile`** (`"desktop"`): in the nested desktop the launcher
|
- **`desktopProfile`** (`"desktop"`): in the nested desktop the launcher
|
||||||
uses this separate profile (a normal Firefox profile with its own browser
|
uses this separate profile (a normal Firefox profile with its own browser
|
||||||
data, created on first use). `null`: the default profile in both sessions.
|
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`).
|
||||||
|
|
||||||
`prefs` and the fixes are *default* values, not user values: `about:config`
|
`prefs` and the fixes are *default* values, not user values: `about:config`
|
||||||
can still change them per profile, and removing one leaves nothing behind.
|
can still change them per profile, and removing one leaves nothing behind.
|
||||||
@@ -37,6 +42,7 @@ Changes take effect at the next start of Firefox.
|
|||||||
steamFrame.firefox = {
|
steamFrame.firefox = {
|
||||||
enable = true;
|
enable = true;
|
||||||
disableAv1 = true;
|
disableAv1 = true;
|
||||||
|
defaultBrowser = true;
|
||||||
prefs."browser.startup.page" = 3; # restore the previous session
|
prefs."browser.startup.page" = 3; # restore the previous session
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
# Keyring launchers
|
||||||
|
|
||||||
|
`keyring.flatpaks.<app ID>`, `keyring.programs.<desktop ID>`, 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.<name>.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=<app ID>`. 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`.
|
||||||
+3
-2
@@ -23,8 +23,9 @@ running") and skips `reloadSystemd`.
|
|||||||
and applies `session.services.start` / `stop` / `restart`, which other
|
and applies `session.services.start` / `stop` / `restart`, which other
|
||||||
modules fill (you can add your own units).
|
modules fill (you can add your own units).
|
||||||
|
|
||||||
**Configuration:** a launcher for an app that must use the single wallet on
|
**Configuration:** apps that keep secrets in the wallet get launchers from
|
||||||
the outer bus (see [Two sessions](../README.md#two-sessions)):
|
[keyring](keyring.md). Another launcher that must reach the outer session
|
||||||
|
(its bus, services or wallet) uses the prefix:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ config, ... }: {
|
{ config, ... }: {
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
};
|
};
|
||||||
firefox = ./modules/firefox.nix;
|
firefox = ./modules/firefox.nix;
|
||||||
jellyfin = ./modules/jellyfin.nix;
|
jellyfin = ./modules/jellyfin.nix;
|
||||||
|
keyring = ./modules/keyring.nix;
|
||||||
|
docker = ./modules/docker.nix;
|
||||||
};
|
};
|
||||||
systems = [ "aarch64-linux" "x86_64-linux" ];
|
systems = [ "aarch64-linux" "x86_64-linux" ];
|
||||||
forSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
forSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# Rootless Docker as a systemd user service (home-manager can't install a
|
||||||
|
# root daemon). SteamOS already provides what it needs: newuidmap/newgidmap
|
||||||
|
# with their capabilities in /usr/bin, /etc/subuid and /etc/subgid entries
|
||||||
|
# for the user, user namespaces and cgroup v2 delegation.
|
||||||
|
# - The socket is in the outer runtime dir (session.runtimeDir): the nested
|
||||||
|
# desktop has its own XDG_RUNTIME_DIR, so the CLI gets DOCKER_HOST as a
|
||||||
|
# default in its wrapper instead of relying on $XDG_RUNTIME_DIR.
|
||||||
|
# - Started, never restarted, on switch (session.services.start): a restart
|
||||||
|
# would stop running containers.
|
||||||
|
# Nothing is written outside the store by this module; dockerd keeps its data
|
||||||
|
# (images, containers, volumes) in ~/.local/share/docker (app data).
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.steamFrame.docker;
|
||||||
|
host = "unix://${config.steamFrame.session.runtimeDir}/docker.sock";
|
||||||
|
|
||||||
|
cli = pkgs.symlinkJoin {
|
||||||
|
name = "docker-rootless-cli";
|
||||||
|
paths = [ cfg.package ];
|
||||||
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||||
|
postBuild = ''
|
||||||
|
wrapProgram $out/bin/docker --set-default DOCKER_HOST ${host}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = [ ./cleanup.nix ];
|
||||||
|
|
||||||
|
options.steamFrame.docker = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
rootless Docker: dockerd as a user service and the docker CLI, usable
|
||||||
|
from both sessions'';
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = pkgs.docker;
|
||||||
|
defaultText = lib.literalExpression "pkgs.docker";
|
||||||
|
description = ''
|
||||||
|
Docker package: dockerd-rootless for the service, the CLI (wrapped
|
||||||
|
with DOCKER_HOST) on PATH.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
host = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = host;
|
||||||
|
defaultText = lib.literalExpression
|
||||||
|
''"unix://''${config.steamFrame.session.runtimeDir}/docker.sock"'';
|
||||||
|
description = "DOCKER_HOST of the daemon (for other clients).";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
home.packages = [ cli ];
|
||||||
|
|
||||||
|
systemd.user.services.docker = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Docker Application Container Engine (Rootless)";
|
||||||
|
StartLimitIntervalSec = 60;
|
||||||
|
StartLimitBurst = 3;
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
Type = "notify";
|
||||||
|
# /usr/bin for SteamOS's newuidmap/newgidmap (they need their caps).
|
||||||
|
Environment = "PATH=/usr/bin";
|
||||||
|
ExecStart = "${cfg.package}/bin/dockerd-rootless";
|
||||||
|
ExecReload = "${pkgs.procps}/bin/kill -s HUP $MAINPID";
|
||||||
|
TimeoutSec = 0;
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 2;
|
||||||
|
LimitNOFILE = "infinity";
|
||||||
|
LimitNPROC = "infinity";
|
||||||
|
LimitCORE = "infinity";
|
||||||
|
Delegate = true;
|
||||||
|
NotifyAccess = "all";
|
||||||
|
KillMode = "mixed";
|
||||||
|
};
|
||||||
|
Install.WantedBy = [ "default.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
steamFrame.session.services.start = [ "docker.service" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
# copies and links) are removed by steam-frame-nix-cleanup (on switch).
|
# copies and links) are removed by steam-frame-nix-cleanup (on switch).
|
||||||
# The entry shadows the Flatpak's (same ID), keeping MIME associations, and is
|
# The entry shadows the Flatpak's (same ID), keeping MIME associations, and is
|
||||||
# seen by the "+" menu (which reads only ~/.local/share/applications).
|
# seen by the "+" menu (which reads only ~/.local/share/applications).
|
||||||
|
# defaultBrowser: without a default the portal picks the first installed
|
||||||
|
# https handler (e.g. Chromium) in both sessions.
|
||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
let
|
let
|
||||||
cfg = config.steamFrame.firefox;
|
cfg = config.steamFrame.firefox;
|
||||||
@@ -98,9 +100,26 @@ in {
|
|||||||
default profile in both sessions.
|
default profile in both sessions.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
defaultBrowser = lib.mkOption {
|
||||||
|
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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
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");
|
||||||
|
};
|
||||||
|
|
||||||
# stable: the branch the launcher runs (the extension point has no
|
# stable: the branch the launcher runs (the extension point has no
|
||||||
# version, so it takes the app's branch).
|
# version, so it takes the app's branch).
|
||||||
xdg.dataFile = lib.optionalAttrs (defaultPrefs != { } || desktopFix) {
|
xdg.dataFile = lib.optionalAttrs (defaultPrefs != { } || desktopFix) {
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
# 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));
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -56,8 +56,17 @@
|
|||||||
# firefox.enable = true; # launcher for the Firefox Flatpak
|
# firefox.enable = true; # launcher for the Firefox Flatpak
|
||||||
# # the Frame has no AV1 decoder: sites send VP9/H.264 (hardware):
|
# # the Frame has no AV1 decoder: sites send VP9/H.264 (hardware):
|
||||||
# firefox.disableAv1 = true;
|
# firefox.disableAv1 = true;
|
||||||
|
# firefox.defaultBrowser = true; # default for http/https links
|
||||||
# # Hardware video decoding in the Jellyfin Desktop Flatpak (install
|
# # Hardware video decoding in the Jellyfin Desktop Flatpak (install
|
||||||
# # org.jellyfin.JellyfinDesktop yourself); gives it devices=all:
|
# # org.jellyfin.JellyfinDesktop yourself); gives it devices=all:
|
||||||
# jellyfin.hardwareDecoding.enable = true;
|
# 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
|
||||||
|
# };
|
||||||
|
# docker.enable = true; # rootless Docker, user service
|
||||||
# };
|
# };
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user