diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 645780a..fc5d5b2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -68,8 +68,53 @@ async fn save_preferences( .map_err(|e| e.to_string()) } +/// On Linux Wayland sessions (KDE, GNOME) WebKitGTK + DMABUF rendering hits +/// known crashes that the HANDOVER documents working around with a manual +/// env-var prefix: +/// +/// env GDK_BACKEND=x11 WINIT_UNIX_BACKEND=x11 \ +/// WEBKIT_DISABLE_DMABUF_RENDERER=1 npm run tauri dev +/// +/// Detect the Wayland session at startup and apply the env vars before +/// anything else loads, so users do not need to remember the prefix and +/// the dev launcher / packaged app both work out of the box. +/// +/// Inspired by Open-Whispr's similar Chromium self-relaunch (with +/// --ozone-platform=x11). Day 6 of the upgrade plan. +#[cfg(target_os = "linux")] +fn ensure_x11_on_wayland() { + // If the user explicitly opted in to Wayland (XDG_SESSION_TYPE set by + // their session, KDE / GNOME compositor flags), force WebKitGTK + GDK + // onto X11 via XWayland. Idempotent: if a value is already set, keep + // it (the user knows best). + let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_default(); + let is_wayland = session_type.eq_ignore_ascii_case("wayland"); + if !is_wayland { + return; + } + + let set_if_unset = |key: &str, value: &str| { + if std::env::var_os(key).is_none() { + // SAFETY: setting env vars before any threads spawn (we are + // pre-Tauri-Builder here). This block is the only place these + // are written. + unsafe { std::env::set_var(key, value); } + eprintln!("[startup] Wayland workaround: {key}={value}"); + } + }; + + set_if_unset("GDK_BACKEND", "x11"); + set_if_unset("WINIT_UNIX_BACKEND", "x11"); + set_if_unset("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); + // PipeWire screen-capture portal still works fine through XWayland; + // we only redirect the GUI rendering path. +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + #[cfg(target_os = "linux")] + ensure_x11_on_wayland(); + tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) diff --git a/src/lib/pages/SettingsPage.svelte b/src/lib/pages/SettingsPage.svelte index 308e858..84fddaa 100644 --- a/src/lib/pages/SettingsPage.svelte +++ b/src/lib/pages/SettingsPage.svelte @@ -43,6 +43,49 @@ audioDevices = []; } } + + // --- Custom vocabulary (dictionary) — Day 5 of upgrade plan --- + // Backed by SQLite `dictionary` table via list_dictionary_command etc. + // Terms get injected into the LLM cleanup prompt so the model preserves + // the user's spellings (e.g. medication names, jargon, place names). + let vocabulary = $state([]); + let vocabularyError = $state(null); + let newVocabTerm = $state(""); + let newVocabNote = $state(""); + + async function refreshVocabulary() { + vocabularyError = null; + try { + vocabulary = await invoke("list_dictionary_command"); + } catch (err) { + vocabularyError = "Could not load vocabulary: " + (err?.message || err); + } + } + + async function addVocabTerm() { + const term = newVocabTerm.trim(); + if (!term) return; + try { + await invoke("add_dictionary_entry_command", { + term, + note: newVocabNote.trim() || null, + }); + newVocabTerm = ""; + newVocabNote = ""; + await refreshVocabulary(); + } catch (err) { + vocabularyError = "Could not save term: " + (err?.message || err); + } + } + + async function deleteVocabTerm(id) { + try { + await invoke("delete_dictionary_entry_command", { id }); + await refreshVocabulary(); + } catch (err) { + vocabularyError = "Could not delete term: " + (err?.message || err); + } + } let parakeetDownloaded = $state(false); let parakeetDownloading = $state(false); let parakeetProgress = $state(0); @@ -131,6 +174,10 @@ // the user opens the Audio section. refreshAudioDevices(); + // Pre-load vocabulary too. Both lists are small; pre-fetching avoids + // a flash of "no data" when the section opens. + refreshVocabulary(); + try { downloadedModels = await invoke("list_models"); } catch {} @@ -380,6 +427,76 @@ {/if} + +
+ Words and phrases the AI cleanup pass should preserve exactly. Useful for medication names, place names, jargon, names of people in your support network, anything Whisper tends to mishear. +
+ +{vocabularyError}
+ {/if} + + {#if vocabulary.length === 0} +No terms yet. Add one above.
+ {:else} +