Skip to content

feat(linux): Wayland/Hyprland parity + consumable Nix package#86

Open
nephalemsec wants to merge 11 commits into
mainfrom
linux/everything
Open

feat(linux): Wayland/Hyprland parity + consumable Nix package#86
nephalemsec wants to merge 11 commits into
mainfrom
linux/everything

Conversation

@nephalemsec

Copy link
Copy Markdown
Collaborator

Summary

Brings Linux — and Wayland/Hyprland specifically — to first-class parity, and makes the Nix flake consumable as an input so NixOS users can install Thoth from their system flake. All 11 commits sit directly on top of current main (0 behind, 11 ahead), and every Linux-specific change is feature- or cfg-gated so macOS and X11 behaviour is unchanged.

Tested live on NixOS + Hyprland (wlroots): GPU Parakeet confirmed via nvidia-smi, keybinds firing, paste landing in both GUI and terminal windows, and the packaged binary built and wrapped end-to-end.

What's in it

Window decorations + custom close button (b176ca4)
Adds a decorations toggle with a custom in-app close button (Linux starts borderless by default). Wires the missing core:window:allow-set-decorations capability that made the old toggle a silent no-op.

Dev overlay fix — keep Tailwind out of Svelte styles (12ece40)
Vite 8 + @tailwindcss/vite was intercepting Svelte component <style> blocks and throwing "Invalid declaration" overlays. The transform now skips .svelte? modules.

Optional GPU (CUDA) Parakeet (159d013, 41309f1)
New opt-in parakeet-cuda Cargo feature (["parakeet", "sherpa-onnx/shared"]) that links the k2-fsa CUDA prebuilt and selects the cuda ONNX execution provider, falling back to CPU if init fails. Default builds are unchanged — CPU static Parakeet, no CUDA pulled in. The flake's .#cuda devShell and the package supply the full CUDA math-lib set (cuDNN, curand, cufft, cublas, cudart) the EP needs at runtime.

Native Hyprland global shortcuts (d1ee8f2)
On Hyprland the XDG GlobalShortcuts portal registers a shortcut but never binds a key, so hotkeys never fire. New shortcuts/hyprland.rs binds directly via hyprctl keyword bind/bindr with an exec dispatcher writing to a FIFO we listen on; each id routes through the same manager::dispatch_shortcut_action the X11/macOS path uses. Detection is runtime (HYPRLAND_INSTANCE_SIGNATURE); non-Hyprland Wayland still uses the portal.

Wayland paste via wl-copy + hyprctl (0996ca8)
wtype/enigo don't reliably inject into Wayland clients, so paste now sets the clipboard with wl-copy and triggers paste with hyprctl dispatch sendshortcut (GUI) / wtype Ctrl+Shift+V (terminals, detected via window class).

Transcription readiness polling (353d3a9)
The Overview "Transcription: Loading…" label never updated once the model was ready. It now polls is_transcription_ready and flips to ready (bounded, self-clearing).

Consumable Nix package (a34733c, 01623fb, dc62ccd)
packages.default builds the full app (GPU Parakeet + Whisper Vulkan) and wraps it with the runtime deps on PATH/LD_LIBRARY_PATH (wl-clipboard, wtype, CUDA/Vulkan libs). Vendoring uses cargoLock (per-crate fetchurl) to dodge the crates.io bulk-download rate-limit 403s, and the package builds with the rust-overlay toolchain so edition-2024 crates compile. Consumers can now do inputs.thoth.packages.${system}.default.

Testing done

  • NixOS + Hyprland: GPU Parakeet confirmed on nvidia-smi
  • Keybinds (Ctrl+Shift+Space + Right Shift) fire on Hyprland
  • Paste lands in both GUI apps and terminal emulators
  • Window decorations toggle + custom close button
  • nix build .#packages.x86_64-linux.default completes and produces a correctly wrapped binary
  • X11 regression pass (unchanged code paths, but worth a sanity check)
  • macOS regression pass (Linux-gated changes only)

