Say the folder dialogue is the portal, and what the Flatpak has not proved

distribution.md §4, outstanding.md's FR-PLAT-LIN-3 entry, the Flatpak
manifest's comment and the README all said a library was chosen by
typing a path and that nothing in the tree called the FileChooser
portal. Since 6683c14 every folder the desktop asks for is chosen
through rfd's xdg-portal backend, so those sentences were false.

What they now say instead is narrower than "it works in the sandbox":
no Flatpak has been built here, so whether the portal's path opens a
library, holds across a restart and takes a sidecar is unobserved, and
volumes() still cannot see a host card. The chooser also landed in ui/
rather than behind the dr-plat seam distribution.md had proposed, and
both documents say so. FR-PLAT-LIN-3 gets a status note to the same
effect.
This commit is contained in:
2026-09-26 14:52:44 -04:00
parent e57c5b8182
commit caaae11d98
5 changed files with 98 additions and 67 deletions
+58 -44
View File
@@ -18,7 +18,7 @@ permission to a package rather than after.
| Platform | Channel | State | What it constrains |
|---|---|---|---|
| Linux | Arch source package — [`packaging/PKGBUILD`](../../packaging/PKGBUILD) | Built, in tree | Nothing. Full filesystem access, system Vulkan, system secret daemon |
| Linux | Flatpak — [`packaging/flatpak/`](../../packaging/flatpak/) | Manifest in tree, **library selection does not work** (§4) | Portals only. No `--filesystem=`, no host mount table, no typed paths |
| Linux | Flatpak — [`packaging/flatpak/`](../../packaging/flatpak/) | Manifest in tree; folders chosen through the FileChooser portal since 0.17.0, **never built or run here** (§4) | Portals only. No `--filesystem=`, no host mount table, no typed paths |
| Linux | AppImage | v1 channel, **recipe not yet written** (§5) | Oldest supported glibc, and no sandbox at all |
| Android | F-Droid | v1 channel, not yet submitted | GPLv3-clean build, reproducible, no proprietary blobs |
| Android | Play Store | **Not v1** (§6) | Would make ARCH §6.9 binding as policy rather than as engineering |
@@ -78,7 +78,7 @@ The Arch package and an AppImage both hand the application the same
unrestricted process the developer runs it in, so neither can discover that a
design assumed unrestricted access. Flatpak takes that assumption away, and
FR-PLAT-LIN-3 exists to make the discovery happen deliberately rather than in a
bug report. §4 is what it discovered.
bug report. §4 is what it discovered, and what has been done about it.
The same argument runs the other way on Android, where SAF has been the only
option since before the first line was written (ARCH §6.9) and `SourceRef`
@@ -120,31 +120,55 @@ advance:
---
## 4. What does not work: choosing a library
## 4. Choosing a library: built, not yet proved in the sandbox
**FR-PLAT-LIN-3 is not satisfied today, and the manifest does not pretend
otherwise.**
**FR-PLAT-LIN-3 was not satisfied up to 0.16.0, and is still not shown to
be.** What changed in 0.17.0 is the code; what has not changed is that no
Flatpak has been built here, so nothing below has been observed inside one.
A folder library is chosen by typing an absolute path. `dr-sync-folder`'s
provider declares `SignIn::EndpointOnly` with the placeholder
`/home/you/Pictures`, and `normalise_endpoint` expands `~`, requires the path
to be absolute, and checks it with `std::fs`. Nothing in the tree calls the
FileChooser portal — there is no `ashpd`, no `rfd`, and no toolkit file dialog
anywhere in `ui/`, `platform/` or `core/`.
Up to 0.16.0 a folder library was chosen by typing an absolute path, and
nothing in the tree called the FileChooser portal. Inside a sandbox with no
`--filesystem=`, `$HOME` still resolves to the real home *path* but that
directory holds only the application's own `.var/app/…` tree, so a typed
`~/Pictures` failed the `exists()` check and the launch screen said so — a
truthful message about a situation the user could not fix from inside the
application.
Inside a sandbox with no `--filesystem=`, `$HOME` still resolves to the real
home *path* but that directory holds only the application's own
`.var/app/…` tree. So a typed `~/Pictures` fails the `exists()` check and the
launch screen says `No folder at /home/you/Pictures.` — a truthful message
about a situation the user cannot fix from inside the application.
**Every folder the desktop asks for is now chosen in the platform's
dialogue** (FR-EXP-6): the library folder on the launch screen (`Choose
folder…`), an import's source and second copy, a folder or file of
Lightroom presets, and an album's folder on this device.
`ui/dr-ui/src/folder_dialog.rs` asks through `rfd` with its `xdg-portal`
backend — `org.freedesktop.portal.FileChooser` over D-Bus, which Flatpak
always permits without a `--talk-name` — and the common item dialogue on
Windows. No path is typed anywhere on the desktop any more; Android keeps
its fields (`Pickers.local-paths`), because SAF returns document trees rather
than paths.
Import is blocked one step earlier. `dr_plat::volumes()` finds a camera card by
reading `/proc/self/mountinfo` and the `removable` flag under `/sys`. A
What the portal hands back inside a sandbox is a path under
`/run/user/$UID/doc/` that the document portal has exported, and the chosen
library goes through the same `normalise_endpoint` a typed one did, which
checks it with `std::fs`. That is the route this section used to ask for —
`rfd` drives `ashpd` underneath, the crate it named. Two things differ from
the plan, and both are stated rather than smoothed over:
- **It is not behind a platform seam.** The plan put the chooser beside
`LocalStorage::grant` in `dr-plat`, the one place a `Path` enters the
application. It is in `ui/`, and the path it returns reaches the folder
connector as a string, as a typed one did.
- **Nothing has confirmed the sandbox half.** Whether the exported path
still resolves after a restart — a library is remembered across launches,
so it has to — and whether the export is writable, so a sidecar can be
written beside a photograph, are the first two things a Flatpak build has
to check.
Import is still blocked one step earlier. `dr_plat::volumes()` finds a camera
card by reading `/proc/self/mountinfo` and the `removable` flag under `/sys`. A
sandboxed process is in its own mount namespace, so the table it reads
describes the sandbox; a card mounted at `/run/media/…` on the host is not in
it. `volumes()` correctly returns an empty list, which the interface presents
as "no card found" — right for the code, wrong for the user, who is looking at
a card.
as "no card found" — but the import page now offers `Browse…` beside that
message, and the dialogue it opens is the portal's, which can reach the card.
### The permission that would hide this, and why it is not in the manifest
@@ -152,41 +176,31 @@ a card.
names as the alternative to portals. Granting it would mean the sandboxed build
never exercises the sandbox, which removes the entire reason for shipping one
(§2). `--filesystem=xdg-pictures` is narrower and would be tempting, but it is
still a static grant that lets a typed path resolve — it makes the same design
work by not testing it, only in a smaller directory.
still a static grant that lets a path resolve without the portal — it makes
the same design work by not testing it, only in a smaller directory.
So the manifest grants no filesystem access at all. The consequence is stated
plainly: **a Flatpak built from this manifest can open photographs handed to it
and cannot yet be pointed at a library.**
So the manifest grants no filesystem access at all, and a Flatpak built from
it reaches the user's photographs only through what the portal hands it.
### What closes it
Two changes, in this order:
1. **A portal file chooser behind a platform seam.** `ashpd`'s
`OpenFileRequest` with `directory(true)` returns a URI the document portal
has exported, which the sandbox can read and which stays valid across
restarts. It resolves to a real path under `/run/user/$UID/doc/`, so
`normalise_endpoint` accepts it as it stands — `canonicalize()` on a fuse
path returns the path itself. The seam matters more than the crate: this
belongs beside `LocalStorage::grant` in `dr-plat`, which is already the one
place a `Path` enters the application, and must not become a second way for
`ui/` to learn about paths.
2. **Removable volumes through the same door.** There is no portal for "list
the mounted cards". The honest answer is that under a sandbox
`imports_supported()` should report the same `false` it reports on Android,
for the same reason it gives there — the operation cannot be performed
however hard the user tries — and the import flow should offer the folder
chooser instead of a volume list.
1. **Build it and run it.** `flatpak-builder` is not installed on the machine
this is developed on, so the manifest has never produced a package.
2. **Removable volumes.** There is no portal for "list the mounted cards".
Under a sandbox `imports_supported()` should report the same `false` it
reports on Android, for the same reason — the volume list cannot be right
however hard the user tries — and leave `Browse…` as the way to a card.
**Done when:** a Flatpak built from
[`packaging/flatpak/paris.tourolle.darkroom.yml`](../../packaging/flatpak/paris.tourolle.darkroom.yml),
with its `finish-args` unchanged and no `flatpak override` applied, can select a
library root, scan it, and write a sidecar back into it.
library root, scan it, write a sidecar back into it, and open it again after a
restart.
### Running a Flatpak build before then
For testing the rest of the application inside the sandbox, grant the access
If the portal's path turns out not to hold across a restart, the rest of the
application can still be tested inside the sandbox by granting the access
per-installation rather than in the manifest, so the file that describes the
application keeps telling the truth: