From 1a7cdd64bb6ab2179fffc3d884675dc298ec14a8 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Wed, 9 Sep 2026 01:04:19 +0800 Subject: [PATCH 1/8] fix(audio): fix return types for Linux speaker input stub --- src-tauri/crates/audio/src/speaker/linux.rs | 9 +++++---- src-tauri/crates/audio/src/speaker/mod.rs | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src-tauri/crates/audio/src/speaker/linux.rs b/src-tauri/crates/audio/src/speaker/linux.rs index 213b30e..a9b0a68 100644 --- a/src-tauri/crates/audio/src/speaker/linux.rs +++ b/src-tauri/crates/audio/src/speaker/linux.rs @@ -1,14 +1,15 @@ +use anyhow::Result; use futures_util::Stream; pub struct SpeakerInput {} impl SpeakerInput { - pub fn new(_sample_rate_override: Option) -> Self { - Self {} + pub fn new(_sample_rate_override: Option) -> Result { + Ok(Self {}) } - pub fn stream(self) -> SpeakerStream { - SpeakerStream::new() + pub fn stream(self) -> Result { + Ok(SpeakerStream::new()) } } diff --git a/src-tauri/crates/audio/src/speaker/mod.rs b/src-tauri/crates/audio/src/speaker/mod.rs index 33729a3..9acb5d4 100644 --- a/src-tauri/crates/audio/src/speaker/mod.rs +++ b/src-tauri/crates/audio/src/speaker/mod.rs @@ -54,7 +54,13 @@ impl SpeakerInput { Ok(SpeakerStream { inner }) } - #[cfg(not(any(target_os = "macos", target_os = "windows")))] + #[cfg(target_os = "linux")] + pub fn stream(self) -> Result { + let inner = self.inner.stream()?; + Ok(SpeakerStream { inner }) + } + + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] pub fn stream(self) -> Result { Err(anyhow::anyhow!( "'SpeakerInput::stream' is not supported on this platform" @@ -74,25 +80,26 @@ impl Stream for SpeakerStream { mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - #[cfg(any(target_os = "macos", target_os = "windows"))] + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] { self.inner.poll_next_unpin(cx) } - #[cfg(not(any(target_os = "macos", target_os = "windows")))] + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] { + let _ = (self, cx); std::task::Poll::Pending } } } impl crate::AsyncSource for SpeakerStream { - #[cfg(any(target_os = "macos", target_os = "windows"))] + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] fn sample_rate(&self) -> u32 { self.inner.sample_rate() } - #[cfg(not(any(target_os = "macos", target_os = "windows")))] + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] fn sample_rate(&self) -> u32 { 0 } From 1a9895938f83a6d79b78d05f1327d47b54629d38 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Wed, 9 Sep 2026 01:04:22 +0800 Subject: [PATCH 2/8] fix(linux): add linux platform capability and bypass WebKit compositing blank screen --- src-tauri/capabilities/default.json | 2 +- src-tauri/src/main.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 6f6aa0f..c40ad1f 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -3,7 +3,7 @@ "identifier": "default", "description": "Minimum permissions required by the FlashVoice desktop windows", "windows": ["*"], - "platforms": ["macOS", "windows"], + "platforms": ["macOS", "windows", "linux"], "permissions": [ "core:default", "core:window:default", diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e11e2c4..46a2580 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,6 +2,16 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { + #[cfg(target_os = "linux")] + { + if std::env::var_os("WEBKIT_DISABLE_COMPOSITING_MODE").is_none() { + std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1"); + } + if std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() { + std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); + } + } + if let Err(error) = flashvoice_lib::main() { eprintln!("FlashVoice failed to start: {error}"); std::process::exit(1); From 9abc108437d3079c18b0b2dfb2767cec1c9b2062 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Wed, 9 Sep 2026 09:21:00 +0800 Subject: [PATCH 3/8] fix(indicator): prevent crash on Wayland and preserve pill capsule shape --- src-tauri/plugins/windows/src/commands.rs | 5 ++- src-tauri/plugins/windows/src/ext.rs | 45 +++++++++++++++-------- src/pages/indicator/index.vue | 5 ++- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src-tauri/plugins/windows/src/commands.rs b/src-tauri/plugins/windows/src/commands.rs index 987adbd..4718a73 100644 --- a/src-tauri/plugins/windows/src/commands.rs +++ b/src-tauri/plugins/windows/src/commands.rs @@ -175,7 +175,10 @@ pub async fn window_set_dock_visibility( window: FlashVoiceWindow, visible: bool, ) -> Result<(), String> { - #[cfg(not(target_os = "windows"))] + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + let _ = (&app, &window, visible); + + #[cfg(target_os = "macos")] let _ = &window; #[cfg(target_os = "macos")] diff --git a/src-tauri/plugins/windows/src/ext.rs b/src-tauri/plugins/windows/src/ext.rs index 3799a29..f7bcd15 100644 --- a/src-tauri/plugins/windows/src/ext.rs +++ b/src-tauri/plugins/windows/src/ext.rs @@ -99,7 +99,7 @@ impl FlashVoiceWindow { pub fn get_min_size(&self) -> LogicalSize { match self { Self::Main => LogicalSize::new(1000.0, 700.0), - Self::Indicator => LogicalSize::new(280.0, 34.0), + Self::Indicator => LogicalSize::new(120.0, 34.0), } } @@ -109,9 +109,14 @@ impl FlashVoiceWindow { pos: KnownPosition, ) -> Result<(), crate::Error> { if let Some(window) = self.get(app) { - let monitor = window - .current_monitor()? - .ok_or(crate::Error::MonitorNotFound)?; + let monitor = match window.current_monitor()? { + Some(m) => Some(m), + None => match window.primary_monitor()? { + Some(m) => Some(m), + None => window.available_monitors()?.into_iter().next(), + }, + } + .ok_or(crate::Error::MonitorNotFound)?; let monitor_size = monitor.size(); let window_size = window.outer_size()?; @@ -143,9 +148,18 @@ impl FlashVoiceWindow { } fn position_indicator_bottom_center(&self, window: &WebviewWindow) -> Result<(), crate::Error> { - let monitor = window - .current_monitor()? - .ok_or(crate::Error::MonitorNotFound)?; + let monitor = match window.current_monitor()? { + Some(m) => Some(m), + None => match window.primary_monitor()? { + Some(m) => Some(m), + None => window.available_monitors()?.into_iter().next(), + }, + }; + + let Some(monitor) = monitor else { + tracing::debug!("No monitor found when positioning indicator"); + return Ok(()); + }; let scale_factor = monitor.scale_factor(); let work_area = monitor.work_area(); @@ -167,7 +181,7 @@ impl FlashVoiceWindow { indicator_y = position.y, "Positioned the live transcription indicator within the monitor work area" ); - window.set_position(position)?; + let _ = window.set_position(position); Ok(()) } @@ -233,19 +247,20 @@ impl FlashVoiceWindow { window.center()?; } Self::Indicator => { + let _ = window.set_size(default_size); + let _ = window.set_min_size(Some(min_size)); + let _ = window.set_decorations(false); + let _ = window.set_shadow(false); + let _ = window.set_always_on_top(true); + let _ = window.set_skip_taskbar(true); + #[cfg(not(target_os = "linux"))] window.set_resizable(false)?; - window.set_size(default_size)?; - window.set_min_size(Some(min_size))?; - window.set_decorations(false)?; - window.set_shadow(false)?; - window.set_always_on_top(true)?; - window.set_skip_taskbar(true)?; } }; } if self == &Self::Indicator { - self.position_indicator_bottom_center(&window)?; + let _ = self.position_indicator_bottom_center(&window); } // Skip focus for indicator to avoid stealing focus from other apps diff --git a/src/pages/indicator/index.vue b/src/pages/indicator/index.vue index 1e75e50..53a72e6 100644 --- a/src/pages/indicator/index.vue +++ b/src/pages/indicator/index.vue @@ -235,10 +235,11 @@ const cancelRecording = (event: Event) => {