fix(preview): pin preview overlay across virtual desktops on KDE/GNOME Wayland

Without _NET_WM_STATE_STICKY the preview overlay only renders on whichever
virtual desktop it opened on — switch desktops mid-dictation and the raw
transcription stream vanishes exactly when you need it (the whole point of
the overlay is that you're working in another app).

visible_on_all_workspaces(true) on the WebviewWindowBuilder sets STICKY via
GTK on X11/XWayland, which KWin + Mutter both honour. Combined with the
existing skip_taskbar(true) — KWin's default Alt+Tab list already respects
_NET_WM_STATE_SKIP_TASKBAR — the preview now behaves like the assistive
overlay it's meant to be: follows you, out of the way of window switchers.

Applied only to the preview window. Task-float and transcript-viewer are
primary surfaces that should stay on their own desktop, so they keep the
current behaviour.

Follow-up if dogfooding shows Alt+Tab clutter on non-KDE compositors: layer
a GTK WindowTypeHint::Utility via with_webview. Not needed for KWin.

Matches OpenWhispr's PR #183 shape for KDE Plasma.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-21 09:28:29 +01:00
parent bc1ae3968e
commit 9b5d08af3d

View File

@@ -58,6 +58,14 @@ pub async fn open_preview_window(app: tauri::AppHandle) -> Result<(), String> {
let use_native_decorations = cfg!(target_os = "linux"); let use_native_decorations = cfg!(target_os = "linux");
// Preview is a transient helper overlay, not a primary surface. It
// should (a) follow the user across virtual desktops — otherwise the
// overlay vanishes the moment they switch workspace mid-dictation —
// and (b) stay out of the Alt+Tab / taskbar lists. skip_taskbar covers
// both on KWin (KWin's default Alt+Tab list reads _NET_WM_STATE_SKIP_TASKBAR);
// visible_on_all_workspaces sets _NET_WM_STATE_STICKY via GTK on X11
// / XWayland so the overlay is pinned. Matches the ergonomic OpenWhispr
// PR #183 shipped for KDE Plasma Wayland.
let mut builder = WebviewWindowBuilder::new( let mut builder = WebviewWindowBuilder::new(
&app, &app,
"transcription-preview", "transcription-preview",
@@ -69,6 +77,7 @@ pub async fn open_preview_window(app: tauri::AppHandle) -> Result<(), String> {
.max_inner_size(520.0, 360.0) .max_inner_size(520.0, 360.0)
.always_on_top(true) .always_on_top(true)
.skip_taskbar(true) .skip_taskbar(true)
.visible_on_all_workspaces(true)
.focused(false) .focused(false)
.decorations(use_native_decorations) .decorations(use_native_decorations)
.resizable(true); .resizable(true);