From 50e5991c59f23647358f8ab719503c3c5f29af81 Mon Sep 17 00:00:00 2001 From: Pierre Kisters <1524059+lhns@users.noreply.github.com> Date: Tue, 29 Sep 2026 03:05:27 +0200 Subject: [PATCH] Portal: KDE file dialog for Flatpak apps in the Steam session Neither Valve backend (gamescope, holo) implements FileChooser, so sandboxed apps fell back to an in-sandbox dialog without the home dir (e.g. Element's Attachments). session.portalFix.fileChooser (on) links kde.portal into the Steam session's portal dir and selects kde for FileChooser; xdg-desktop-portal-kde is D-Bus activated with DISPLAY=:0. Flake check for the portal config. --- README.md | 3 ++- docs/development.md | 1 + docs/session.md | 28 ++++++++++++++++++++++------ flake.nix | 5 +++-- modules/portal.nix | 36 ++++++++++++++++++++++++++++-------- modules/portal/check.nix | 36 ++++++++++++++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 modules/portal/check.nix diff --git a/README.md b/README.md index 566888f..f6b9ea9 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ configuration, limitations and how it works. **Session** ([docs/session.md](docs/session.md)): - [Session settings](docs/session.md#session-settings-and-services) (`session.*`): outer bus and user services, for launchers and the other modules. -- [Portal fix](docs/session.md#portal-fix) (`session.portalFix`, on): apps in the Steam session can open links. +- [Portal fix](docs/session.md#portal-fix) (`session.portalFix`, on): apps in the Steam session can open links, and Flatpak apps get KDE's file dialog. - [Applications menu](docs/session.md#applications-menu) (`session.applicationsMenu`, on): KDE apps in the Steam session (Dolphin from the "+" menu) know the installed apps. - [Keyboard layout](docs/session.md#keyboard-layout) (`keyboard.layout`, `keyboard.variant`): XKB layout for the Steam session. - [Clipboard sync](docs/session.md#clipboard-sync) (`clipboardSync`, on): one clipboard for the Steam session and the nested desktop. @@ -232,6 +232,7 @@ which: [Repository layout](docs/development.md). | `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). | | `steamFrame.session.portalFix.enable` | bool | `true` | Working portal config (OpenURI) for the Steam session. | +| `steamFrame.session.portalFix.fileChooser` | bool | `true` | KDE's file dialog as the Steam session's FileChooser portal (Flatpak apps can open and save files outside their sandbox). Needs `portalFix.enable`. | | `steamFrame.session.applicationsMenu.enable` | bool | `true` | `~/.config/menus/applications.menu` linked to Plasma's, for KDE apps in the Steam session. | | `steamFrame.keyboard.layout` | null or str | `null` | XKB layout for the Steam session, e.g. `"de"`; `null`: US. | | `steamFrame.keyboard.variant` | null or str | `null` | XKB variant for the Steam session, e.g. `"nodeadkeys"`; see [Keyboard layout](docs/session.md#keyboard-layout). | diff --git a/docs/development.md b/docs/development.md index af0435d..0b540c9 100644 --- a/docs/development.md +++ b/docs/development.md @@ -50,6 +50,7 @@ modules/ cleanup/package.nix build: install.sh as a command (cleanup, steamvr-debugger-arm) cleanup/check.nix test: install.sh cleanup on fake home/runtime dirs portal.nix Steam session portal config (session.portalFix) + portal/check.nix test: KDE FileChooser on by default, absent when disabled applications-menu.nix applications.menu link for KDE apps (session.applicationsMenu) applications-menu/check.nix test: the link on by default, absent when disabled keyboard-layout.nix gamescope-session drop-in with XKB_DEFAULT_* (keyboard.layout) diff --git a/docs/session.md b/docs/session.md index 9c2a744..1ee9672 100644 --- a/docs/session.md +++ b/docs/session.md @@ -40,21 +40,37 @@ launcher's `hostEnv`; a hand-written entry uses the prefix `session.busEnv`: ## Portal fix -`session.portalFix.enable`, module `portal`. On by default. +`session.portalFix.enable` and `session.portalFix.fileChooser`, module +`portal`. Both on by default. **Problem:** the Frame image (SteamOS 0.3.0, build 20260922) points the Steam session's `xdg-desktop-portal` at `/usr/share/xdg-desktop-portal/gamescope-portals`, which lacks `gamescope-portals.conf`: no backend, no OpenURI, so no app in -the Steam session can open links. +the Steam session can open links. Its two backends (gamescope, holo) also +have no FileChooser, so Flatpak apps fall back to a dialog inside their +sandbox: e.g. Element's Attachments shows an empty file selector without +your home dir. -**What you get:** a working OpenURI portal in the Steam session; the -desktop's portal is unaffected. +**What you get:** a working OpenURI portal in the Steam session and, with +`fileChooser`, KDE's file dialog there (a normal window in VR) with access +to all your files; the app gets only the files you pick. The desktop's +portal is unaffected. -**Remove when** SteamOS ships `gamescope-portals.conf`. +**Configuration:** takes effect when the portal restarts (next Steam session +start, or `systemctl --user restart xdg-desktop-portal`) and the app +restarts (Electron apps check for the portal at startup). To keep the old +dialog: `steamFrame.session.portalFix.fileChooser = false;`. + +**Remove when** SteamOS ships `gamescope-portals.conf` (and a FileChooser +backend, for `fileChooser`). **How it works:** a portal dir in `~/.local/share` linking Valve's `.portal` files plus a config (`default=holo;gamescope`), and a drop-in on -`xdg-desktop-portal.service`. +`xdg-desktop-portal.service`. `fileChooser` adds a link to +`/usr/share/xdg-desktop-portal/portals/kde.portal` and +`org.freedesktop.impl.portal.FileChooser=kde`; the preinstalled +`xdg-desktop-portal-kde` starts on demand (D-Bus activation) with the Steam +session's `DISPLAY=:0`. ## Applications menu diff --git a/flake.nix b/flake.nix index 2bb6eed..5f889fb 100644 --- a/flake.nix +++ b/flake.nix @@ -66,14 +66,15 @@ # Tests of the VR keyboard (text model, corrector, swipe decoder on the # default German + English dictionary), the Jellyfin mpv shim, the - # Firefox wrapper, the launchers, install.sh cleanup and the - # applications.menu link: nix flake check + # Firefox wrapper, the launchers, install.sh cleanup, the + # applications.menu link and the portal config: nix flake check checks = forSystems (pkgs: { applications-menu = import ./modules/applications-menu/check.nix { inherit 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; }; + portal = import ./modules/portal/check.nix { inherit pkgs; }; vr-keyboard = import ./modules/vr-keyboard/check.nix { inherit pkgs; }; }); diff --git a/modules/portal.nix b/modules/portal.nix index 717b5b2..d64c37e 100644 --- a/modules/portal.nix +++ b/modules/portal.nix @@ -3,8 +3,11 @@ # gamescope-portals.conf, so no backend is selected and there is no OpenURI # (links don't open). We point it at our own dir: Valve's .portal files + a # config. Remove once SteamOS ships the .conf. +# Neither Valve backend has a FileChooser, so sandboxed apps fall back to an +# in-sandbox dialog that can't see home; fileChooser adds KDE's for it. { config, lib, ... }: let + cfg = config.steamFrame.session.portalFix; sys = "/usr/share/xdg-desktop-portal/gamescope-portals"; dir = "xdg-desktop-portal/gamescope-portals"; in { @@ -15,23 +18,40 @@ in { [ "steamFrame" "session" "portalFix" "enable" ]) ]; - options.steamFrame.session.portalFix.enable = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Give the Steam session's xdg-desktop-portal a config so apps there can - open links (OpenURI). - ''; + options.steamFrame.session.portalFix = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Give the Steam session's xdg-desktop-portal a config so apps there can + open links (OpenURI). + ''; + }; + fileChooser = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Use KDE's file dialog (xdg-desktop-portal-kde) as the Steam session's + FileChooser portal, so sandboxed apps (Flatpak) can open and save + files outside their sandbox. Needs `enable`. + ''; + }; }; - config = lib.mkIf config.steamFrame.session.portalFix.enable { + config = lib.mkIf cfg.enable { xdg.dataFile."${dir}/holo.portal".source = config.lib.file.mkOutOfStoreSymlink "${sys}/holo.portal"; xdg.dataFile."${dir}/gamescope.portal".source = config.lib.file.mkOutOfStoreSymlink "${sys}/gamescope.portal"; + xdg.dataFile."${dir}/kde.portal" = lib.mkIf cfg.fileChooser { + source = config.lib.file.mkOutOfStoreSymlink + "/usr/share/xdg-desktop-portal/portals/kde.portal"; + }; xdg.dataFile."${dir}/gamescope-portals.conf".text = '' [preferred] default=holo;gamescope + '' + lib.optionalString cfg.fileChooser '' + org.freedesktop.impl.portal.FileChooser=kde ''; # Outer (systemd) portal only; the nested desktop's keeps kde-portals.conf. diff --git a/modules/portal/check.nix b/modules/portal/check.nix new file mode 100644 index 0000000..d626cd8 --- /dev/null +++ b/modules/portal/check.nix @@ -0,0 +1,36 @@ +# Evaluates the portal module against a stub of Home Manager's options: on by +# default the Steam session's portal config picks KDE's FileChooser, with +# `fileChooser = false` it has neither the kde.portal link nor that line. +{ pkgs }: +let + inherit (pkgs) lib; + dir = "xdg-desktop-portal/gamescope-portals"; + eval = cfg: (lib.evalModules { + modules = [ + ../portal.nix + { + options.xdg.dataFile = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf lib.types.anything); + default = { }; + }; + options.lib = lib.mkOption { type = lib.types.attrsOf lib.types.anything; }; + config = { + _module.check = false; # Home Manager options the stub doesn't declare + _module.args.pkgs = pkgs; + lib.file.mkOutOfStoreSymlink = p: "out-of-store:${p}"; + }; + } + cfg + ]; + }).config.xdg.dataFile; + on = eval { }; + off = eval { steamFrame.session.portalFix.fileChooser = false; }; + kde = "org.freedesktop.impl.portal.FileChooser=kde"; +in +assert on."${dir}/kde.portal".source + == "out-of-store:/usr/share/xdg-desktop-portal/portals/kde.portal"; +assert lib.hasInfix kde on."${dir}/gamescope-portals.conf".text; +assert !(off ? "${dir}/kde.portal"); +assert !(lib.hasInfix kde off."${dir}/gamescope-portals.conf".text); +assert off ? "${dir}/holo.portal"; +pkgs.runCommand "portal-check" { } "touch $out"