# GtkHx — Flatpak manifest, GNOME 48 runtime, local-source variant. # # Build: # flatpak-builder --user --install --force-clean build-flatpak \ # com.nasledov.gtkhx.yml # # Run: # flatpak run com.nasledov.gtkhx # # Distribution target is Flathub-ready. The app-id, license tags, # AppStream metainfo, and OARS content_rating in # data/com.nasledov.gtkhx.metainfo.xml all line up with what # Flathub's review bot looks for. To submit, swap the `dir` source # below for a `git` source pointing at github.com/nasledov/gtkhx # at a tagged commit; the rest of the manifest stays put. # # Runtime choice: GNOME 49 (the GNOME 48 runtime went end-of-life # 2026-03-24). 49 ships GTK 4.20 / libadwaita 1.8 / glib 2.86, # well above GtkHx's meson floor (gtk4 >= 4.6, libadwaita >= 1.6). # gtksourceview-5 (syntax-highlighted source preview viewer) is # present in the runtime; poppler-glib, libpanel, and imagemagick # are NOT (poppler-glib was dropped in 49; libpanel isn't part of # the GNOME runtime; imagemagick is too large to ship by default). # We build those as inline modules below. Alert sounds no longer # need libcanberra/gsound — the in-tree hxsound crate uses the # runtime's own alsa-lib (see the modules note and finish-args). # # poppler-glib and gtksourceview-5 are declared optional in the # meson build; both are auto-detected and HAVE_POPPLER / # HAVE_GTKSOURCEVIEW config flags gate the corresponding # src/preview.c viewers. PDFs and source files fall through to the # plain-text viewer if either lib is absent on the build host. # # Glycin migration (docs/image-decoding.md G.5): the # Rust hx-image-decode crate consumes the `glycin` crate, which # at decode time spawns a sandboxed subprocess from # /usr/libexec/glycin-loaders/2+/ (glycin-image-rs covers the # JPEG/PNG/GIF allowlist; the heif/svg/jxl siblings live there # too but our sniff layer rejects those formats before they # ever spawn). The loaders + libglycin-2 / libseccomp / # fontconfig / lcms2 surface are all part of the GNOME 49 # runtime — no extra modules needed. # # Sandbox subprocess mechanism: inside Flatpak glycin uses # `flatpak-spawn` to launch loaders, which requires # --talk-name=org.freedesktop.Flatpak on the host's D-Bus. # Without it glycin's SandboxSelector::Auto fails to spawn # the loader and decode returns UnsupportedFormat for every # image. See the finish-args block below. app-id: com.nasledov.gtkhx runtime: org.gnome.Platform runtime-version: "49" sdk: org.gnome.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.rust-stable command: gtkhx build-options: append-path: /usr/lib/sdk/rust-stable/bin # Pin the install libdir for every inline module (libpanel, poppler, # imagemagick). Left to itself, meson picks lib vs lib64 by probing the # *build host* for a real /usr/lib64, so which one we get is a property of # whatever org.gnome.Sdk//49 snapshot the build machine happened to have — # it silently flipped from lib to lib64 partway through the 49 series. # # That matters at runtime, not just at build time. Flatpak guarantees exactly # one search path for app-private libraries, LD_LIBRARY_PATH=/app/lib, and we # set no install_rpath. Anything landing in /app/lib64 is then reachable only # through the ld.so.cache flatpak regenerates per (app, runtime) pair on the # user's own machine, which depends on their runtime commit and flatpak # version — so the identical bundle starts on one box and dies on another # with "libpanel-1.so.1: cannot open shared object file". Pin it and the # question doesn't arise. Also keeps the typelibs in /app/lib, where the # default GI_TYPELIB_PATH looks. libdir: /app/lib # Belt and braces: if some future module's buildsystem ignores libdir and # installs to lib64 anyway, a later module's `dependency('libpanel-1')` would # fail to find its .pc file. Costs nothing when the directory doesn't exist. # (tools/build-flatpak-bundle.sh fails the build if one shows up.) append-pkg-config-path: /app/lib64/pkgconfig # Sandbox permissions. Each line is justified — Flathub reviewers # expect a finish-args list that's the minimum for the app to work. finish-args: # Connecting to Hotline servers (TCP 5500/5501 by default). - --share=network # X11 fallback for non-Wayland sessions; GTK 4 prefers Wayland # but Linux desktops with i3 / older Xfce / KDE-on-X11 still # exist and shouldn't be left out. - --share=ipc - --socket=fallback-x11 - --socket=wayland # GPU acceleration for GTK 4's Vulkan/OpenGL renderer. Falls # back to software rendering if /dev/dri isn't present, but # without dri access the renderer would log a stream of # "no GPU" warnings on every redraw. - --device=dri # Audio I/O: chat/msg/news ping sounds via the hxsound crate # (rodio/cpal, ALSA backend), and the voice-chat extension's # full-duplex capture + playback via GStreamer's autoaudiosrc / # autoaudiosink. # # --socket=pulseaudio exposes $XDG_RUNTIME_DIR/pulse/native to # the sandbox; on every modern desktop pipewire-pulse provides # that socket on top of PipeWire. The runtime's alsa-lib routes # its default PCM to that pulse socket, so cpal's ALSA output and # GStreamer's default pulsesrc / pulsesink elements work # transparently. # Capture goes through here as well — PipeWire-pulse doesn't # gate microphone access separately from playback. # # --filesystem=xdg-run/pipewire-0 additionally exposes the # native PipeWire socket at $XDG_RUNTIME_DIR/pipewire-0, so # GStreamer's pipewiresrc / pipewiresink (preferred by # autoaudio* in 1.22+) can pick it up. Same shape Discord, # Element, Signal, and OBS ship. # # We deliberately do NOT request --device=all (raw /dev/snd # access — too broad for Flathub review). A dedicated Audio # portal that would let us drop the PipeWire socket exposure in # favour of a per-app permission prompt is under discussion # upstream (flatpak/xdg-desktop-portal #1129) but hasn't # shipped — revisit once it does. - --socket=pulseaudio - --filesystem=xdg-run/pipewire-0 # ~/Downloads access for received-file destinations. The user # picks the path in Settings; XDG_DOWNLOAD_DIR is the default. # Without this the file-transfer save dialog can't write # anywhere outside the app's private $XDG_DATA_HOME. - --filesystem=xdg-download # Tray icon support (src/tray.c). We implement # StatusNotifierItem + DBusMenu directly over GDBus rather than # depending on libayatana-appindicator, so there's no extra # module in this manifest. The two D-Bus permissions below are # needed because Flatpak's default policy only lets us own # names under our app-id prefix: # # - own-name: each SNI app registers a unique name shaped # "org.kde.StatusNotifierItem--1" (hyphens, not dots — # it's a single bus-name segment). Flatpak's bus-filter # wildcards only accept ".*" as a complete trailing # segment, so "org.kde.StatusNotifierItem-*" gets rejected # by newer flatpak-builder at finalization ("Invalid dbus # name"). Broaden to "org.kde.*" — any subname under # org.kde., which our specific name matches. Same # workaround other tray-using flatpaks (KeePassXC, # QGIS, ...) use. # # - talk-name: we call RegisterStatusNotifierItem on the # watcher (KDE Plasma's kded, GNOME's AppIndicator # extension, MATE/Cinnamon/Budgie's panel applet, # waybar's tray module, etc.). Without it the proxy call # returns AccessDenied and the icon registers locally but # never reaches a host. - --own-name=org.kde.* - --talk-name=org.kde.StatusNotifierWatcher # Desktop notifications (src/notify.c). GApplication routes # g_notification_send to org.freedesktop.Notifications on the # session bus — the standard notification daemon (mako, dunst, # GNOME Shell, KWin, etc.). Without this permission the call # silently no-ops behind xdg-dbus-proxy. - --talk-name=org.freedesktop.Notifications - --talk-name=org.freedesktop.portal.Desktop # Inline-media decode (glycin G.5). Glycin's # SandboxSelector::Auto detects /.flatpak-info and switches # to flatpak-spawn, which calls into the host's Flatpak # D-Bus service to launch the loader subprocess. Without # this permission glycin can't spawn the loader and every # image decode would return UnsupportedFormat — the # inline-media auto-fetch path silently degrades to the # styled placeholder, the click-to-view dialog shows the # error page. Same shape Loupe and Image Viewer use. - --talk-name=org.freedesktop.Flatpak modules: # Alert-sound playback is the in-tree Rust hxsound crate (rodio / # cpal, ALSA backend) — no inline module needed. The freedesktop # base runtime ships alsa-lib (headers in the Sdk, libasound.so.2 # in the Platform), so cargo builds cpal's alsa-sys and the app # links against it without us vendoring anything. The old gsound / # libcanberra inline modules were dropped when hxsound replaced # that path. # ImageMagick — used as the heavy fallback for QuickDraw PICT # files. PICTs are still common on Hotline servers (classic Mac # screencapture output); the cheap embedded-image sniff in # src/pict_embed.c handles JPEG/PNG/etc. wrapped in PICT v2, but # the long tail of classic raster-opcode PICTs needs a full # QuickDraw decoder. ImageMagick has carried one for ~25 years. # # We restrict the build to the subset of delegates and coders # actually used by src/pict_magick.c — PICT in, raw RGBA out via # MagickExportImagePixels. That keeps the install footprint # modest. --disable-installed runs the test suite from the build # tree without polluting the prefix. - name: imagemagick config-opts: - --disable-static - --disable-docs - --disable-installed - --without-utilities - --without-magick-plus-plus - --without-perl - --without-x - --without-modules # Delegate libraries we don't need for PICT decoding. - --without-bzlib - --without-djvu - --without-fontconfig - --without-freetype - --without-fpx - --without-gslib - --without-gvc - --without-heic - --without-jbig - --without-lcms - --without-lqr - --without-lzma - --without-openexr - --without-openjp2 - --without-pango - --without-raqm - --without-raw - --without-rsvg - --without-tiff - --without-webp - --without-wmf - --without-xml - --without-zip - --without-zstd # Pinned to 7.1.2-18 (released 2026-03-22). The 7.x API has been # stable for years; bump to a newer 7.1.x point release whenever # you regenerate the sha256. New releases are at # https://github.com/ImageMagick/ImageMagick/releases sources: - type: archive url: https://github.com/ImageMagick/ImageMagick/archive/refs/tags/7.1.2-18.tar.gz sha256: 2db8a1f9bac19831c76574e4fd514ecb80a71a49911ee5dc8c1f3fb6d9d550df # Poppler — used for the PDF preview viewer in src/preview.c. # GNOME 49 dropped poppler-glib from the runtime; without this # module the meson dependency check returns "poppler-glib: no" # and HAVE_POPPLER stays unset, so PDFs fall through to the # plain-text viewer (i.e. binary noise on screen). # # We only need the GLib bindings and the core PDF parsing — # disable the Qt5/Qt6 frontends, the standalone command-line # utilities (pdfinfo, pdftotext, etc.), the cpp bindings, and # the test/manual builds. Boost, gpgme, libcurl, NSS-based # signature verification, and OpenJPEG are all off too — # rasterising PDF pages to Cairo doesn't touch any of those # code paths, and trimming them keeps the build footprint # small. libpng / libjpeg / libtiff stay enabled (in the # runtime already, and JPEG-in-PDF is common enough that # disabling DCT decoding would be noticeable). - name: poppler buildsystem: cmake-ninja config-opts: - -DCMAKE_BUILD_TYPE=Release - -DENABLE_GLIB=ON - -DENABLE_CPP=OFF - -DENABLE_QT5=OFF - -DENABLE_QT6=OFF - -DENABLE_UTILS=OFF - -DENABLE_BOOST=OFF - -DENABLE_GPGME=OFF - -DENABLE_LIBCURL=OFF - -DENABLE_NSS3=OFF - -DENABLE_LIBOPENJPEG=none - -DBUILD_GTK_TESTS=OFF - -DBUILD_QT5_TESTS=OFF - -DBUILD_QT6_TESTS=OFF - -DBUILD_CPP_TESTS=OFF - -DBUILD_MANUAL_TESTS=OFF - -DENABLE_GTK_DOC=OFF # Pinned to 26.05.0 (released 2026-05-03). Bump and regen sha256 # from https://poppler.freedesktop.org/releases.html as needed. sources: - type: archive url: https://poppler.freedesktop.org/poppler-26.05.0.tar.xz sha256: 6fef27ff04f37db43054c86bcdff6128c9fb1f6af4ef3c8b369a7e9abd68d0bb - name: libpanel buildsystem: meson sources: - type: archive url: https://download.gnome.org/sources/libpanel/1.10/libpanel-1.10.4.tar.xz sha256: 593888a7691f0af8aaa6e193c9e14afa86a810c0c2f27515c6d813f18733b1cd - name: gtkhx buildsystem: meson config-opts: - --buildtype=release # GNOME 49 ships glycin's 2+ loader generation, so the image # decoder uses the glycin 3.x backend. Pinned explicitly rather # than relying on the meson default (`auto`, which probes the build # host) so the Flatpak build is deterministic regardless of what # the build environment happens to have installed. (Native builds # default to auto-detect; Debian-stable packagers can also force # -Dglycin_compat=1 — see docs/image-decoding.md.) - -Dglycin_compat=2 build-options: env: CARGO_HOME: /run/build/gtkhx/cargo sources: # Local-source variant: builds from the working tree. Swap # this for a `type: git` block when targeting a Flathub # submission. - type: dir path: . # Pre-downloaded Cargo dependencies (the Flatpak build sandbox # has no network access). Regenerate with: # python3 tools/flatpak-cargo-generator.py rust/Cargo.lock - rust/cargo-sources.json