diff --git a/src/overlay.rs b/src/overlay.rs index cc42bb9..d2efd44 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -181,10 +181,32 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) { (accent_hex, accent_rgb) }; + fn get_active_gdk_monitor(display: &gdk::Display) -> Option { + if let Ok(out) = std::process::Command::new("hyprctl").args(["monitors", "-j"]).output() { + if let Ok(v) = serde_json::from_slice::>(&out.stdout) { + let focused = v.iter().find(|m| m["focused"].as_bool().unwrap_or(false)) + .or_else(|| v.first()); + if let Some(m) = focused { + let fx = m["x"].as_i64().unwrap_or(0) as i32; + let fy = m["y"].as_i64().unwrap_or(0) as i32; + for i in 0..display.n_monitors() { + if let Some(mon) = display.monitor(i) { + let geom = mon.geometry(); + if geom.x() == fx && geom.y() == fy { + return Some(mon); + } + } + } + } + } + } + display.primary_monitor().or_else(|| display.monitor(0)) + } + app.connect_activate(move |app| { let window = ApplicationWindow::builder() .application(app) - .default_width(340) + .default_width(360) .default_height(64) .build(); @@ -193,26 +215,31 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) { #[cfg(not(target_os = "linux"))] let layer_shell_ok = false; + let active_monitor = gdk::Display::default().and_then(|d| get_active_gdk_monitor(&d)); + if layer_shell_ok { window.init_layer_shell(); window.set_layer(Layer::Overlay); window.set_namespace("scythe-notification"); + #[cfg(target_os = "linux")] + if let Some(ref mon) = active_monitor { + gtk_layer_shell::LayerShell::set_monitor(&window, mon); + } window.set_anchor(gtk_layer_shell::Edge::Top, true); window.set_anchor(gtk_layer_shell::Edge::Right, true); - window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 24); - window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, 24); + window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 28); + window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, 28); window.set_keyboard_interactivity(false); } else { window.set_decorated(false); window.set_keep_above(true); window.set_skip_taskbar_hint(true); window.set_accept_focus(false); - if let Some(display) = gdk::Display::default() - && let Some(mon) = display.primary_monitor().or_else(|| display.monitor(0)) { - let geom = mon.geometry(); - let x = geom.x() + geom.width() - 340 - 24; - let y = geom.y() + 24; - window.move_(x, y); + if let Some(ref mon) = active_monitor { + let geom = mon.geometry(); + let x = geom.x() + geom.width() - 360 - 28; + let y = geom.y() + 28; + window.move_(x, y); } } @@ -406,8 +433,8 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) { hbox.pack_start(&vbox, true, true, 0); let fixed = Fixed::new(); - fixed.set_size_request(340, 64); - fixed.put(&hbox, 340, 4); + fixed.set_size_request(360, 64); + fixed.put(&hbox, 360, 4); window.add(&fixed); window.show_all(); @@ -426,16 +453,16 @@ pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) { let slide_x = if elapsed < 0.35 { let t = (elapsed / 0.35).min(1.0); let ease = 1.0 - (1.0 - t).powi(3); - (1.0 - ease) * 340.0 + (1.0 - ease) * 360.0 } else if elapsed < 2.40 { 0.0 } else { let t = ((elapsed - 2.40) / 0.40).min(1.0); let ease = t.powi(3); - ease * 340.0 + ease * 360.0 }; - let target_x = (slide_x + 10.0).round() as i32; + let target_x = (slide_x + 14.0).round() as i32; fixed_clone.move_(&hbox_clone, target_x, 4); window_clone.queue_draw(); gtk::glib::ControlFlow::Continue diff --git a/src/overlay_egui.rs b/src/overlay_egui.rs index 97fc09d..b4e5250 100644 --- a/src/overlay_egui.rs +++ b/src/overlay_egui.rs @@ -2030,7 +2030,7 @@ impl ScytheOverlayApp { let card_w = 320.0; let card_h = 56.0; let toast_rect = egui::Rect::from_min_size( - egui::pos2(screen_w - card_w - 24.0 + slide_x, 24.0), + egui::pos2(screen_w - card_w - 28.0 + slide_x, 28.0), egui::vec2(card_w, card_h), ); @@ -2287,22 +2287,79 @@ pub fn apply_windows_transparency(title: &str) { } } +#[cfg(not(target_os = "windows"))] +pub fn query_focused_monitor_rect() -> (f32, f32, f32, f32) { + // 1. hyprctl monitors -j (Hyprland/Wayland) + if let Ok(out) = std::process::Command::new("hyprctl").args(["monitors", "-j"]).output() { + if let Ok(v) = serde_json::from_slice::>(&out.stdout) { + let focused = v.iter().find(|m| m["focused"].as_bool().unwrap_or(false)) + .or_else(|| v.first()); + if let Some(m) = focused { + let x = m["x"].as_f64().unwrap_or(0.0) as f32; + let y = m["y"].as_f64().unwrap_or(0.0) as f32; + let w = m["width"].as_f64().unwrap_or(1920.0) as f32; + let h = m["height"].as_f64().unwrap_or(1080.0) as f32; + let scale = m["scale"].as_f64().unwrap_or(1.0) as f32; + let lw = if scale > 0.0 { w / scale } else { w }; + let lh = if scale > 0.0 { h / scale } else { h }; + let lx = if scale > 0.0 { x / scale } else { x }; + let ly = if scale > 0.0 { y / scale } else { y }; + if lw > 320.0 && lh > 200.0 { + return (lx, ly, lw, lh); + } + } + } + } + // 2. xrandr fallback (X11 / XWayland) + if let Ok(out) = std::process::Command::new("xrandr").output() { + if let Ok(text) = std::str::from_utf8(&out.stdout) { + for line in text.lines() { + if line.contains(" connected") { + for part in line.split_whitespace() { + if part.contains('x') && part.contains('+') { + let parts: Vec<&str> = part.split('+').collect(); + if parts.len() >= 3 { + let dims: Vec<&str> = parts[0].split('x').collect(); + if dims.len() == 2 { + if let (Ok(w), Ok(h), Ok(x), Ok(y)) = ( + dims[0].parse::(), + dims[1].parse::(), + parts[1].parse::(), + parts[2].parse::(), + ) { + if w > 320.0 && h > 200.0 { + return (x, y, w, h); + } + } + } + } + } + } + } + } + } + } + (0.0, 0.0, 1920.0, 1080.0) +} + pub fn run_egui_overlay() { #[cfg(target_os = "windows")] - let (screen_w, screen_h): (f32, f32) = unsafe { + let (screen_x, screen_y, screen_w, screen_h): (f32, f32, f32, f32) = unsafe { use windows::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN}; let w = GetSystemMetrics(SM_CXSCREEN) as f32; let h = GetSystemMetrics(SM_CYSCREEN) as f32; - if w > 100.0 && h > 100.0 { (w, h) } else { (1920.0, 1080.0) } + let sw = if w > 100.0 { w } else { 1920.0 }; + let sh = if h > 100.0 { h } else { 1080.0 }; + (0.0, 0.0, sw, sh) }; #[cfg(not(target_os = "windows"))] - let (screen_w, screen_h): (f32, f32) = (1920.0, 1080.0); + let (screen_x, screen_y, screen_w, screen_h) = query_focused_monitor_rect(); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_title("Scythe") .with_app_id("scythe-overlay") - .with_position([0.0, 0.0]) + .with_position([screen_x, screen_y]) .with_inner_size([screen_w, screen_h]) .with_maximized(false) .with_resizable(false) @@ -2474,16 +2531,17 @@ pub fn run_egui_toast(title: &str, subtitle: &str, icon: crate::overlay::ToastIc } #[cfg(target_os = "windows")] - let screen_w: f32 = unsafe { + let (mon_x, screen_w): (f32, f32) = unsafe { use windows::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXSCREEN}; let w = GetSystemMetrics(SM_CXSCREEN) as f32; - if w > 100.0 { w } else { 1920.0 } + let sw = if w > 100.0 { w } else { 1920.0 }; + (0.0, sw) }; #[cfg(not(target_os = "windows"))] - let screen_w: f32 = 1920.0; + let (mon_x, _mon_y, screen_w, _screen_h) = query_focused_monitor_rect(); - let pos_x = (screen_w - toast_w - 24.0).max(10.0); - let pos_y = 24.0; + let pos_x = (mon_x + screen_w - toast_w - 28.0).max(10.0); + let pos_y = 28.0; let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default()