fix(linux): reposition notifications for multi-monitor and arbitrary resolutions
This commit is contained in:
parent
efd7d65bd6
commit
80f489e2fa
2 changed files with 109 additions and 24 deletions
|
|
@ -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<gdk::Monitor> {
|
||||
if let Ok(out) = std::process::Command::new("hyprctl").args(["monitors", "-j"]).output() {
|
||||
if let Ok(v) = serde_json::from_slice::<Vec<serde_json::Value>>(&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
|
||||
|
|
|
|||
|
|
@ -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::<Vec<serde_json::Value>>(&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::<f32>(),
|
||||
dims[1].parse::<f32>(),
|
||||
parts[1].parse::<f32>(),
|
||||
parts[2].parse::<f32>(),
|
||||
) {
|
||||
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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue