diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 6a4388f..8f372c4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -340,7 +340,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -632,7 +632,7 @@ checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -739,7 +739,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec 1.15.1", - "target-lexicon", + "target-lexicon 0.12.16", +] + +[[package]] +name = "cfg-expr" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" +dependencies = [ + "smallvec 1.15.1", + "target-lexicon 0.13.3", ] [[package]] @@ -2054,7 +2064,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -2071,7 +2081,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -2085,7 +2095,7 @@ dependencies = [ "gobject-sys", "libc", "pkg-config", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -2111,7 +2121,7 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps", + "system-deps 6.2.2", "x11", ] @@ -2201,7 +2211,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", "winapi", ] @@ -2249,7 +2259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" dependencies = [ "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -2284,7 +2294,7 @@ checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -2308,6 +2318,34 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "gtk-layer-shell" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc759b3184830a547b31549ab40c4b54450ab702bba79ba23f049bc1d1e3ca98" +dependencies = [ + "bitflags 2.10.0", + "gdk", + "glib", + "glib-sys", + "gtk", + "gtk-layer-shell-sys", + "libc", +] + +[[package]] +name = "gtk-layer-shell-sys" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eee067e022416d53a70de69d3d3929d8a6e687f3278b8934faa671750fa6eb" +dependencies = [ + "gdk-sys", + "glib-sys", + "gtk-sys", + "libc", + "system-deps 7.0.7", +] + [[package]] name = "gtk-sys" version = "0.18.2" @@ -2323,7 +2361,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -2381,6 +2419,8 @@ dependencies = [ "ferrous-opencc", "flate2", "futures-util", + "gtk", + "gtk-layer-shell", "handy-keys", "hound", "log", @@ -2976,7 +3016,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -4089,7 +4129,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -5636,7 +5676,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -5993,13 +6033,26 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr", + "cfg-expr 0.15.8", "heck 0.5.0", "pkg-config", "toml 0.8.2", "version-compare", ] +[[package]] +name = "system-deps" +version = "7.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +dependencies = [ + "cfg-expr 0.20.6", + "heck 0.5.0", + "pkg-config", + "toml 0.9.8", + "version-compare", +] + [[package]] name = "tao" version = "0.34.5" @@ -6074,6 +6127,12 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + [[package]] name = "tauri" version = "2.9.1" @@ -7530,7 +7589,7 @@ dependencies = [ "libc", "pkg-config", "soup3-sys", - "system-deps", + "system-deps 6.2.2", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e44a8e7..0b7bb0c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -96,6 +96,10 @@ windows = { version = "0.61.3", features = [ [target.'cfg(target_os = "macos")'.dependencies] tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2.1" } +[target.'cfg(target_os = "linux")'.dependencies] +gtk-layer-shell = { version = "0.8", features = ["v0_6"] } +gtk = "0.18" + [profile.release] lto = true codegen-units = 1 diff --git a/src-tauri/src/overlay.rs b/src-tauri/src/overlay.rs index 4d328a4..5fa51a1 100644 --- a/src-tauri/src/overlay.rs +++ b/src-tauri/src/overlay.rs @@ -15,6 +15,9 @@ use tauri::WebviewUrl; #[cfg(target_os = "macos")] use tauri_nspanel::{tauri_panel, CollectionBehavior, PanelBuilder, PanelLevel}; +#[cfg(target_os = "linux")] +use gtk_layer_shell::{Edge, KeyboardMode, Layer, LayerShell}; + #[cfg(target_os = "macos")] tauri_panel! { panel!(RecordingOverlayPanel { @@ -39,6 +42,50 @@ const OVERLAY_BOTTOM_OFFSET: f64 = 15.0; #[cfg(any(target_os = "windows", target_os = "linux"))] const OVERLAY_BOTTOM_OFFSET: f64 = 40.0; +#[cfg(target_os = "linux")] +fn update_gtk_layer_shell_anchors(overlay_window: &tauri::webview::WebviewWindow) { + let window_clone = overlay_window.clone(); + let _ = overlay_window.run_on_main_thread(move || { + // Try to get the GTK window from the Tauri webview + if let Ok(gtk_window) = window_clone.gtk_window() { + let settings = settings::get_settings(window_clone.app_handle()); + match settings.overlay_position { + OverlayPosition::Top => { + gtk_window.set_anchor(Edge::Top, true); + gtk_window.set_anchor(Edge::Bottom, false); + } + OverlayPosition::Bottom | OverlayPosition::None => { + gtk_window.set_anchor(Edge::Bottom, true); + gtk_window.set_anchor(Edge::Top, false); + } + } + } + }); +} + +/// Initializes GTK layer shell for Linux overlay window +/// Returns true if layer shell was successfully initialized, false otherwise +#[cfg(target_os = "linux")] +fn init_gtk_layer_shell(overlay_window: &tauri::webview::WebviewWindow) -> bool { + if !gtk_layer_shell::is_supported() { + return false; + } + + // Try to get the GTK window from the Tauri webview + if let Ok(gtk_window) = overlay_window.gtk_window() { + // Initialize layer shell + gtk_window.init_layer_shell(); + gtk_window.set_layer(Layer::Overlay); + gtk_window.set_keyboard_mode(KeyboardMode::None); + gtk_window.set_exclusive_zone(0); + + update_gtk_layer_shell_anchors(overlay_window); + + return true; + } + false +} + /// Forces a window to be topmost using Win32 API (Windows only) /// This is more reliable than Tauri's set_always_on_top which can be overridden #[cfg(target_os = "windows")] @@ -134,35 +181,56 @@ fn calculate_overlay_position(app_handle: &AppHandle) -> Option<(f64, f64)> { /// Creates the recording overlay window and keeps it hidden by default #[cfg(not(target_os = "macos"))] pub fn create_recording_overlay(app_handle: &AppHandle) { - if let Some((x, y)) = calculate_overlay_position(app_handle) { - match WebviewWindowBuilder::new( - app_handle, - "recording_overlay", - tauri::WebviewUrl::App("src/overlay/index.html".into()), - ) - .title("Recording") - .position(x, y) - .resizable(false) - .inner_size(OVERLAY_WIDTH, OVERLAY_HEIGHT) - .shadow(false) - .maximizable(false) - .minimizable(false) - .closable(false) - .accept_first_mouse(true) - .decorations(false) - .always_on_top(true) - .skip_taskbar(true) - .transparent(true) - .focused(false) - .visible(false) - .build() - { - Ok(_window) => { - debug!("Recording overlay window created successfully (hidden)"); - } - Err(e) => { - debug!("Failed to create recording overlay window: {}", e); + let position = calculate_overlay_position(app_handle); + + // On Linux (Wayland), monitor detection often fails, but we don't need exact coordinates + // for Layer Shell as we use anchors. On other platforms, we require a position. + #[cfg(not(target_os = "linux"))] + if position.is_none() { + debug!("Failed to determine overlay position, not creating overlay window"); + return; + } + + let mut builder = WebviewWindowBuilder::new( + app_handle, + "recording_overlay", + tauri::WebviewUrl::App("src/overlay/index.html".into()), + ) + .title("Recording") + .resizable(false) + .inner_size(OVERLAY_WIDTH, OVERLAY_HEIGHT) + .shadow(false) + .maximizable(false) + .minimizable(false) + .closable(false) + .accept_first_mouse(true) + .decorations(false) + .always_on_top(true) + .skip_taskbar(true) + .transparent(true) + .focused(false) + .visible(false); + + if let Some((x, y)) = position { + builder = builder.position(x, y); + } + + match builder.build() { + Ok(window) => { + #[cfg(target_os = "linux")] + { + // Try to initialize GTK layer shell, ignore errors if compositor doesn't support it + if init_gtk_layer_shell(&window) { + debug!("GTK layer shell initialized for overlay window"); + } else { + debug!("GTK layer shell not available, falling back to regular window"); + } } + + debug!("Recording overlay window created successfully (hidden)"); + } + Err(e) => { + debug!("Failed to create recording overlay window: {}", e); } } } @@ -214,6 +282,11 @@ pub fn show_recording_overlay(app_handle: &AppHandle) { if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") { // Update position before showing to prevent flicker from position changes + #[cfg(target_os = "linux")] + { + update_gtk_layer_shell_anchors(&overlay_window); + } + if let Some((x, y)) = calculate_overlay_position(app_handle) { let _ = overlay_window .set_position(tauri::Position::Logical(tauri::LogicalPosition { x, y })); @@ -255,6 +328,11 @@ pub fn show_transcribing_overlay(app_handle: &AppHandle) { /// Updates the overlay window position based on current settings pub fn update_overlay_position(app_handle: &AppHandle) { if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") { + #[cfg(target_os = "linux")] + { + update_gtk_layer_shell_anchors(&overlay_window); + } + if let Some((x, y)) = calculate_overlay_position(app_handle) { let _ = overlay_window .set_position(tauri::Position::Logical(tauri::LogicalPosition { x, y }));