Notes for review

  • The CUDA path is entirely opt-in; reviewers on macOS/X11 can ignore it. Default cargo build and pnpm tauri dev are CPU Parakeet as before.
  • The packaged closure is ~6.2 GB because it bundles the CUDA stack + WebKit — a one-time, cacheable fetch.
  • All new Linux behaviour is gated (#[cfg(target_os = ...)] / feature flags), so no cross-platform surface area changes.

Add a `window_decorations` general-setting (default on). When toggled off on
Linux the native titlebar is removed and a custom in-app close button
(WindowControls, in the Settings titlebar) takes over — useful on tiling WMs.

- config: window_decorations field (serde default true)
- capability: core:window:allow-set-decorations (+ minimize/maximize); without
  it setDecorations() silently no-ops
- lib.rs: apply the saved preference at startup (Linux)
- Overview: a "Window Decorations" Switch beside "Show in Dock" (Linux only)
- Settings: custom close button shown when decorations are off
Under Vite 8 + @tailwindcss/vite 4.3 + vite-plugin-svelte 7, the Tailwind
transform matches Svelte's `&lang.css` virtual modules and intermittently
receives the raw `.svelte` source, throwing `Invalid declaration: \`invoke\``
500s in the dev server. Exclude `.svelte?` modules from Tailwind's transform —
no component style uses Tailwind directives (sole entry is src/app.css), so
output is unchanged.
Adds an opt-in `parakeet-cuda` feature that runs Parakeet on an NVIDIA GPU
through sherpa-onnx's CUDA execution provider. Default builds are unchanged
(Parakeet stays CPU; macOS stays CoreML).

- Cargo: `parakeet-cuda = ["parakeet", "sherpa-onnx/shared"]`. sherpa-onnx is
  now `default-features = false` so the link mode is explicit — the default
  `parakeet` feature still resolves to static CPU (sherpa-onnx-sys defaults to
  static when no link feature is set), so nothing changes for current builds.
- parakeet.rs: under `parakeet-cuda` on Linux, request the `cuda` provider and
  fall back to CPU if the EP can't initialise (mirrors the macOS CoreML path).
- flake: a `cuda` dev shell fetches the k2-fsa CUDA prebuilt (CUDA 12 / cuDNN 9)
  and wires SHERPA_ONNX_LIB_DIR + LD_LIBRARY_PATH (sherpa libs, cuDNN, cudart,
  cublas, driver) so the CUDA provider loads at build and runtime.

Build/run:
  nix develop .#cuda
  pnpm tauri dev --no-default-features --features parakeet-cuda,vulkan

Verified: links against the CUDA sherpa-onnx (libonnxruntime_providers_cuda.so)
and the default CPU build is unaffected. Real GPU engagement should be confirmed
on-device with `nvidia-smi` during a transcription — onnxruntime silently falls
back to CPU if cuDNN/driver libs aren't found.
The onnxruntime CUDA provider dlopen()s libcublasLt/cublas/curand/cufft/cudart/
cudnn; a single missing one aborts the process (no CPU fallback). The cuda dev
shell only had cudnn/cudart/cublas, so it failed on libcurand.so.10. Add
libcurand, libcufft, libcusparse to LD_LIBRARY_PATH (cublasLt ships with cublas).
main pastes by setting the clipboard with arboard and synthesising a
wtype/enigo keystroke. On wlroots compositors (Hyprland) arboard's clipboard
isn't readable by other clients and the synthetic keystroke doesn't land, so
paste silently fails. On Wayland, set the clipboard with wl-copy and dispatch
the paste through the compositor: Hyprland uses 'hyprctl dispatch sendshortcut
CTRL,v' for GUI apps and wtype Ctrl+Shift+V for terminals (detected via
hyprctl activewindow); other compositors keep the wtype/enigo path.
The Overview System status checked is_transcription_ready once at mount; if the
model was downloaded but the warm-up thread hadn't finished, it stuck on
'Loading…' until the next pane visit. Poll every 1s (max 30s) until ready.
On Hyprland the XDG GlobalShortcuts portal registers a shortcut but never binds
a key (the binding comes back empty in the activation log), so global hotkeys
never fire. Detect Hyprland (HYPRLAND_INSTANCE_SIGNATURE) and bind the
configured accelerators with 'hyprctl keyword bind/bindr', whose exec dispatcher
writes the shortcut id to a FIFO; a listener thread routes each id through
manager::dispatch_shortcut_action — the same dispatcher used by the X11/macOS
plugin callback. Modifier-only keys (e.g. Right Shift) use bindr + keycodes.
Adds packages.default so the flake is consumable as an input:
  inputs.thoth.url = "github:poodle64/thoth";
  environment.systemPackages = [ inputs.thoth.packages.${system}.default ];

buildRustPackage with parakeet-cuda + vulkan, linking the pinned CUDA
sherpa-onnx via SHERPA_ONNX_LIB_DIR; the binary is wrapped with wl-clipboard,
wtype, gsettings, canberra and the CUDA/Vulkan runtime libraries (hyprctl is
taken from the host PATH). pnpmDeps hash pinned; cargoHash left as a one-time
TODO (crates.io rate-limited the vendor fetch where this was authored).
buildRustPackage's default cargoHash uses fetch-cargo-vendor-util, which bulk-
fetches every crate from crates.io's api/v1 download endpoint in parallel and
gets random 403s (rate-limiting) — the whole vendor FOD then fails and re-fetches
from scratch. Switch to cargoLock: each crate is its own fetchurl derivation, so
successful fetches cache and only failures retry. Build with --max-jobs 2 to
stay under the limit. No cargoHash needed (registry checksums come from
Cargo.lock); the one git dep (fluidaudio-rs) is pinned in outputHashes.
pkgs.rustPlatform uses the pinned nixpkgs' rustc, which is too old for some deps
(libsqlite3-sys 0.38.1 uses the cfg_select! macro). Build with the same newer
rust-overlay toolchain the dev shell uses, via makeRustPlatform.
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.

1 participant