diff --git a/.gitignore b/.gitignore index ba2478f..69321fb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,10 @@ Cargo.lock.bak # tools/film-profiles/convert.py --fetch. Not source: the converted # profiles in core/dr-film/profiles are. tools/film-profiles/upstream/ + +# flatpak-builder's cache and its output tree. `packaging/flatpak/` holds the +# manifest, which is source; everything a build derives from it is not — and +# `.flatpak-builder/` in particular caches an unpacked copy of the whole +# checkout, so it is larger than the repository it sits in. +/.flatpak-builder/ +/build/ diff --git a/packaging/flatpak/paris.tourolle.darkroom.yml b/packaging/flatpak/paris.tourolle.darkroom.yml new file mode 100644 index 0000000..c6b9c27 --- /dev/null +++ b/packaging/flatpak/paris.tourolle.darkroom.yml @@ -0,0 +1,186 @@ +# Flatpak manifest — FR-PLAT-LIN-3 (sandboxed distribution), NFR-COMPAT-2. +# +# YAML rather than JSON because this file has more to explain than to declare, +# and JSON cannot hold a comment. flatpak-builder reads both. +# +# Build it, from the repository root: +# +# flatpak-builder --user --install --force-clean \ +# build/flatpak packaging/flatpak/paris.tourolle.darkroom.yml +# +# Read docs/distribution.md before changing any permission below. Every line in +# `finish-args` is a hole in the sandbox, and the one that is conspicuously +# absent — `--filesystem=` — is absent on purpose and is explained there. +id: paris.tourolle.darkroom + +# 25.08 is the current freedesktop runtime, and the choice is made by what the +# binary needs rather than by what is newest. `ldd` on a release build names +# fontconfig, freetype, expat, libpng, zlib, brotli and bzip2 — all in the +# Platform — and nothing else: Vulkan, libxkbcommon and the two display-server +# protocols are reached without a link-time dependency (wgpu dlopens +# libvulkan.so.1, and x11rb and wayland-client speak the wire protocols in Rust +# rather than binding libxcb or libwayland). So the runtime has to supply a +# Vulkan loader and an ICD at *runtime*, which is the GL extension's job, and +# not much else. +runtime: org.freedesktop.Platform +runtime-version: '25.08' +sdk: org.freedesktop.Sdk + +# The Rust toolchain is an SDK extension rather than something this manifest +# installs, so the build is offline-capable in the part that matters and the +# compiler is the one freedesktop tested against its own glibc. +# +# Note what this quietly overrides: `rust-toolchain.toml` pins 1.92.0, and that +# pin is honoured by *rustup*, which is not what the extension provides. The +# extension's cargo therefore ignores the file and builds with its own stable +# (1.98.0 on 25.08). That is fine here and deliberately different from +# CONTRIBUTING.md's "do not override the toolchain": the pin exists so `cargo +# fmt --check` and `clippy -D warnings` agree between a laptop and CI, and +# neither runs in this build. A *release binary* only needs a compiler at or +# above the workspace's `rust-version`. +sdk-extensions: + - org.freedesktop.Sdk.Extension.rust-stable + +command: darkroom-desktop + +finish-args: + # FR-PLAT-LIN-2 asks for both display servers. `fallback-x11` rather than + # `x11`: it grants the X socket only when Wayland is unavailable, so a + # Wayland session does not leave an X11 hole open beside the socket actually + # in use. `--share=ipc` goes with it — without it X11 cannot use shared + # memory and every frame is pushed through the socket instead. + - --socket=wayland + - --socket=fallback-x11 + - --share=ipc + + # The GPU. The develop pipeline is compute shaders through wgpu and there is + # no CPU renderer behind it, so this is not an optimisation: without + # /dev/dri the application starts and cannot develop anything. + # + # `dri` rather than `all`: it covers the render nodes and the NVIDIA device + # nodes, which is the whole of what a Vulkan ICD opens. `all` would add every + # other device on the machine for no gain. + - --device=dri + + # Nextcloud (FR-NC-*). Nothing else here reaches the network — face grouping, + # segmentation and lens correction are all local and stay local (NFR-SEC-5). + - --share=network + + # Credential storage (FR-NC-2). The keyring crate speaks the Secret Service + # D-Bus interface directly, which GNOME Keyring and KWallet's `ksecretd` both + # implement, so what it needs is a talk hole to that well-known name. + # + # Worth being precise, because FR-PLAT-LIN-3 says "Secret Service portal" and + # these are two different things: xdg-desktop-portal's `org.freedesktop. + # portal.Secret` hands an application a master key for a store it keeps + # itself, whereas this talks to the session's secret daemon. Only the latter + # puts the app password where `secret-tool` and Seahorse can see it, which is + # what makes a credential individually revocable by the user rather than + # opaque inside our own data directory. If no daemon answers, FR-NC-2's + # degraded mode is what the user gets — the same behaviour as outside a + # sandbox, which is the point. + - --talk-name=org.freedesktop.secrets + + # No `--filesystem=` line of any kind, and this is the substance of + # FR-PLAT-LIN-3 rather than an omission. + # + # What that leaves working: /run/user/$UID/doc is mounted in every sandbox, so + # a photograph opened from a file manager — the .desktop entry declares the + # RAW MIME types and `Exec=darkroom-desktop %F` — arrives as a document-portal + # path in argv and opens. That path is genuinely portal-mediated and needs no + # code change. + # + # What that leaves broken: choosing a *library root*. The folder connector + # takes a typed absolute path (`SignIn::EndpointOnly`, placeholder + # `/home/you/Pictures`) and checks it with `std::fs`, and nothing in the tree + # calls the FileChooser portal — there is no ashpd, no rfd, no toolkit dialog. + # A path typed into that field does not exist in this sandbox, so the launch + # screen refuses it with "that folder does not exist", which is at least an + # honest error. + # + # `--filesystem=host` would make that work today and is exactly what the + # requirement forbids, so it is not here. docs/distribution.md §4 records what + # closes the gap and how to run a Flatpak build in the meantime. + +modules: + - name: darkroom + buildsystem: simple + + build-options: + append-path: /usr/lib/sdk/rust-stable/bin + env: + # Inside the build sandbox rather than in $HOME, so a rebuild starts + # from the state flatpak-builder is managing and not from whatever the + # host's cargo cache happens to hold. + CARGO_HOME: /run/build/darkroom/cargo + # Cargo fetches 826 crates, and Flathub's builders forbid this — a + # submission there needs `cargo-sources.json` generated by + # flatpak-builder-tools' `flatpak-cargo-generator.py` from Cargo.lock, + # listing every crate as its own source, plus a vendored-registry + # `.cargo/config.toml`. That file is ~30k lines, has to be regenerated on + # every dependency change, and buys nothing for a build from a local + # checkout, which is what this manifest is for and what packaging/PKGBUILD + # is for as well. Add it when there is a Flathub submission, not before. + build-args: + - --share=network + + build-commands: + # `--locked` for the reason CI uses it: a lockfile that resolves + # differently in the packaging build than in the tree is a release whose + # dependency versions nobody chose. + - cargo build --release --locked -p darkroom-desktop + + - install -Dm755 target/release/darkroom-desktop /app/bin/darkroom-desktop + + - install -Dm644 packaging/paris.tourolle.darkroom.desktop + /app/share/applications/paris.tourolle.darkroom.desktop + + - install -Dm644 packaging/paris.tourolle.darkroom.metainfo.xml + /app/share/metainfo/paris.tourolle.darkroom.metainfo.xml + + # The icon's name is the contract, not its path — the desktop entry says + # `Icon=paris.tourolle.darkroom` and the shell resolves that through the + # hicolor theme. 256x256 because that is the source's actual size; + # installing it under a size it is not makes scaled icons look wrong. + - install -Dm644 ui/dr-ui/ui/app-icon.png + /app/share/icons/hicolor/256x256/apps/paris.tourolle.darkroom.png + + # The face models, where `system_face_models_dirs()` looks: it reads + # $XDG_DATA_DIRS, which includes /app/share inside a Flatpak, so this is + # the same lookup that finds /usr/share/darkroom/models from the Arch + # package. Last in the search order, so a pair the user dropped in their + # own data directory still outranks these. + # + # These live in Git LFS. A checkout made without `git lfs pull` has + # ~130-byte pointers here, and `type: dir` below would copy the pointers + # in without complaint — producing a Flatpak whose face indexing fails + # inside the graph loader on the user's machine. Refuse instead, with the + # command that fixes it. + - | + for m in scrfd_500m_640.onnx arcface_mbf_b1.onnx; do + if [ "$(stat -c%s "models/face/$m")" -lt 100000 ]; then + echo "error: $m is an LFS pointer, not a model — run: git lfs pull" >&2 + exit 1 + fi + install -Dm644 "models/face/$m" "/app/share/darkroom/models/$m" + done + + - install -Dm644 README.md /app/share/doc/darkroom/README.md + + sources: + # The local checkout, for the same reason packaging/PKGBUILD builds from + # one: this makes a Flatpak of what you are actually working on. Swap it + # for an `archive` or `git` source with a tag when there is a release to + # point at. + # + # `skip` is not tidiness. `target/` is tens of gigabytes and `.git` with + # LFS objects is not small; flatpak-builder copies a `dir` source + # wholesale, so without these two lines the copy is the slowest part of + # the build by a wide margin. + - type: dir + path: ../.. + skip: + - target + - target-android + - .git + - build