diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
new file mode 100644
index 0000000..f6b759a
--- /dev/null
+++ b/.github/workflows/build-linux.yml
@@ -0,0 +1,157 @@
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Builds Flow's Linux packages (AppImage, .deb, .rpm) and uploads them as
+# workflow artifacts. Separate from and does not touch the existing
+# (manual, Windows-only) release process.
+#
+# Runs on ubuntu-22.04 deliberately, not a newer Ubuntu: linuxdeploy's
+# bundled `strip` doesn't understand the RELR relocations that newer
+# glibc/binutils toolchains emit by default, which breaks AppImage bundling
+# entirely on more recent distros. This was reproduced and confirmed by
+# hand during Phase 6.1 of the Linux port.
+#
+# The AppImage build additionally works around two more environment-
+# specific issues also found and fixed by hand in Phase 6.1:
+# - linuxdeploy's own AppImage tries to FUSE-mount itself, which sandboxed/
+# containerized CI runners commonly can't do; APPIMAGE_EXTRACT_AND_RUN=1
+# makes every AppImage runtime involved (including nested plugin
+# AppImages) fall back to extracting instead.
+# - linuxdeploy's dependency scanner runs `ldd` on the compiled binary to
+# decide what to bundle, but can't resolve the vendored (not system-
+# installed) libvosk.so unless it's on LD_LIBRARY_PATH at bundle time.
+# The binary's own runtime resolution goes through its RUNPATH, not
+# this variable - this is only needed to help linuxdeploy find the file
+# to copy in the first place.
+#
+# packaging/linux/fix-appimage-libs.sh removes a handful of bundled Wayland/
+# XCB client libraries that linuxdeploy-plugin-gtk pulls in but which must
+# come from the host's own compositor/X server to avoid version-mismatch
+# EGL failures (also found and fixed by hand in Phase 6.1, reproduced on a
+# real desktop after building on Ubuntu 22.04).
+#
+# TAURI_SIGNING_PRIVATE_KEY is expected to be the same key already used for
+# the existing Windows updater artifacts (see Phase 6.4 - the plan calls
+# for reusing it, not creating a new one). If it isn't configured as a
+# repository secret yet, the AppImage is still built, just without updater
+# artifacts, so this workflow doesn't hard-depend on Phase 6.4 being done
+# first.
+
+name: Build Linux
+
+on:
+ push:
+ branches:
+ - linux-native-build
+ paths:
+ - "src/**"
+ - "src-tauri/**"
+ - "packaging/linux/**"
+ - "package.json"
+ - "package-lock.json"
+ - ".github/workflows/build-linux.yml"
+ pull_request:
+ paths:
+ - "src/**"
+ - "src-tauri/**"
+ - "packaging/linux/**"
+ - "package.json"
+ - "package-lock.json"
+ - ".github/workflows/build-linux.yml"
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Build AppImage / deb / rpm
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install system build dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libwebkit2gtk-4.1-dev \
+ libgtk-3-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev \
+ libasound2-dev \
+ libssl-dev \
+ pkg-config \
+ xdg-utils \
+ unzip \
+ file
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache cargo build artifacts
+ uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: src-tauri -> target
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Install npm dependencies
+ run: npm ci
+
+ - name: Fetch vendored Vosk library
+ run: bash packaging/linux/fetch-vosk.sh
+
+ - name: Build AppImage
+ env:
+ FLOW_LINUX_PACKAGE: appimage
+ APPIMAGE_EXTRACT_AND_RUN: "1"
+ LD_LIBRARY_PATH: ${{ github.workspace }}/src-tauri/resources/vosk/linux-x64
+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+ run: |
+ if [ -n "${TAURI_SIGNING_PRIVATE_KEY}" ]; then
+ echo "Signing key configured - building AppImage with updater artifacts"
+ npx tauri build \
+ --config src-tauri/tauri.linux.conf.json \
+ --config src-tauri/tauri.linux.appimage.conf.json \
+ --bundles appimage
+ else
+ echo "::warning::TAURI_SIGNING_PRIVATE_KEY not configured - building AppImage without updater artifacts"
+ npx tauri build \
+ --config src-tauri/tauri.linux.conf.json \
+ --bundles appimage
+ fi
+
+ - name: Fix bundled Wayland/XCB libraries in AppImage
+ run: |
+ APPIMAGE_PATH="$(find src-tauri/target/release/bundle/appimage -maxdepth 1 -name '*.AppImage')"
+ bash packaging/linux/fix-appimage-libs.sh "${APPIMAGE_PATH}"
+
+ - name: Build .deb
+ env:
+ FLOW_LINUX_PACKAGE: deb
+ run: npx tauri build --config src-tauri/tauri.linux.conf.json --bundles deb
+
+ - name: Build .rpm
+ env:
+ FLOW_LINUX_PACKAGE: rpm
+ run: npx tauri build --config src-tauri/tauri.linux.conf.json --bundles rpm
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: flow-linux-x86_64
+ path: |
+ src-tauri/target/release/bundle/appimage/*.AppImage
+ src-tauri/target/release/bundle/appimage/*.AppImage.sig
+ src-tauri/target/release/bundle/deb/*.deb
+ src-tauri/target/release/bundle/rpm/*.rpm
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index 97eef33..f140c20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ dist-ssr
*.local
src-tauri/target/
src-tauri/gen/
+src-tauri/resources/vosk/linux-x64/*.so
tools/
tmp/
src/pro
diff --git a/BUILDING-LINUX.md b/BUILDING-LINUX.md
new file mode 100644
index 0000000..05bbb44
--- /dev/null
+++ b/BUILDING-LINUX.md
@@ -0,0 +1,178 @@
+
+
+# Building Flow on Linux
+
+Flow is primarily a Windows application, but this branch adds a native
+Linux build (AppImage, `.deb`, `.rpm`, and an Arch `PKGBUILD`). This
+document covers everything needed to build and run it from source on
+Linux, plus the known platform limitations.
+
+## Prerequisites
+
+You need Rust (stable), Node.js 20+, and the Tauri v2 native
+dependencies for your distro.
+
+### Debian / Ubuntu (22.04+)
+
+```bash
+sudo apt-get update
+sudo apt-get install -y \
+ libwebkit2gtk-4.1-dev \
+ libgtk-3-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev \
+ libasound2-dev \
+ libssl-dev \
+ pkg-config \
+ xdg-utils \
+ unzip \
+ file
+```
+
+(`unzip` and `file` are only needed to run `fetch-vosk.sh` and to build
+an AppImage; skip them if you only want a debug build for development.)
+
+### Arch Linux
+
+```bash
+sudo pacman -S --needed webkit2gtk-4.1 gtk3 libayatana-appindicator \
+ alsa-lib vosk-api hicolor-icon-theme gtk-update-icon-cache \
+ desktop-file-utils rust nodejs npm pkgconf
+```
+
+Arch's `vosk-api` package is used directly (see [Speech recognition
+library (Vosk)](#speech-recognition-library-vosk) below) — you do not
+need `fetch-vosk.sh` on Arch. A ready-to-use `PKGBUILD` is provided at
+[packaging/arch/PKGBUILD](packaging/arch/PKGBUILD).
+
+### Fedora / openSUSE (rpm-based)
+
+Package names vary by distro/version; the runtime dependencies a built
+`.rpm` declares are `webkit2gtk4.1`, `gtk3`, `libayatana-appindicator-
+gtk3`, and `alsa-lib` (see
+[tauri.linux.conf.json](src-tauri/tauri.linux.conf.json)) — install
+their `-devel` equivalents to build from source.
+
+## Speech recognition library (Vosk)
+
+Flow's voice engine links against `libvosk.so` at runtime. Two ways to
+provide it:
+
+- **System package** (Arch): install the distro's `vosk-api` package;
+ `FLOW_LINUX_PACKAGE=pacman` tells the build to expect it on the
+ system instead of vendoring a copy (see
+ [packaging/arch/PKGBUILD](packaging/arch/PKGBUILD)).
+- **Vendored copy** (AppImage/deb/rpm, or plain development builds on
+ distros without a `vosk-api` package): run
+ [packaging/linux/fetch-vosk.sh](packaging/linux/fetch-vosk.sh) once.
+ It downloads the official prebuilt `libvosk.so` (pinned to v0.3.45 —
+ the last version with published binary release assets, see the
+ script's comments) into
+ `src-tauri/resources/vosk/linux-x64/libvosk.so`, verifying its
+ SHA-256 against a hardcoded value. `src-tauri/build.rs` picks it up
+ automatically once present.
+
+## Building
+
+```bash
+npm ci
+bash packaging/linux/fetch-vosk.sh # skip on Arch if using system vosk-api
+```
+
+**Development build (debug, runs in place):**
+
+```bash
+cd src-tauri && cargo build
+./packaging/linux/flow-launcher.sh # or run target/debug/flow directly
+```
+
+**Packaged builds**, each via `tauri build` with the Linux config
+overlay (`tauri.linux.conf.json` merges automatically-shared settings
+from `tauri.conf.json`; nothing there needs editing per-package):
+
+```bash
+# AppImage
+FLOW_LINUX_PACKAGE=appimage npx tauri build \
+ --config src-tauri/tauri.linux.conf.json \
+ --bundles appimage
+bash packaging/linux/fix-appimage-libs.sh \
+ src-tauri/target/release/bundle/appimage/*.AppImage
+
+# .deb
+FLOW_LINUX_PACKAGE=deb npx tauri build \
+ --config src-tauri/tauri.linux.conf.json --bundles deb
+
+# .rpm
+FLOW_LINUX_PACKAGE=rpm npx tauri build \
+ --config src-tauri/tauri.linux.conf.json --bundles rpm
+```
+
+`fix-appimage-libs.sh` is **required** after every AppImage build — see
+[Known issues](#known-issues) below for why. It re-signs the AppImage
+if a `.sig` file (updater artifact) exists next to it and
+`TAURI_SIGNING_PRIVATE_KEY` is set in the environment, since repacking
+changes the file's signature-relevant bytes.
+
+To also produce a signed updater artifact (`.AppImage.sig`), add
+`--config src-tauri/tauri.linux.appimage.conf.json` as a second
+`--config` flag to the AppImage build above, with
+`TAURI_SIGNING_PRIVATE_KEY` (and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`,
+even if empty) set — this is the same signing key already used for
+Windows releases, not a separate one.
+
+[.github/workflows/build-linux.yml](.github/workflows/build-linux.yml)
+runs this exact sequence in CI (on `ubuntu-22.04` — see the workflow's
+own comments for why not a newer Ubuntu) and uploads all three package
+formats as artifacts on every push/PR to `linux-native-build`.
+
+## Known issues and platform limitations
+
+- **Wayland**: Flow is an always-on-top overlay that relies on absolute
+ window positioning, always-on-top, and global hotkeys. Native
+ Wayland's security model doesn't allow any of these for regular
+ applications, and neither GTK3 nor `tao` (Tauri's windowing layer on
+ Linux) work around that. [flow-launcher.sh](packaging/linux/flow-launcher.sh)
+ therefore forces the app onto XWayland by default
+ (`GDK_BACKEND=x11`); set `FLOW_ALLOW_WAYLAND=1` to opt out and run
+ under native Wayland, with the caveat that window positioning,
+ always-on-top, and global hotkeys will not work correctly.
+- **Blank/black window (DMA-BUF renderer)**: on some AMD/Mesa driver
+ combinations, WebKitGTK's DMA-BUF renderer fails to initialize the
+ window contents. `flow-launcher.sh` sets
+ `WEBKIT_DISABLE_DMABUF_RENDERER=1` by default to work around this;
+ unset it explicitly if you've verified your system doesn't need it.
+- **Screen-capture protection is not available on Linux.** The
+ Windows build can hide the teleprompter window from screen
+ recording/sharing (via a Windows-only API); there is no equivalent
+ mechanism on X11 or Wayland, so this feature is disabled on Linux.
+- **Remote Control needs a firewall exception.** The "Remote Control"
+ feature runs a small local HTTP relay on TCP port **43127** so
+ another device on the same network can send text to Flow directly
+ (see `REMOTE_RELAY_PORT` in
+ [src-tauri/src/lib.rs](src-tauri/src/lib.rs)). Most desktop Linux
+ distros ship a default-deny firewall (`ufw`, `firewalld`) with no
+ interactive per-app prompt like Windows Defender Firewall shows on
+ first launch — you need to open the port yourself if you want to use
+ this feature from another device on your LAN, e.g.:
+ ```bash
+ sudo ufw allow 43127/tcp # ufw (Debian/Ubuntu)
+ sudo firewall-cmd --add-port=43127/tcp --permanent && sudo firewall-cmd --reload # firewalld (Fedora)
+ ```
+- **AppImage and bundled Wayland/XCB libraries**: `linuxdeploy-plugin-
+ gtk` bundles Wayland/X11/XCB client libraries and `libxkbcommon`
+ into the AppImage, but these must match the exact protocol/ABI
+ version of the host's own compositor or X server. A version mismatch
+ causes WebKitGTK to fail EGL/GL context creation entirely (`Could
+ not create default EGL display: EGL_BAD_PARAMETER`).
+ [fix-appimage-libs.sh](packaging/linux/fix-appimage-libs.sh) strips
+ these specific libraries out and repacks the AppImage so the dynamic
+ linker falls through to the host's own copies instead — this is why
+ the script must run after every AppImage build, not just optionally.
diff --git a/README.ar.md b/README.ar.md
index 1faa1af..04bd314 100644
--- a/README.ar.md
+++ b/README.ar.md
@@ -38,6 +38,7 @@
+
@@ -227,6 +228,21 @@ npm run tauri build
---
+## Linux (community port)
+
+Flow also builds natively on Linux, as an AppImage, `.deb`, `.rpm`, or an Arch `PKGBUILD`. This is a community-maintained port; Windows remains the primary, officially supported platform. See [BUILDING-LINUX.md](BUILDING-LINUX.md) for prerequisites and build instructions — there is no official pre-built binary release yet, so building from source (or grabbing an artifact from the [Linux CI workflow](.github/workflows/build-linux.yml)) is currently required.
+
+Honest limitations compared to Windows:
+
+| Feature | Linux |
+| --- | --- |
+| Screen-capture protection | Not available — the underlying API is Windows-only, with no X11/Wayland equivalent. |
+| Wayland | Runs via XWayland by default (always-on-top, absolute positioning, and global hotkeys need it); native Wayland is opt-in but breaks those three. |
+| Remote Control | Works, but needs a one-time manual firewall exception for TCP port 43127 (most Linux desktops default-deny inbound connections, unlike Windows' first-launch prompt). |
+| In-app auto-update | Works for the AppImage; `.deb`/`.rpm`/`PKGBUILD` installs update through your distro's package manager instead. |
+
+---
+
## الخصوصية
- يتم تخزين معظم البيانات محلياً على جهازك.
diff --git a/README.de.md b/README.de.md
index 0451891..495e2d1 100644
--- a/README.de.md
+++ b/README.de.md
@@ -38,6 +38,7 @@
+
@@ -209,6 +210,21 @@ Die `.sig`-Datei wird zusammen mit der MSI zur Referenz generiert, während `lat
---
+## Linux (Community-Portierung)
+
+Flow lässt sich auch nativ unter Linux bauen — als AppImage, `.deb`, `.rpm` oder über ein Arch-`PKGBUILD`. Diese Portierung wird von der Community gepflegt; Windows bleibt die primäre, offiziell unterstützte Plattform. Voraussetzungen und Build-Anleitung stehen in [BUILDING-LINUX.md](BUILDING-LINUX.md) — ein offizielles, fertig gebautes Release gibt es noch nicht, daher ist aktuell ein Build aus dem Quellcode nötig (oder ein Artefakt aus dem [Linux-CI-Workflow](.github/workflows/build-linux.yml)).
+
+Ehrliche Übersicht der Einschränkungen gegenüber Windows:
+
+| Funktion | Linux |
+| --- | --- |
+| Schutz vor Bildschirmaufnahme | Nicht verfügbar — die zugrunde liegende API ist Windows-exklusiv, ein Äquivalent für X11/Wayland gibt es nicht. |
+| Wayland | Läuft standardmäßig über XWayland (Always-on-Top, absolute Fensterpositionierung und globale Hotkeys benötigen es); natives Wayland ist optional aktivierbar, bricht dabei aber genau diese drei Funktionen. |
+| Fernsteuerung (Remote Control) | Funktioniert, benötigt aber einmalig eine manuelle Firewall-Freigabe für TCP-Port 43127 (die meisten Linux-Desktops blockieren eingehende Verbindungen standardmäßig, anders als der Erstlaunch-Dialog unter Windows). |
+| Automatisches In-App-Update | Funktioniert bei der AppImage; `.deb`/`.rpm`/`PKGBUILD`-Installationen werden stattdessen über den Paketmanager der Distribution aktualisiert. |
+
+---
+
## Datenschutz
- Die meisten Daten werden lokal auf dem Gerät gespeichert.
diff --git a/README.es.md b/README.es.md
index 5fafd9b..dcbc8ed 100644
--- a/README.es.md
+++ b/README.es.md
@@ -38,6 +38,7 @@
+
@@ -209,6 +210,21 @@ El archivo `.sig` se genera junto con el instalador MSI como referencia, mientra
---
+## Linux (community port)
+
+Flow also builds natively on Linux, as an AppImage, `.deb`, `.rpm`, or an Arch `PKGBUILD`. This is a community-maintained port; Windows remains the primary, officially supported platform. See [BUILDING-LINUX.md](BUILDING-LINUX.md) for prerequisites and build instructions — there is no official pre-built binary release yet, so building from source (or grabbing an artifact from the [Linux CI workflow](.github/workflows/build-linux.yml)) is currently required.
+
+Honest limitations compared to Windows:
+
+| Feature | Linux |
+| --- | --- |
+| Screen-capture protection | Not available — the underlying API is Windows-only, with no X11/Wayland equivalent. |
+| Wayland | Runs via XWayland by default (always-on-top, absolute positioning, and global hotkeys need it); native Wayland is opt-in but breaks those three. |
+| Remote Control | Works, but needs a one-time manual firewall exception for TCP port 43127 (most Linux desktops default-deny inbound connections, unlike Windows' first-launch prompt). |
+| In-app auto-update | Works for the AppImage; `.deb`/`.rpm`/`PKGBUILD` installs update through your distro's package manager instead. |
+
+---
+
## Privacidad
- La mayoría de los datos se almacenan localmente en el dispositivo.
diff --git a/README.fi.md b/README.fi.md
index 6d52a21..d88c4fb 100644
--- a/README.fi.md
+++ b/README.fi.md
@@ -38,6 +38,7 @@
+
@@ -209,6 +210,21 @@ Julkaise nämä tiedostot hakemistosta `src-tauri/target/release/bundle` GitHub-
---
+## Linux (community port)
+
+Flow also builds natively on Linux, as an AppImage, `.deb`, `.rpm`, or an Arch `PKGBUILD`. This is a community-maintained port; Windows remains the primary, officially supported platform. See [BUILDING-LINUX.md](BUILDING-LINUX.md) for prerequisites and build instructions — there is no official pre-built binary release yet, so building from source (or grabbing an artifact from the [Linux CI workflow](.github/workflows/build-linux.yml)) is currently required.
+
+Honest limitations compared to Windows:
+
+| Feature | Linux |
+| --- | --- |
+| Screen-capture protection | Not available — the underlying API is Windows-only, with no X11/Wayland equivalent. |
+| Wayland | Runs via XWayland by default (always-on-top, absolute positioning, and global hotkeys need it); native Wayland is opt-in but breaks those three. |
+| Remote Control | Works, but needs a one-time manual firewall exception for TCP port 43127 (most Linux desktops default-deny inbound connections, unlike Windows' first-launch prompt). |
+| In-app auto-update | Works for the AppImage; `.deb`/`.rpm`/`PKGBUILD` installs update through your distro's package manager instead. |
+
+---
+
## Tietosuoja
- Suurin osa tiedoista tallennetaan paikallisesti laitteellesi.
diff --git a/README.fr.md b/README.fr.md
index cec4eb3..3a1ecc0 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -38,6 +38,7 @@
+
@@ -209,6 +210,21 @@ Le fichier `.sig` est généré aux côtés du MSI à titre de référence, tand
---
+## Linux (community port)
+
+Flow also builds natively on Linux, as an AppImage, `.deb`, `.rpm`, or an Arch `PKGBUILD`. This is a community-maintained port; Windows remains the primary, officially supported platform. See [BUILDING-LINUX.md](BUILDING-LINUX.md) for prerequisites and build instructions — there is no official pre-built binary release yet, so building from source (or grabbing an artifact from the [Linux CI workflow](.github/workflows/build-linux.yml)) is currently required.
+
+Honest limitations compared to Windows:
+
+| Feature | Linux |
+| --- | --- |
+| Screen-capture protection | Not available — the underlying API is Windows-only, with no X11/Wayland equivalent. |
+| Wayland | Runs via XWayland by default (always-on-top, absolute positioning, and global hotkeys need it); native Wayland is opt-in but breaks those three. |
+| Remote Control | Works, but needs a one-time manual firewall exception for TCP port 43127 (most Linux desktops default-deny inbound connections, unlike Windows' first-launch prompt). |
+| In-app auto-update | Works for the AppImage; `.deb`/`.rpm`/`PKGBUILD` installs update through your distro's package manager instead. |
+
+---
+
## Confidentialité
- La majorité des données est stockée localement sur votre appareil.
diff --git a/README.md b/README.md
index 7928b48..7c47cce 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@
+
@@ -209,6 +210,21 @@ The `.sig` file is generated alongside the MSI for reference, while `latest.json
---
+## Linux (community port)
+
+Flow also builds natively on Linux, as an AppImage, `.deb`, `.rpm`, or an Arch `PKGBUILD`. This is a community-maintained port; Windows remains the primary, officially supported platform. See [BUILDING-LINUX.md](BUILDING-LINUX.md) for prerequisites and build instructions — there is no official pre-built binary release yet, so building from source (or grabbing an artifact from the [Linux CI workflow](.github/workflows/build-linux.yml)) is currently required.
+
+Honest limitations compared to Windows:
+
+| Feature | Linux |
+| --- | --- |
+| Screen-capture protection | Not available — the underlying API is Windows-only, with no X11/Wayland equivalent. |
+| Wayland | Runs via XWayland by default (always-on-top, absolute positioning, and global hotkeys need it); native Wayland is opt-in but breaks those three. |
+| Remote Control | Works, but needs a one-time manual firewall exception for TCP port 43127 (most Linux desktops default-deny inbound connections, unlike Windows' first-launch prompt). |
+| In-app auto-update | Works for the AppImage; `.deb`/`.rpm`/`PKGBUILD` installs update through your distro's package manager instead. |
+
+---
+
## Privacy
- Most data is stored locally on the device.
diff --git a/README.pt.md b/README.pt.md
index 26aefb1..e341b18 100644
--- a/README.pt.md
+++ b/README.pt.md
@@ -38,6 +38,7 @@
+
@@ -209,6 +210,21 @@ O arquivo `.sig` é gerado junto com o MSI para referência, enquanto o `latest.
---
+## Linux (community port)
+
+Flow also builds natively on Linux, as an AppImage, `.deb`, `.rpm`, or an Arch `PKGBUILD`. This is a community-maintained port; Windows remains the primary, officially supported platform. See [BUILDING-LINUX.md](BUILDING-LINUX.md) for prerequisites and build instructions — there is no official pre-built binary release yet, so building from source (or grabbing an artifact from the [Linux CI workflow](.github/workflows/build-linux.yml)) is currently required.
+
+Honest limitations compared to Windows:
+
+| Feature | Linux |
+| --- | --- |
+| Screen-capture protection | Not available — the underlying API is Windows-only, with no X11/Wayland equivalent. |
+| Wayland | Runs via XWayland by default (always-on-top, absolute positioning, and global hotkeys need it); native Wayland is opt-in but breaks those three. |
+| Remote Control | Works, but needs a one-time manual firewall exception for TCP port 43127 (most Linux desktops default-deny inbound connections, unlike Windows' first-launch prompt). |
+| In-app auto-update | Works for the AppImage; `.deb`/`.rpm`/`PKGBUILD` installs update through your distro's package manager instead. |
+
+---
+
## Privacidade
- A maior parte dos dados é armazenada localmente no dispositivo.
diff --git a/README.tr.md b/README.tr.md
index 50e7af0..47d1161 100644
--- a/README.tr.md
+++ b/README.tr.md
@@ -38,6 +38,7 @@
+
@@ -209,6 +210,21 @@ npm run tauri build
---
+## Linux (community port)
+
+Flow also builds natively on Linux, as an AppImage, `.deb`, `.rpm`, or an Arch `PKGBUILD`. This is a community-maintained port; Windows remains the primary, officially supported platform. See [BUILDING-LINUX.md](BUILDING-LINUX.md) for prerequisites and build instructions — there is no official pre-built binary release yet, so building from source (or grabbing an artifact from the [Linux CI workflow](.github/workflows/build-linux.yml)) is currently required.
+
+Honest limitations compared to Windows:
+
+| Feature | Linux |
+| --- | --- |
+| Screen-capture protection | Not available — the underlying API is Windows-only, with no X11/Wayland equivalent. |
+| Wayland | Runs via XWayland by default (always-on-top, absolute positioning, and global hotkeys need it); native Wayland is opt-in but breaks those three. |
+| Remote Control | Works, but needs a one-time manual firewall exception for TCP port 43127 (most Linux desktops default-deny inbound connections, unlike Windows' first-launch prompt). |
+| In-app auto-update | Works for the AppImage; `.deb`/`.rpm`/`PKGBUILD` installs update through your distro's package manager instead. |
+
+---
+
## Gizlilik
- Çoğu veri yerel olarak cihazda depolanır.
diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD
new file mode 100644
index 0000000..e15c381
--- /dev/null
+++ b/packaging/arch/PKGBUILD
@@ -0,0 +1,74 @@
+# Maintainer: Emanuel Halbwachs
+#
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+pkgname=flow-teleprompter
+pkgver=2.0.0
+pkgrel=1
+pkgdesc="A modern desktop teleprompter for smooth reading, quick editing, voice controls, and remote message injection"
+arch=('x86_64')
+url="https://github.com/LumoRez07/Flow"
+license=('GPL-3.0-or-later')
+depends=('webkit2gtk-4.1' 'gtk3' 'libayatana-appindicator' 'alsa-lib' 'vosk-api' 'hicolor-icon-theme'
+ 'gtk-update-icon-cache' 'desktop-file-utils')
+makedepends=('rust' 'cargo' 'nodejs' 'npm' 'pkgconf')
+provides=('flow-teleprompter-bin')
+conflicts=('flow-teleprompter-bin')
+options=('!lto')
+install="$pkgname.install"
+source=("$pkgname-$pkgver.tar.gz::https://github.com/LumoRez07/Flow/archive/refs/tags/v$pkgver.tar.gz")
+# TODO: replace with the real checksum once v$pkgver is tagged upstream and
+# the tarball above actually exists (this Linux port has not been released
+# yet — see plan/01-SONNET-TASKS.md Phase 7.3 "Upstream-PR").
+sha256sums=('SKIP')
+
+build() {
+ cd "Flow-$pkgver"
+
+ export FLOW_LINUX_PACKAGE=pacman
+ export CARGO_HOME="$srcdir/cargo-home"
+
+ # tauri-build automatically merges tauri.linux.conf.json into every Linux
+ # build (not just `tauri build`), and its `bundle.resources` entry requires
+ # resources/vosk/linux-x64/libvosk.so to exist on disk at build time even
+ # though this package depends on the system vosk-api package at runtime.
+ # Stage the system lib into the vendored path instead of vendoring/
+ # downloading a redundant copy — the `vosk-api` depends entry already
+ # guarantees it exists.
+ local system_libvosk
+ system_libvosk="$(ldconfig -p | awk '/libvosk\.so/ { print $NF; exit }')"
+ if [[ -z "$system_libvosk" ]]; then
+ echo "libvosk.so not found via ldconfig; is vosk-api installed?" >&2
+ return 1
+ fi
+ install -Dm644 "$system_libvosk" "src-tauri/resources/vosk/linux-x64/libvosk.so"
+
+ npm ci --no-audit --no-fund
+ cargo build --release --manifest-path=src-tauri/Cargo.toml
+}
+
+package() {
+ cd "Flow-$pkgver"
+
+ install -Dm755 "src-tauri/target/release/flow" "$pkgdir/usr/lib/flow/flow"
+ install -Dm755 "packaging/linux/flow-launcher.sh" "$pkgdir/usr/bin/flow"
+
+ install -Dm644 "packaging/linux/app.flowteleprompter.Flow.desktop" \
+ "$pkgdir/usr/share/applications/app.flowteleprompter.Flow.desktop"
+ install -Dm644 "packaging/linux/app.flowteleprompter.Flow.metainfo.xml" \
+ "$pkgdir/usr/share/metainfo/app.flowteleprompter.Flow.metainfo.xml"
+
+ for size in 32x32 64x64 128x128 256x256 512x512; do
+ install -Dm644 "packaging/linux/icons/hicolor/$size/apps/app.flowteleprompter.Flow.png" \
+ "$pkgdir/usr/share/icons/hicolor/$size/apps/app.flowteleprompter.Flow.png"
+ done
+
+ install -Dm644 "LICENSE" \
+ "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
+}
diff --git a/packaging/arch/PKGBUILD-bin b/packaging/arch/PKGBUILD-bin
new file mode 100644
index 0000000..97cf364
--- /dev/null
+++ b/packaging/arch/PKGBUILD-bin
@@ -0,0 +1,59 @@
+# Maintainer: Emanuel Halbwachs
+#
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Binary counterpart to flow-teleprompter (packaging/arch/PKGBUILD): skips
+# compiling from source and instead installs a prebuilt release binary.
+# Desktop file / icons / metainfo / license are still pulled from the
+# lightweight source tag tarball rather than duplicated into the binary
+# release asset.
+#
+# NOTE: the second source entry's filename/URL must match whatever the
+# Linux release workflow (plan/01-SONNET-TASKS.md Phase 6.3, not yet
+# implemented) actually publishes as a release asset. Update it here once
+# that workflow exists and its real asset name is known.
+
+pkgname=flow-teleprompter-bin
+pkgver=2.0.0
+pkgrel=1
+pkgdesc="A modern desktop teleprompter for smooth reading, quick editing, voice controls, and remote message injection (prebuilt binary)"
+arch=('x86_64')
+url="https://github.com/LumoRez07/Flow"
+license=('GPL-3.0-or-later')
+depends=('webkit2gtk-4.1' 'gtk3' 'libayatana-appindicator' 'alsa-lib' 'vosk-api' 'hicolor-icon-theme'
+ 'gtk-update-icon-cache' 'desktop-file-utils')
+provides=('flow-teleprompter')
+conflicts=('flow-teleprompter')
+options=('!strip')
+install="flow-teleprompter.install"
+source=("flow-src-$pkgver.tar.gz::https://github.com/LumoRez07/Flow/archive/refs/tags/v$pkgver.tar.gz"
+ "flow-linux-$CARCH-$pkgver.tar.gz::https://github.com/LumoRez07/Flow/releases/download/v$pkgver/flow-linux-$CARCH.tar.gz")
+# TODO: replace with real checksums once v$pkgver is tagged/released
+# upstream (this Linux port has not been released yet — see
+# plan/01-SONNET-TASKS.md Phase 7.3 "Upstream-PR").
+sha256sums=('SKIP'
+ 'SKIP')
+
+package() {
+ install -Dm755 "flow-linux-$CARCH/flow" "$pkgdir/usr/lib/flow/flow"
+ install -Dm755 "Flow-$pkgver/packaging/linux/flow-launcher.sh" "$pkgdir/usr/bin/flow"
+
+ install -Dm644 "Flow-$pkgver/packaging/linux/app.flowteleprompter.Flow.desktop" \
+ "$pkgdir/usr/share/applications/app.flowteleprompter.Flow.desktop"
+ install -Dm644 "Flow-$pkgver/packaging/linux/app.flowteleprompter.Flow.metainfo.xml" \
+ "$pkgdir/usr/share/metainfo/app.flowteleprompter.Flow.metainfo.xml"
+
+ for size in 32x32 64x64 128x128 256x256 512x512; do
+ install -Dm644 "Flow-$pkgver/packaging/linux/icons/hicolor/$size/apps/app.flowteleprompter.Flow.png" \
+ "$pkgdir/usr/share/icons/hicolor/$size/apps/app.flowteleprompter.Flow.png"
+ done
+
+ install -Dm644 "Flow-$pkgver/LICENSE" \
+ "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
+}
diff --git a/packaging/arch/flow-teleprompter.install b/packaging/arch/flow-teleprompter.install
new file mode 100644
index 0000000..fae5cce
--- /dev/null
+++ b/packaging/arch/flow-teleprompter.install
@@ -0,0 +1,33 @@
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pacman's built-in gtk-update-icon-cache hook only fires when a whole new
+# top-level icon theme directory appears; it does not cover files an
+# application package adds into the already-installed shared "hicolor"
+# theme. Without this, the icon cache stays stale and app launchers/menus
+# fall back to a generic icon until something else happens to rebuild it.
+# Confirmed by hand on a KDE Plasma system: after a plain `pacman -U`, the
+# app menu kept showing a generic icon until kbuildsycoca6 (KDE's own
+# service/desktop-file cache, separate from the GTK icon cache) was rebuilt
+# too. kbuildsycoca6 is guarded with `command -v` since it comes from KDE
+# Frameworks (kservice) and must not become a hard dependency for users on
+# GNOME/XFCE/etc.
+
+post_install() {
+ gtk-update-icon-cache -q -t -f usr/share/icons/hicolor &>/dev/null || true
+ update-desktop-database -q &>/dev/null || true
+ command -v kbuildsycoca6 &>/dev/null && kbuildsycoca6 --noincremental &>/dev/null || true
+}
+
+post_upgrade() {
+ post_install
+}
+
+post_remove() {
+ post_install
+}
diff --git a/packaging/linux/app.flowteleprompter.Flow.desktop b/packaging/linux/app.flowteleprompter.Flow.desktop
new file mode 100644
index 0000000..57a471d
--- /dev/null
+++ b/packaging/linux/app.flowteleprompter.Flow.desktop
@@ -0,0 +1,19 @@
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+[Desktop Entry]
+Type=Application
+Name=Flow
+GenericName=Teleprompter
+Comment=A modern desktop teleprompter for smooth reading, quick editing, voice controls, and remote message injection
+Exec=flow %U
+Icon=app.flowteleprompter.Flow
+Terminal=false
+Categories=AudioVideo;Utility;
+StartupWMClass=flow
+StartupNotify=true
diff --git a/packaging/linux/app.flowteleprompter.Flow.metainfo.xml b/packaging/linux/app.flowteleprompter.Flow.metainfo.xml
new file mode 100644
index 0000000..5ef4ad3
--- /dev/null
+++ b/packaging/linux/app.flowteleprompter.Flow.metainfo.xml
@@ -0,0 +1,48 @@
+
+
+
+ app.flowteleprompter.Flow
+ CC0-1.0
+ GPL-3.0-or-later
+ Flow
+ A modern desktop teleprompter for smooth reading and voice-controlled scripts
+ app.flowteleprompter.Flow.desktop
+ https://flowteleprompter.vercel.app/
+ https://github.com/LumoRez07/Flow/issues
+ https://github.com/LumoRez07/Flow
+
+ LumoRez
+
+
+
+ Flow is a teleprompter app built with web technologies and Tauri. It's
+ designed to be simple, lightweight, and customizable.
+
+
+ Flow includes script editing, multiple playback modes, voice tracking,
+ AI-assisted drafting, remote message injection, and tray controls, made
+ for creators, educators, students, and everyone who wants to read off
+ their screen while looking at the camera.
+
+
+
+ AudioVideo
+ Utility
+
+
+
+
+
+
Native Linux build of Flow, alongside the existing Windows release.
+
+
+
+
diff --git a/packaging/linux/fetch-vosk.sh b/packaging/linux/fetch-vosk.sh
new file mode 100755
index 0000000..f277e91
--- /dev/null
+++ b/packaging/linux/fetch-vosk.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+#
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Fetches the official prebuilt Vosk API shared library for Linux x86_64
+# and stages it under src-tauri/resources/vosk/linux-x64/, so that
+# src-tauri/build.rs can pick it up as a vendored fallback for packaged
+# builds (AppImage/deb/rpm) that should not depend on a system-installed
+# vosk-api package.
+#
+# The Vosk project stopped publishing prebuilt release assets after
+# v0.3.45 (v0.3.50, the version Arch Linux's `vosk-api` package builds
+# from source, has no binary release assets) - see
+# https://github.com/alphacep/vosk-api/releases. v0.3.45 is therefore
+# pinned deliberately, not because it is "latest".
+
+set -euo pipefail
+
+VOSK_VERSION="0.3.45"
+ARCHIVE_NAME="vosk-linux-x86_64-${VOSK_VERSION}.zip"
+DOWNLOAD_URL="https://github.com/alphacep/vosk-api/releases/download/v${VOSK_VERSION}/${ARCHIVE_NAME}"
+# Verified by hand against the published GitHub release asset:
+# curl -sL "$DOWNLOAD_URL" | sha256sum
+EXPECTED_SHA256="bbdc8ed85c43979f6443142889770ea95cbfbc56cffb5c5dcd73afa875c5fbb2"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+DEST_DIR="${REPO_ROOT}/src-tauri/resources/vosk/linux-x64"
+DEST_LIB="${DEST_DIR}/libvosk.so"
+
+if [[ -f "${DEST_LIB}" ]]; then
+ echo "libvosk.so already present at ${DEST_LIB}, skipping download."
+ echo "Delete it and re-run this script to force a re-fetch."
+ exit 0
+fi
+
+WORK_DIR="$(mktemp -d)"
+trap 'rm -rf "${WORK_DIR}"' EXIT
+
+ARCHIVE_PATH="${WORK_DIR}/${ARCHIVE_NAME}"
+
+echo "Downloading Vosk API ${VOSK_VERSION} for linux-x86_64..."
+curl -fL --retry 3 -o "${ARCHIVE_PATH}" "${DOWNLOAD_URL}"
+
+echo "Verifying checksum..."
+ACTUAL_SHA256="$(sha256sum "${ARCHIVE_PATH}" | cut -d' ' -f1)"
+if [[ "${ACTUAL_SHA256}" != "${EXPECTED_SHA256}" ]]; then
+ echo "ERROR: checksum mismatch for ${ARCHIVE_NAME}" >&2
+ echo " expected: ${EXPECTED_SHA256}" >&2
+ echo " actual: ${ACTUAL_SHA256}" >&2
+ exit 1
+fi
+
+echo "Extracting libvosk.so..."
+unzip -q -j "${ARCHIVE_PATH}" "vosk-linux-x86_64-${VOSK_VERSION}/libvosk.so" -d "${WORK_DIR}"
+
+mkdir -p "${DEST_DIR}"
+mv "${WORK_DIR}/libvosk.so" "${DEST_LIB}"
+chmod 0644 "${DEST_LIB}"
+
+echo "Staged $(du -h "${DEST_LIB}" | cut -f1) libvosk.so at ${DEST_LIB}"
diff --git a/packaging/linux/fix-appimage-libs.sh b/packaging/linux/fix-appimage-libs.sh
new file mode 100755
index 0000000..e089e81
--- /dev/null
+++ b/packaging/linux/fix-appimage-libs.sh
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+#
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Tauri's AppImage bundling (via linuxdeploy + linuxdeploy-plugin-gtk) pulls
+# in the full GTK dependency chain, including Wayland/X11 protocol client
+# libraries that must match the exact protocol/ABI version of whatever
+# compositor or X server the AppImage actually runs against. Bundling them
+# is a well-known linuxdeploy-plugin-gtk anti-pattern: the AppImage's own
+# (older, build-host-vintage) copies get preferred over the running
+# system's copies via RUNPATH, and a mismatch there causes WebKitGTK to
+# fail EGL/GL context creation entirely ("Could not create default EGL
+# display: EGL_BAD_PARAMETER. Aborting...") even though every other part
+# of the app is fine.
+#
+# Verified by hand: building the AppImage on Ubuntu 22.04 (per the CI
+# environment used to sidestep unrelated RELR-relocation/strip
+# incompatibilities with newer distros) and running the result on an
+# unrelated, much newer Arch Linux desktop reproduced the EGL failure
+# every time these libraries were present, and fixed it every time they
+# were removed and the AppImage was repacked, letting the dynamic linker
+# fall through to the host's own (correct, matching) copies instead.
+#
+# Usage: fix-appimage-libs.sh
+# Rewrites the given AppImage in place. Run from the repository root (needs
+# `npx tauri` on PATH via package.json) if a pre-existing .sig should
+# be re-signed after repacking -- see below.
+#
+# Repacking changes the AppImage's bytes, which invalidates any updater
+# signature (.sig) generated for the original file before this script
+# ran. If TAURI_SIGNING_PRIVATE_KEY is set and a stale .sig is found next to
+# the input file, this script re-signs the repacked file so the two stay
+# consistent. Found by hand while testing Phase 6.4's update flow: the
+# updater's signature verification failed until the .sig was regenerated to
+# match the post-fix file.
+
+set -euo pipefail
+
+if [[ $# -ne 1 ]]; then
+ echo "Usage: $0 " >&2
+ exit 1
+fi
+
+APPIMAGE_PATH="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
+INVOKED_FROM_DIR="$(pwd)"
+
+# Libraries that must come from the host system, never be bundled: Wayland/
+# X11/XCB client protocol libraries and their EGL glue. libxkbcommon is
+# included because a mismatched keymap-compiler ABI causes similar (if
+# rarer) failures.
+LIBS_TO_REMOVE=(
+ libwayland-client.so.0
+ libwayland-server.so.0
+ libwayland-egl.so.1
+ libwayland-cursor.so.0
+ libxcb-randr.so.0
+ libxcb-render.so.0
+ libxcb-shm.so.0
+ libxkbcommon.so.0
+)
+
+WORK_DIR="$(mktemp -d)"
+trap 'rm -rf "${WORK_DIR}"' EXIT
+
+echo "Extracting ${APPIMAGE_PATH}..."
+cd "${WORK_DIR}"
+APPIMAGE_EXTRACT_AND_RUN=1 "${APPIMAGE_PATH}" --appimage-extract >/dev/null
+
+REMOVED_ANY=0
+for lib in "${LIBS_TO_REMOVE[@]}"; do
+ LIB_PATH="squashfs-root/usr/lib/${lib}"
+ if [[ -f "${LIB_PATH}" ]]; then
+ echo "Removing bundled ${lib} (must come from the host system)"
+ rm -f "${LIB_PATH}"
+ REMOVED_ANY=1
+ fi
+done
+
+if [[ "${REMOVED_ANY}" -eq 0 ]]; then
+ echo "No known-problematic libraries were bundled; nothing to do."
+ exit 0
+fi
+
+echo "Repacking AppImage..."
+LINUXDEPLOY_PLUGIN_APPIMAGE="${LINUXDEPLOY_PLUGIN_APPIMAGE:-${HOME}/.cache/tauri/linuxdeploy-plugin-appimage.AppImage}"
+if [[ ! -x "${LINUXDEPLOY_PLUGIN_APPIMAGE}" ]]; then
+ echo "linuxdeploy-plugin-appimage.AppImage not found at ${LINUXDEPLOY_PLUGIN_APPIMAGE}" >&2
+ echo "(set LINUXDEPLOY_PLUGIN_APPIMAGE to override, or run a tauri appimage build once first so it gets cached)" >&2
+ exit 1
+fi
+
+APPIMAGE_EXTRACT_AND_RUN=1 "${LINUXDEPLOY_PLUGIN_APPIMAGE}" --appdir=squashfs-root
+
+REBUILT="$(find . -maxdepth 1 -name '*.AppImage' -not -path "$(basename "${APPIMAGE_PATH}")")"
+if [[ -z "${REBUILT}" ]]; then
+ echo "Repacking did not produce a new .AppImage file" >&2
+ exit 1
+fi
+
+mv "${REBUILT}" "${APPIMAGE_PATH}"
+chmod +x "${APPIMAGE_PATH}"
+echo "Fixed AppImage written to ${APPIMAGE_PATH}"
+
+SIG_PATH="${APPIMAGE_PATH}.sig"
+if [[ -f "${SIG_PATH}" ]]; then
+ if [[ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]]; then
+ echo "Re-signing repacked AppImage (existing .sig is now stale)..."
+ (cd "${INVOKED_FROM_DIR}" && npx tauri signer sign "${APPIMAGE_PATH}")
+ else
+ echo "WARNING: ${SIG_PATH} exists but TAURI_SIGNING_PRIVATE_KEY is not set;" >&2
+ echo "it is now stale and will fail update verification. Re-run with the" >&2
+ echo "signing key available, or delete it if this build isn't meant to be signed." >&2
+ exit 1
+ fi
+fi
diff --git a/packaging/linux/flow-launcher.sh b/packaging/linux/flow-launcher.sh
new file mode 100755
index 0000000..befad58
--- /dev/null
+++ b/packaging/linux/flow-launcher.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+#
+# Flow - A high-performance teleprompter for Windows.
+# Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Launcher wrapper for the Flow teleprompter on Linux.
+#
+# Flow is an always-on-top overlay: it needs absolute window positioning,
+# always-on-top, and global hotkeys to work the way it does on Windows.
+# Neither GTK3 nor the windowing toolkit Tauri uses on Linux (tao) support
+# those under native Wayland, so this wrapper forces the app onto X11 via
+# XWayland by default. Set FLOW_ALLOW_WAYLAND=1 to opt out and run under
+# native Wayland instead (window positioning, always-on-top and global
+# hotkeys will not work correctly in that mode).
+#
+# WEBKIT_DISABLE_DMABUF_RENDERER=1 works around the most common cause of a
+# blank/black WebKitGTK window on Linux (DMA-BUF renderer issues, seen
+# especially on some AMD/Mesa driver combinations). It is on by default;
+# unset it explicitly if you have verified your system does not need it.
+
+set -euo pipefail
+
+if [[ -z "${FLOW_ALLOW_WAYLAND:-}" ]]; then
+ export GDK_BACKEND=x11
+fi
+
+export WEBKIT_DISABLE_DMABUF_RENDERER="${WEBKIT_DISABLE_DMABUF_RENDERER:-1}"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Installed layout: binary lives at /usr/lib/flow/flow next to this wrapper's
+# install location (/usr/bin/flow). Development layout: fall back to the
+# debug binary built by `cargo build` inside the repository.
+if [[ -x "${SCRIPT_DIR}/../lib/flow/flow" ]]; then
+ BINARY="${SCRIPT_DIR}/../lib/flow/flow"
+elif [[ -x "${SCRIPT_DIR}/../../src-tauri/target/debug/flow" ]]; then
+ BINARY="${SCRIPT_DIR}/../../src-tauri/target/debug/flow"
+elif [[ -x "${SCRIPT_DIR}/../../src-tauri/target/release/flow" ]]; then
+ BINARY="${SCRIPT_DIR}/../../src-tauri/target/release/flow"
+else
+ echo "flow-launcher.sh: could not locate the flow binary" >&2
+ exit 1
+fi
+
+exec "${BINARY}" "$@"
diff --git a/packaging/linux/icons/hicolor/128x128/apps/app.flowteleprompter.Flow.png b/packaging/linux/icons/hicolor/128x128/apps/app.flowteleprompter.Flow.png
new file mode 100644
index 0000000..fdd3f8c
Binary files /dev/null and b/packaging/linux/icons/hicolor/128x128/apps/app.flowteleprompter.Flow.png differ
diff --git a/packaging/linux/icons/hicolor/256x256/apps/app.flowteleprompter.Flow.png b/packaging/linux/icons/hicolor/256x256/apps/app.flowteleprompter.Flow.png
new file mode 100644
index 0000000..1e1ba35
Binary files /dev/null and b/packaging/linux/icons/hicolor/256x256/apps/app.flowteleprompter.Flow.png differ
diff --git a/packaging/linux/icons/hicolor/32x32/apps/app.flowteleprompter.Flow.png b/packaging/linux/icons/hicolor/32x32/apps/app.flowteleprompter.Flow.png
new file mode 100644
index 0000000..ce68970
Binary files /dev/null and b/packaging/linux/icons/hicolor/32x32/apps/app.flowteleprompter.Flow.png differ
diff --git a/packaging/linux/icons/hicolor/512x512/apps/app.flowteleprompter.Flow.png b/packaging/linux/icons/hicolor/512x512/apps/app.flowteleprompter.Flow.png
new file mode 100644
index 0000000..e58211d
Binary files /dev/null and b/packaging/linux/icons/hicolor/512x512/apps/app.flowteleprompter.Flow.png differ
diff --git a/packaging/linux/icons/hicolor/64x64/apps/app.flowteleprompter.Flow.png b/packaging/linux/icons/hicolor/64x64/apps/app.flowteleprompter.Flow.png
new file mode 100644
index 0000000..bc0bf2c
Binary files /dev/null and b/packaging/linux/icons/hicolor/64x64/apps/app.flowteleprompter.Flow.png differ
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 847cf82..4d4fd05 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -1211,12 +1211,14 @@ dependencies = [
"tauri-plugin-global-shortcut",
"tauri-plugin-opener",
"tauri-plugin-prevent-default",
+ "tauri-plugin-single-instance",
"tauri-plugin-updater",
"tokio",
"uuid",
"vosk",
"windows 0.61.3",
"winreg 0.52.0",
+ "zbus",
"zip 0.6.6",
]
@@ -4659,6 +4661,22 @@ dependencies = [
"thiserror 2.0.18",
]
+[[package]]
+name = "tauri-plugin-single-instance"
+version = "2.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3214becf9ef5783c0ae99a3bb25adf5353a7a16ebf53e74b909e29205735c6c"
+dependencies = [
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "windows-sys 0.60.2",
+ "zbus",
+]
+
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"
@@ -4922,8 +4940,10 @@ dependencies = [
"libc",
"mio",
"pin-project-lite",
+ "signal-hook-registry",
"socket2",
"tokio-macros",
+ "tracing",
"windows-sys 0.61.2",
]
@@ -6464,6 +6484,7 @@ dependencies = [
"rustix",
"serde",
"serde_repr",
+ "tokio",
"tracing",
"uds_windows",
"uuid",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index e91dd89..cf528c0 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -32,11 +32,17 @@ tauri-plugin-prevent-default = "4"
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "time"] }
uuid = { version = "1", features = ["serde", "v4"] }
vosk = "0.3.1"
-windows = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Power", "Win32_UI_WindowsAndMessaging"] }
futures-util = "0.3"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream"] }
zip = { version = "0.6", default-features = false, features = ["deflate"] }
[target.'cfg(windows)'.dependencies]
+windows = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Power", "Win32_UI_WindowsAndMessaging"] }
winreg = "0.52"
+[target.'cfg(unix)'.dependencies]
+tauri-plugin-single-instance = "2"
+
+[target.'cfg(target_os = "linux")'.dependencies]
+zbus = { version = "5", default-features = false, features = ["tokio", "blocking-api"] }
+
diff --git a/src-tauri/build.rs b/src-tauri/build.rs
index 5901c1e..1e726a8 100644
--- a/src-tauri/build.rs
+++ b/src-tauri/build.rs
@@ -60,10 +60,14 @@ fn panic_message(payload: Box) -> String {
}
fn stage_vosk_runtime() -> Result<(), String> {
- if env::var("CARGO_CFG_TARGET_OS").ok().as_deref() != Some("windows") {
- return Ok(());
+ match env::var("CARGO_CFG_TARGET_OS").ok().as_deref() {
+ Some("windows") => stage_vosk_windows(),
+ Some("linux") => stage_vosk_linux(),
+ _ => Ok(()),
}
+}
+fn stage_vosk_windows() -> Result<(), String> {
let runtime_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?)
.join("resources")
@@ -109,6 +113,102 @@ fn stage_vosk_runtime() -> Result<(), String> {
Ok(())
}
+fn stage_vosk_linux() -> Result<(), String> {
+ let manifest_dir =
+ PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?);
+ let vendored_dir = manifest_dir.join("resources").join("vosk").join("linux-x64");
+ let vendored_lib = vendored_dir.join("libvosk.so");
+
+ println!("cargo:rerun-if-env-changed=FLOW_VOSK_LIB_DIR");
+ println!("cargo:rerun-if-changed={}", vendored_dir.display());
+
+ // Resolution order: explicit override, then a vendored copy (AppImage/deb/rpm
+ // packaging), then whatever the system linker can already find (the Arch path,
+ // where `vosk-api` installs libvosk.so into /usr/lib).
+ if let Ok(override_dir) = env::var("FLOW_VOSK_LIB_DIR") {
+ let override_path = PathBuf::from(&override_dir);
+ let override_lib = override_path.join("libvosk.so");
+
+ if !override_lib.is_file() {
+ return Err(format!(
+ "FLOW_VOSK_LIB_DIR is set to '{}' but no libvosk.so was found there.\n\
+ Install the Vosk API runtime (e.g. `sudo pacman -S vosk-api` on Arch \
+ Linux) or point FLOW_VOSK_LIB_DIR at a directory containing libvosk.so.",
+ override_path.display()
+ ));
+ }
+
+ emit_vosk_link_directives(&override_path);
+ return Ok(());
+ }
+
+ if vendored_lib.is_file() {
+ emit_vosk_link_directives(&vendored_dir);
+
+ let profile_dir = resolve_profile_dir()?;
+ fs::create_dir_all(&profile_dir).map_err(|error| error.to_string())?;
+ let destination = profile_dir.join("libvosk.so");
+ copy_if_changed(&vendored_lib, &destination)?;
+
+ return Ok(());
+ }
+
+ if let Some(system_dir) = find_system_vosk_lib_dir() {
+ emit_vosk_link_directives(&system_dir);
+ return Ok(());
+ }
+
+ Err(
+ "Could not find libvosk.so for the Linux build.\n\
+ Install the Vosk API runtime, e.g. `sudo pacman -S vosk-api` on Arch Linux,\n\
+ or place a vendored copy at src-tauri/resources/vosk/linux-x64/libvosk.so\n\
+ (see packaging/linux/fetch-vosk.sh), or set FLOW_VOSK_LIB_DIR to a directory\n\
+ containing libvosk.so."
+ .to_string(),
+ )
+}
+
+fn emit_vosk_link_directives(dir: &Path) {
+ println!("cargo:rustc-link-search=native={}", dir.display());
+ println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
+ println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib/flow");
+}
+
+fn find_system_vosk_lib_dir() -> Option {
+ let output = process::Command::new("ldconfig").arg("-p").output().ok()?;
+ if !output.status.success() {
+ return None;
+ }
+
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ stdout.lines().find_map(|line| {
+ let line = line.trim();
+ if !line.starts_with("libvosk.so") {
+ return None;
+ }
+
+ let resolved_path = line.rsplit("=> ").next()?.trim();
+ PathBuf::from(resolved_path)
+ .parent()
+ .map(Path::to_path_buf)
+ })
+}
+
+fn copy_if_changed(source: &Path, destination: &Path) -> Result<(), String> {
+ if destination.is_file() {
+ let source_metadata = fs::metadata(source).map_err(|error| error.to_string())?;
+ let destination_metadata = fs::metadata(destination).map_err(|error| error.to_string())?;
+
+ if source_metadata.len() == destination_metadata.len() {
+ return Ok(());
+ }
+ }
+
+ fs::copy(source, destination)
+ .map(|_| ())
+ .map_err(|error| error.to_string())
+}
+
fn stage_runtime_file(source: &Path, destination: &Path) -> Result<(), String> {
if destination.is_file() {
let source_metadata = fs::metadata(source).map_err(|error| error.to_string())?;
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index bc37b97..c335fd3 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -8,6 +8,7 @@
"opener:default",
"updater:default",
"core:window:allow-close",
+ "core:window:allow-start-dragging",
"core:window:allow-set-position",
"core:window:allow-set-size",
"core:window:allow-current-monitor",
diff --git a/src-tauri/resources/vosk/linux-x64/.gitkeep b/src-tauri/resources/vosk/linux-x64/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 6d7508a..ec6dce6 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -43,6 +43,7 @@ use uuid::Uuid;
use vosk::{set_log_level, LogLevel};
use zip::ZipArchive;
+mod platform;
mod voice_engine;
#[cfg(windows)]
@@ -1135,7 +1136,12 @@ fn set_sleep_prevention(enabled: bool) {
let _ = unsafe { SetThreadExecutionState(flags) };
}
-#[cfg(not(windows))]
+#[cfg(target_os = "linux")]
+fn set_sleep_prevention(enabled: bool) {
+ platform::set_sleep_prevention(enabled);
+}
+
+#[cfg(all(not(windows), not(target_os = "linux")))]
fn set_sleep_prevention(_enabled: bool) {}
fn load_dev_tray_icon() -> tauri::Result> {
@@ -1167,7 +1173,12 @@ fn show_startup_error_dialog(message: &str) {
};
}
-#[cfg(not(windows))]
+#[cfg(target_os = "linux")]
+fn show_startup_error_dialog(message: &str) {
+ platform::show_startup_error_dialog(message);
+}
+
+#[cfg(all(not(windows), not(target_os = "linux")))]
fn show_startup_error_dialog(_message: &str) {}
#[cfg(windows)]
@@ -1266,6 +1277,24 @@ fn ensure_webview2_runtime_available() -> bool {
true
}
+#[cfg(target_os = "linux")]
+fn format_startup_error_message(error: &tauri::Error) -> String {
+ format!(
+ concat!(
+ "Flow could not start the desktop runtime.\n\n",
+ "Common causes on Linux:\n",
+ "- The WebKitGTK runtime (webkit2gtk-4.1) is missing or too old.\n",
+ "- A GPU/driver DMA-BUF rendering issue is blocking the embedded webview \n",
+ " (try relaunching with WEBKIT_DISABLE_DMABUF_RENDERER=1).\n",
+ "- The user profile or app data location (~/.local/share) is unavailable.\n\n",
+ "Install webkit2gtk-4.1 via your distribution's package manager, then try again.\n\n",
+ "Technical details:\n{}"
+ ),
+ error
+ )
+}
+
+#[cfg(not(target_os = "linux"))]
fn format_startup_error_message(error: &tauri::Error) -> String {
format!(
concat!(
@@ -1374,7 +1403,16 @@ fn ensure_window(
if label == "remote-inbox" {
if let Some(main) = app.get_webview_window("main") {
- builder = builder.owner(&main)?;
+ // `owner()` is Windows-only in Tauri; `parent()` is the cross-platform
+ // equivalent (GTK `set_transient_for` on Linux, child window on macOS).
+ #[cfg(windows)]
+ {
+ builder = builder.owner(&main)?;
+ }
+ #[cfg(not(windows))]
+ {
+ builder = builder.parent(&main)?;
+ }
}
}
@@ -1523,15 +1561,22 @@ fn exit_app(app: &tauri::AppHandle) {
}
fn center_aux_window_on_active_monitor(window: &WebviewWindow, anchor: &WebviewWindow) {
- if let Ok(Some(monitor)) = anchor.current_monitor() {
- if let Ok(window_size) = window.outer_size() {
- let monitor_pos = monitor.position();
- let monitor_size = monitor.size();
- let x = monitor_pos.x + ((monitor_size.width as i32 - window_size.width as i32) / 2).max(0);
- let y = monitor_pos.y + ((monitor_size.height as i32 - window_size.height as i32) / 2).max(0);
- let _ = window.set_position(tauri::Position::Physical(tauri::PhysicalPosition { x, y }));
- return;
- }
+ // Center over the anchor window's own bounds rather than querying monitor
+ // geometry. On X11/XWayland with multiple monitors of differing
+ // resolutions, `Window::current_monitor()` has been observed to report
+ // stale/incorrect geometry, which places the aux window off in an
+ // unrelated corner of the desktop instead of centering it. Anchoring to
+ // the main window's outer position/size avoids monitor enumeration
+ // entirely and is also more intuitive (dialog-style centering).
+ if let (Ok(anchor_pos), Ok(anchor_size), Ok(window_size)) = (
+ anchor.outer_position(),
+ anchor.outer_size(),
+ window.outer_size(),
+ ) {
+ let x = anchor_pos.x + (anchor_size.width as i32 - window_size.width as i32) / 2;
+ let y = anchor_pos.y + (anchor_size.height as i32 - window_size.height as i32) / 2;
+ let _ = window.set_position(tauri::Position::Physical(tauri::PhysicalPosition { x, y }));
+ return;
}
let _ = window.center();
}
@@ -1694,6 +1739,29 @@ fn toggle_clickthrough_impl(
set_clickthrough_impl(app, desktop, !current)
}
+/// Lets a user on native Wayland (where global hotkeys can't be grabbed by
+/// the app itself, see `platform::session_type`) bind desktop-environment
+/// shortcuts to launching `flow --toggle-clickthrough` / `--show` / `--hide`.
+/// The single-instance plugin forwards the second invocation's argv here
+/// instead of starting a second app instance.
+#[cfg(unix)]
+fn handle_secondary_instance_cli_args(app: &tauri::AppHandle, argv: &[String]) {
+ let Some(desktop) = app.try_state::() else {
+ return;
+ };
+
+ for arg in argv.iter().skip(1) {
+ match arg.as_str() {
+ "--toggle-clickthrough" => {
+ let _ = toggle_clickthrough_impl(app, &desktop);
+ }
+ "--show" => show_main_window(app),
+ "--hide" => hide_main_window_impl(app),
+ _ => {}
+ }
+ }
+}
+
#[tauri::command]
fn close_app(app: tauri::AppHandle) {
exit_app(&app);
@@ -1919,6 +1987,72 @@ fn get_distribution_channel() -> String {
.to_string()
}
+#[derive(Debug, Clone, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct PlatformCapabilities {
+ os: &'static str,
+ session: String,
+ capture_protection: bool,
+ global_shortcuts: bool,
+ always_on_top: bool,
+ window_positioning: bool,
+ in_app_updater: bool,
+ package_source: String,
+}
+
+#[cfg(target_os = "linux")]
+fn platform_session_type() -> String {
+ platform::session_type()
+}
+
+#[cfg(not(target_os = "linux"))]
+fn platform_session_type() -> String {
+ "unknown".to_string()
+}
+
+#[cfg(target_os = "linux")]
+fn platform_package_source() -> String {
+ option_env!("FLOW_LINUX_PACKAGE")
+ .unwrap_or("unknown")
+ .to_string()
+}
+
+#[cfg(not(target_os = "linux"))]
+fn platform_package_source() -> String {
+ "unknown".to_string()
+}
+
+#[tauri::command]
+fn get_platform_capabilities() -> PlatformCapabilities {
+ let session = platform_session_type();
+ let package_source = platform_package_source();
+
+ // On Linux, always-on-top / absolute positioning / global shortcuts all
+ // rely on the same thing: an X11 server tao/global-hotkey can talk to.
+ // That's present under X11 and under XWayland, but not under native
+ // Wayland (see plan.md E2 and Task 3.6's verification).
+ let has_x11_window_management = cfg!(target_os = "linux").then(|| session == "x11").unwrap_or(true);
+
+ PlatformCapabilities {
+ os: if cfg!(target_os = "windows") {
+ "windows"
+ } else if cfg!(target_os = "linux") {
+ "linux"
+ } else if cfg!(target_os = "macos") {
+ "macos"
+ } else {
+ "unknown"
+ },
+ session,
+ capture_protection: cfg!(windows),
+ global_shortcuts: has_x11_window_management,
+ always_on_top: has_x11_window_management,
+ window_positioning: has_x11_window_management,
+ in_app_updater: cfg!(windows) || package_source == "appimage",
+ package_source,
+ }
+}
+
#[tauri::command]
async fn download_voice_model(
app: tauri::AppHandle,
@@ -2802,7 +2936,16 @@ pub fn run() {
let voice_model_downloads = VoiceModelDownloads::default();
let voice_engine_state = voice_engine::VoiceEngineState::default();
- let result = tauri::Builder::default()
+ let mut builder = tauri::Builder::default();
+
+ #[cfg(unix)]
+ {
+ builder = builder.plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| {
+ handle_secondary_instance_cli_args(app, &argv);
+ }));
+ }
+
+ let result = builder
.manage(relay.clone())
.manage(desktop_state)
.manage(voice_model_downloads)
@@ -2833,6 +2976,7 @@ pub fn run() {
save_persisted_app_state,
save_persisted_voice_model_registry,
get_distribution_channel,
+ get_platform_capabilities,
fetch_updater_feed_metadata,
list_voice_models,
get_voice_model_status,
diff --git a/src-tauri/src/platform/linux.rs b/src-tauri/src/platform/linux.rs
new file mode 100644
index 0000000..52b2977
--- /dev/null
+++ b/src-tauri/src/platform/linux.rs
@@ -0,0 +1,202 @@
+/*
+ * Flow - A high-performance teleprompter for Windows.
+ * Copyright (C) 2026 Waled Alturkmani (LumoRez07)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ */
+
+use std::env;
+use std::process::Command;
+use std::sync::Mutex;
+
+use zbus::blocking::Connection;
+use zbus::zvariant::OwnedFd;
+
+const STARTUP_ERROR_TITLE: &str = "Flow startup error";
+
+/// Shows a startup error dialog on Linux, mirroring Windows' `MessageBoxW`
+/// use in lib.rs. Tries native dialog tools in order of how commonly they're
+/// preinstalled, since at this point in startup nothing GTK-based can be
+/// assumed to work yet (that's exactly what failed) - `kdialog`/`zenity`/
+/// `notify-send` are separate processes, so they don't depend on our own
+/// (broken) webview or GTK context.
+pub fn show_startup_error_dialog(message: &str) {
+ if try_command_dialog("kdialog", &["--error", message, "--title", STARTUP_ERROR_TITLE]) {
+ return;
+ }
+
+ if try_command_dialog(
+ "zenity",
+ &[
+ "--error",
+ &format!("--title={STARTUP_ERROR_TITLE}"),
+ &format!("--text={message}"),
+ ],
+ ) {
+ return;
+ }
+
+ if try_command_dialog(
+ "notify-send",
+ &["--urgency=critical", STARTUP_ERROR_TITLE, message],
+ ) {
+ return;
+ }
+
+ eprintln!("[flow] {STARTUP_ERROR_TITLE}: {message}");
+}
+
+fn try_command_dialog(program: &str, args: &[&str]) -> bool {
+ Command::new(program)
+ .args(args)
+ .status()
+ .map(|status| status.success())
+ .unwrap_or(false)
+}
+
+const INHIBIT_APP_NAME: &str = "Flow";
+const INHIBIT_REASON: &str = "Teleprompter is actively being used";
+
+/// Holds whatever handles are needed to release an active sleep/screensaver
+/// inhibitor later. Dropping the logind file descriptor releases that
+/// inhibitor automatically; the ScreenSaver one needs an explicit UnInhibit
+/// call on the same connection that requested it.
+struct SleepInhibitor {
+ screensaver: Option<(Connection, u32)>,
+ // Only ever dropped, never read: closing the fd is what releases the
+ // logind inhibitor lock.
+ #[allow(dead_code)]
+ logind_fd: Option,
+}
+
+static SLEEP_INHIBITOR: Mutex