Skip to content

Add native Linux support (AppImage, .deb, .rpm, Arch PKGBUILD) - #3

Closed
Emanschi wants to merge 17 commits into
LumoRez07:mainfrom
Emanschi:linux-native-build
Closed

Emanschi wants to merge 17 commits into
LumoRez07:mainfrom
Emanschi:linux-native-build

Conversation

@Emanschi

Copy link
Copy Markdown

Summary

Adds a native Linux build of Flow, packaged as an AppImage, .deb, .rpm, and an Arch PKGBUILD — without touching the existing Windows build path (#[cfg(windows)] code and tauri.conf.json are untouched; Linux-specific config lives in tauri.linux.conf.json and tauri.linux.appimage.conf.json, merged in only for Linux builds).

  • Cross-platform core: sleep-prevention (D-Bus/logind), startup error dialog, platform-capability detection, ALSA input-device filtering, vendored/system libvosk.so handling.
  • Desktop integration: tray icon (AppIndicator/StatusNotifierItem), .desktop file, AppStream metainfo, hicolor icons.
  • Packaging: AppImage (with a post-processing fix for bundled Wayland/XCB libraries that otherwise break EGL init on the host's real compositor), .deb/.rpm (vendoring libvosk.so since it isn't a real Debian/Fedora package), an Arch PKGBUILD (using the system vosk-api package instead).
  • CI: .github/workflows/build-linux.yml builds and uploads all three formats, entirely separate from the existing Windows release process.
  • In-app updater: works on the AppImage using the same signing key as Windows; .deb/.rpm/PKGBUILD update through the distro's package manager instead.
  • Docs: BUILDING-LINUX.md, plus a new "Linux" section (with an honest limitations table) added to README.md/README.de.md, and in English to the other six translations pending community translation help.

What doesn't work / known limitations

  • Screen-capture protection is not available on Linux — the underlying API is Windows-only, no X11/Wayland equivalent exists.
  • Native Wayland is not supported for the app's core UX. Always-on-top, absolute window positioning, and global hotkeys all require X11 semantics that Wayland's security model doesn't allow. The app runs via XWayland by default; native Wayland is available as an opt-in (FLOW_ALLOW_WAYLAND=1) but breaks those three features.
  • Remote Control needs a manual firewall exception for TCP port 43127 on distros with a default-deny firewall (most of them) — there's no equivalent to Windows' first-launch Defender Firewall prompt.
  • No official pre-built Linux binaries yet — this PR only adds the build infrastructure; publishing signed releases (and thus a real linux-x86_64 entry in the production latest.json) is a maintainer decision for after merge.
  • Six of the eight README translations only got the new Linux section in English — translation help would be appreciated.

How this was tested

Everything below was verified by hand, not just "it compiles":

  • Native desktop run: Arch Linux, KDE Plasma 6.7 (Wayland host, app running via XWayland) — tray icon menu (all six items, verified over the real D-Bus/StatusNotifierItem/dbusmenu interfaces a real click would use), audio input device listing/filtering, sleep-prevention, aux windows (About/Settings/Input) on a multi-monitor XWayland setup, voice engine against the vendored libvosk.so.
  • AppImage: built on Ubuntu 22.04 (matches the CI runner), then run standalone on the Arch host with no system vosk-api/webkit2gtk installed beyond what the AppImage bundles, confirming it's genuinely self-contained. Also used for a full end-to-end in-app updater test: a real packaged AppImage, running its own unmodified JS update-check flow (no test hooks), against a local test feed — confirmed the running binary was replaced in place with byte-identical v2 content after a real download + signature verification + install cycle.
  • .deb: built, then installed via apt-get install ./flow_*.deb on a clean Ubuntu 24.04 container — resolves all declared dependencies, launches with a real window.
  • .rpm: built, then installed via dnf install ./flow-*.rpm on a clean Fedora 40 container — same verification as the .deb.
  • Arch PKGBUILD: built and installed via makepkg/pacman -U on the host, including the icon-cache/desktop-database refresh scriptlet and a check for icon-name collisions with papirus-icon-theme.
  • CI workflow: dry-run replicated by hand, step-by-step, in a clean-room Ubuntu 22.04 container using the exact commands the workflow YAML specifies — this caught and fixed several real bugs (missing unzip/file packages, an icon-lookup failure specific to the AppImage bundler's shared desktop-template handling, and an updater-artifact exit-code bug) before ever touching a real GitHub Actions run.

Test matrix

Package Build host Install/run tested on
AppImage Ubuntu 22.04 (container) Arch Linux (bare run, no host vosk-api)
.deb Arch Linux (native) Ubuntu 24.04 (container)
.rpm Arch Linux (native) Fedora 40 (container)
PKGBUILD Arch Linux (native) Arch Linux (native)

Windows build/behavior was not touched and was not re-tested as part of this PR (no Windows-specific files were modified).

… module

Ports Flow to compile and run natively on Linux (Arch, verified) while
keeping every Windows code path untouched:

- Gate the `windows`/`winreg` crates to cfg(windows) only, so Cargo no
  longer tries to build Windows-only bindings on Linux.
- Add Linux Vosk linking to build.rs: resolves libvosk.so via
  FLOW_VOSK_LIB_DIR override, a vendored copy, or the system library
  (ldconfig), with rpath set for portable/vendored builds.
- Add packaging/linux/fetch-vosk.sh to fetch and checksum-verify a
  vendored libvosk.so for AppImage/deb/rpm builds that shouldn't depend
  on a system vosk-api install.
- Add packaging/linux/flow-launcher.sh, forcing GDK_BACKEND=x11 (XWayland)
  by default since the overlay's always-on-top/positioning/global
  hotkeys don't work under native Wayland, plus the WebKitGTK DMABUF
  renderer workaround.
- Add src-tauri/src/platform/ with a Linux session_type() helper.
- Filter cpal's ALSA device enumeration on Linux down to the devices
  that make sense to pick as a microphone (raw enumeration mixes in
  ALSA plugin internals and output-only topology variants).
- Add tauri-plugin-single-instance (Unix only) so a second `flow
  --toggle-clickthrough/--show/--hide` invocation controls the running
  instance instead of opening a second window - needed on native
  Wayland where the app can't register its own global hotkeys.
- Use Tauri's cross-platform `.parent()` instead of the Windows-only
  `.owner()` for the remote-inbox window relationship.

Verified on Arch Linux/KDE under XWayland: builds clean, Vosk model
loads and decodes without ABI issues against the system vosk-api
package, tray icon and its menu work end-to-end, global shortcuts
register and fire correctly. See plan/notes/ for full verification
detail.
…, UI

Phase 4 of the Linux port - brings feature parity where possible and
honestly reports where it isn't, plus fixes a pre-existing (not
Linux-specific) window dragging bug found during manual testing.

Platform parity (src-tauri/src/platform/linux.rs):
- Sleep/screensaver prevention via D-Bus: org.freedesktop.ScreenSaver
  Inhibit/UnInhibit and org.freedesktop.login1.Manager.Inhibit("idle"),
  used together since desktop environments vary in which one they honor.
  Verified against `systemd-inhibit --list`.
- Startup error dialog fallback chain: kdialog -> zenity -> notify-send
  -> stderr, since nothing GTK-based can be assumed to work at the point
  this fires. Verified for real: kdialog isn't installed on this KDE
  system, so the fallback to zenity is exercised, not just theoretical.
- New get_platform_capabilities command reports what's actually
  available on this session (os, X11/Wayland, capture protection,
  global shortcuts, in-app updater) instead of assuming Windows-level
  capability everywhere. Fixed a priority bug found while wiring this
  up: session_type() checked XDG_SESSION_TYPE before GDK_BACKEND, so it
  misreported "wayland" even when the launcher's GDK_BACKEND=x11 forces
  the app onto XWayland where those features do work.

Frontend (src/core/platform.js + consumers):
- New platform.js module mirrors distribution.js's cache/promise
  pattern, fails open (assumes full capability) if the query fails, so
  Windows behavior can't regress.
- Settings UI hides capture protection when unavailable (with an
  explanatory line instead of just disappearing), adds a hint about the
  --toggle-clickthrough CLI flags when global shortcuts aren't
  available, and shows a package-manager hint instead of the update
  button when the in-app updater isn't. New i18n strings added in all
  8 languages, not just English.
- Both updater paths (services/updater.js's manual check and
  teleprompter/auto-updater.js's background timer) now gate on
  in-app-updater availability the same way they already gate on
  Microsoft Store builds, so non-AppImage Linux builds don't poll the
  update endpoint or run a dead interval.
- Font fallback chains extended with Inter/Noto Sans/DejaVu Sans and
  Fira Code/DejaVu Sans Mono, Segoe entries kept first so Windows
  rendering is untouched.

Window dragging fix (not Linux-specific):
- Settings, About, and the text editor window had no drag region at
  all, unlike the main window - noticed only now because the main
  window has its own drag-handle overlay and a settings-based position
  picker, so nobody had tried grabbing the others by their header.
  Fixing this took two changes: `data-tauri-drag-region` has to be on
  the exact element the mousedown lands on (Tauri's drag script checks
  e.target directly, it doesn't walk up to parents), and the
  `core:window:allow-start-dragging` permission was missing from
  capabilities/default.json entirely - without it every drag attempt
  was silently rejected by the ACL, main window included.
… X11

center_aux_window_on_active_monitor() queried monitor geometry via
Window::current_monitor(), which returned incorrect coordinates on a
dual-monitor X11/XWayland setup and placed the window in an unrelated
corner of the desktop instead of centering it — making it easy to miss
entirely (reported as "About window doesn't appear").

Center over the anchor (main) window's own outer bounds instead, which
sidesteps monitor enumeration entirely and is more robust across
different multi-monitor configurations.
- packaging/linux/flow.desktop, hicolor icons, and AppStream metainfo for
  desktop-menu integration (KDE Discover, GNOME Software). Both validate
  cleanly with desktop-file-validate and appstreamcli.
- src-tauri/tauri.linux.conf.json: Tauri merge-config for
  `tauri build --config` on Linux (appimage/deb/rpm bundle targets, runtime
  deps, vendored libvosk.so as a bundle resource). tauri.conf.json itself is
  untouched, per the Linux-port ground rules.
Sources a GitHub tag tarball (sha256 left as SKIP with a TODO — this Linux
port has no tagged release yet). build() stages the system vosk-api
package's libvosk.so into the vendored resource path before compiling,
since tauri-build hard-requires that vendored file to exist at build time
even though the package only needs vosk-api at runtime.

Verified end-to-end with a throwaway git+file:// source variant (not
committed): makepkg -si builds and packages cleanly, namcap reports no
real issues on either the PKGBUILD or the built package. Final `pacman -U`
install step needs to be run interactively (requires a sudo password this
sandboxed session doesn't have).
pacman's built-in gtk-update-icon-cache hook only triggers on a brand-new
top-level icon theme directory; it doesn't cover an application package
adding files into the already-installed shared "hicolor" theme. Confirmed
by hand: after installing the PKGBUILD-built package, the app menu showed
a generic icon instead of Flow's icon until the cache was refreshed.

Adds flow-teleprompter.install running gtk-update-icon-cache and
update-desktop-database on install/upgrade/remove, and the two packages
that provide those binaries to depends.
gtk-update-icon-cache/update-desktop-database alone weren't enough on a
real KDE Plasma test install: the app menu still showed a generic icon
until kbuildsycoca6 (KDE's own service/desktop-file cache) was rebuilt.
Guarded with `command -v` so it stays a no-op on non-KDE systems rather
than becoming a hard dependency on KDE Frameworks.
…sion

Root-caused the "generic icon in app menu" report the hard way: files were
staged correctly, kbuildsycoca6/gtk-update-icon-cache ran, plasmashell was
even restarted — none of it helped. GTK's own icon-theme lookup for "flow"
resolved to /usr/share/icons/Papirus/64x64/apps/flow.svg instead of ours;
papirus-icon-theme (a very common third-party theme, active here as
Papirus-Dark) already ships an icon literally named "flow", which the
active theme's search order prefers over our hicolor fallback icon.

Renaming to the more specific "flow-teleprompter" (verified no collision
in Papirus/Papirus-Dark) sidesteps this entirely. StartupWMClass stays
"flow" (matches the real WM_CLASS, unrelated to icon lookup).
…hen)

Root-caused the icon still not showing after the flow-teleprompter rename:
kiconfinder6 (KIconLoader's own exact lookup tool) resolved
"flow-teleprompter" to Papirus's unrelated flow.svg, not our hicolor icon.

The freedesktop icon-naming spec's fallback algorithm strips everything
after the *last hyphen* and retries within the active theme (and its
inheritance chain) before ever consulting hicolor as the final fallback.
"flow-teleprompter" truncates to "flow", which papirus-icon-theme happens
to ship — so the truncated match wins before hicolor is ever reached. Any
hyphenated name is at risk of this for as long as some prefix collides
with an icon in the user's active theme.

Switching to the dotted app-id form used by AppStream/Flatpak conventions
sidesteps the hyphen-fallback path entirely (verified via kiconfinder6: no
match anywhere before adding our own file, so no collision risk). Renames
the desktop file, all icon files, and the metainfo launchable together for
consistency.
Sources the lightweight source tag tarball for desktop/icon/metainfo/
license assets plus a prebuilt flow-linux-$CARCH.tar.gz release asset for
the binary itself, avoiding a full from-source compile. That release
asset doesn't exist yet (depends on Phase 6.3's not-yet-built release
workflow) — flagged in a comment for whoever implements it.

Added mutual provides/conflicts between flow-teleprompter and
flow-teleprompter-bin so pacman treats them as interchangeable.

Verified with a throwaway local variant (git+file:// source + a tarball
built from the already-compiled release binary): makepkg builds and
packages cleanly, namcap shows nothing beyond already-investigated
warnings from the source package plus an expected "unstripped ELF" from
my un-stripped test binary (real CI output would be pre-stripped).
…hase 6.1)

Tauri's AppImage bundling (linuxdeploy + linuxdeploy-plugin-gtk) vendors
Wayland/X11/XCB client protocol libraries, which must match the exact
protocol/ABI version of the host's compositor/X server. Bundling them
means the AppImage's own RUNPATH shadows the host's matching copies,
breaking EGL context creation on any system whose graphics stack differs
enough from the AppImage's build host (reproduced building on Ubuntu
22.04, running on a much newer Arch system: "Could not create default
EGL display: EGL_BAD_PARAMETER").

fix-appimage-libs.sh extracts a built AppImage, removes the known-bad
libraries (libwayland-client/-server/-egl/-cursor, libxcb-randr/-render/
-shm, libxkbcommon), and repacks it. Verified end-to-end: the resulting
AppImage launches with a fully rendering UI and an empty stderr log,
confirmed both via wmctrl/screenshot and by direct user observation on
their desktop.

Also verified (in a fresh vosk-api-free Ubuntu 22.04 container, via ldd)
that the AppImage's vendored libvosk.so resolves correctly without any
system Vosk installation, closing out Task 6.1's core acceptance
criterion.
…ase 6.2)

Two real bugs found by actually installing the built packages:

1. Launch-blocking: neither .deb nor .rpm bundled libvosk.so at all (only
   the AppImage's top-level bundle.resources entry covered that). vosk-api
   isn't a real Debian/Ubuntu/Fedora package (apt-cache search returns
   nothing), so the binary failed at dynamic-link time before main() ran:
   "error while loading shared libraries: libvosk.so: cannot open shared
   object file". Fixed with a deb/rpm-specific `files` mapping to
   /usr/lib/flow/libvosk.so, matching the binary's own RUNPATH.

2. Same icon-name-collision class of bug as the Arch package (5.3): Tauri's
   default deb/rpm bundler auto-generates Icon=flow with empty Categories
   and a stale Windows-only Comment, colliding with papirus-icon-theme's
   own pre-existing "flow" icon -- a real risk on any distro, not just
   Arch, since Papirus is popular everywhere. Fixed by pointing
   desktopTemplate/files at the already-tested
   packaging/linux/app.flowteleprompter.Flow.desktop and hicolor icons
   instead of Tauri's auto-generated ones.

Verified end-to-end: .deb installs and launches on a fresh Ubuntu 24.04
container, .rpm installs and launches on a fresh Fedora 40 container, both
confirmed via wmctrl and direct observation, both showing the correct
custom desktop entry/icon and no library-loading errors.
Discovered while writing the workflow: every tauri build invocation this
whole session (deb, rpm, and appimage alike) has been exiting with a
non-zero status due to "A public key has been found, but no private key"
-- harmless for local testing since the bundle file gets created before
that final step runs, but this would mark every CI step as failed despite
producing the right artifact. Root cause: the base (Windows) tauri.conf.json
sets createUpdaterArtifacts: true globally for the existing Windows
updater, and omitting the key from tauri.linux.conf.json only means
"inherit from base" in JSON-merge semantics, not "reset to the schema
default of false".

Fixed by explicitly setting createUpdaterArtifacts: false in
tauri.linux.conf.json (so deb/rpm/plain appimage builds no longer attempt
signing) and adding tauri.linux.appimage.conf.json as a small additional
--config overlay that re-enables it only for the specific build that wants
updater artifacts, verified via a direct build of each combination.

.github/workflows/build-linux.yml builds AppImage/deb/rpm on ubuntu-22.04
(the RELR/strip-incompatibility-avoiding runner already established in
Phase 6.1), reusing packaging/linux/fetch-vosk.sh and fix-appimage-libs.sh.
The AppImage step only enables updater artifacts if
TAURI_SIGNING_PRIVATE_KEY is configured as a repo secret, so this workflow
doesn't hard-depend on Phase 6.4's key setup landing first. No existing
Windows release automation was touched -- there wasn't any: neither this
checkout nor the actual GitHub repo (checked via the public API) has a
.github/workflows directory at all, so Windows releases are evidently
built and uploaded manually today.
Replicated the entire build-linux.yml step sequence end-to-end inside a
fresh ubuntu-22.04 container from a clean checkout (no push access to
origin or `act` available to trigger real Actions from here). Found and
fixed three real bugs that would have failed in actual CI:

- fetch-vosk.sh needs `unzip`, missing from the runner's apt-get list.
- linuxdeploy's AppImage bundler reuses the deb bundler's desktopTemplate
  internally (no separate field in AppImageConfig), so it inherited our
  custom Icon=app.flowteleprompter.Flow reference, but the icon file was
  only staged via deb.files/rpm.files, which the AppImage bundler doesn't
  read. Added a matching linux.appimage.files section.
- appimagetool needs the `file` command, also missing from the runner.

Verified the corrected sequence end-to-end in one continuous run:
AppImage, .deb, and .rpm all build with exit code 0, and
fix-appimage-libs.sh processes the AppImage successfully in the same
container.
Repacking the AppImage to strip bundled Wayland/X11/XCB libraries changes
its bytes, which invalidates any updater .sig generated for the original
file before this script ran. Without this fix, a CI-built AppImage with
createUpdaterArtifacts enabled would ship a signature that no longer
matches the actual shipped file, silently breaking updater signature
verification for every real user.

Found and verified by hand during Phase 6.4 updater testing: a full
end-to-end self-update test (real packaged AppImage, Flow's own JS
update flow, local HTTPS-exempted test feed) failed signature
verification until the .sig was regenerated to match the post-fix file.
After the fix, the same test succeeded end-to-end (v1 AppImage replaced
in place with byte-identical v2 content).
Covers per-distro prerequisites (Debian/Ubuntu, Arch, Fedora/openSUSE),
how to fetch/vendor libvosk.so, build commands for the dev build and all
three packaged formats, and the known Linux-specific limitations
(Wayland/XWayland, DMA-BUF blank window workaround, missing screen-capture
protection, the Remote Control feature's port 43127 firewall requirement,
and why fix-appimage-libs.sh must run after every AppImage build).
README.md and README.de.md get a fully localized "Linux (community port)"
section with an honest limitations table (no screen-capture protection,
XWayland-by-default, the Remote Control port 43127 firewall requirement,
and AppImage-only in-app auto-update) plus a Linux platform badge next to
the existing Windows one.

The other six translations (es/tr/ar/fr/pt/fi) get the same section and
badge inserted in English, since translating them accurately isn't
something to guess at — the PR description asks maintainers/translators
for help translating those six.
@LumoRez07

Copy link
Copy Markdown
Owner

Thanks for putting this together. Right now my focus is strictly on the Windows version, refining it, and improving core features. Linux and macOS support might happen in the future, but they are not on the current roadmap and would interfere with the plans already in place. Because I cannot test or maintain Linux builds myself, I am closing this PR. You are completely welcome to keep maintaining and distributing these builds on your own fork under the GPL license though. Once the project gets to the point where Linux is actively on the development roadmap, we can always revisit it.

@LumoRez07 LumoRez07 closed this Aug 29, 2026
Emanschi added a commit to Emanschi/Flow that referenced this pull request Sep 5, 2026
LumoRez07 closed the upstream PR (github.com/LumoRez07/pull/3):
Linux isn't on their roadmap and they can't test/maintain it, but they
explicitly authorized this fork to keep distributing it under GPL. That
makes this permanently a standalone fork rather than a pending
contribution, so the docs needed to say so clearly instead of reading
like a feature branch of the original project.

- README.md/README.de.md: replaced the duplicated feature list,
  screenshots, and Windows-focused sections with a prominent fork notice
  and a short, Linux-only README that links to the upstream project for
  everything not specific to this fork (features, screenshots, official
  downloads, roadmap). Badges/star-history now point at this fork
  instead of upstream.
- The other six README translations (es/tr/ar/fr/pt/fi) are replaced with
  a short translated fork notice pointing to upstream's own translation,
  this fork's English/German README, and BUILDING-LINUX.md, rather than
  maintaining eight long, duplicated documents for a niche Linux fork.
- BUILDING-LINUX.md: added a one-line fork-context banner at the top.
- packaging/arch/PKGBUILD(-bin): fixed source tarball/release URLs to
  pull from the fork (Emanschi/Flow) instead of upstream, which will
  never have these Linux packaging files -- this was a real bug, not
  just a clarity issue, since building against upstream's tag would
  have failed outright.
- AppStream metainfo: bugtracker/vcs-browser now point at the fork, plus
  an added description paragraph noting the fork relationship.
- privacy-policy.md: added a note clarifying this is upstream's original
  policy, unchanged in substance for this fork since no data-handling
  code was modified.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants