feat(overlay): add layer-shell for wayland support (#680)
This commit is contained in:
parent
0afd5ec01f
commit
9e538b1ca7
3 changed files with 185 additions and 44 deletions
91
src-tauri/Cargo.lock
generated
91
src-tauri/Cargo.lock
generated
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }));
|
||||
|
|
|
|||
Loading…
Reference in a new issue