a lightweight, interval-based utility to combat digital strain through "Ma" (intentional pauses) for the eyes and body.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: detect monitor setup change

+56 -39
+30
src/main.rs
··· 90 90 let mut snooze_used = false; 91 91 let mut dark_check_tick: u32 = 0; 92 92 let mut last_dark: Option<bool> = None; 93 + let mut monitor_check_tick: u32 = 0; 94 + let mut known_monitors: Vec<overlay::monitors::MonitorInfo> = { 95 + let mut v = overlay::monitors::enumerate(); 96 + v.sort_by_key(|m| (m.x, m.y)); 97 + v 98 + }; 99 + let mut overlay_needs_rebuild = false; 93 100 94 101 poll_timer.start( 95 102 slint::TimerMode::Repeated, ··· 330 337 mgr.set_is_dark(dark); 331 338 } 332 339 } 340 + } 341 + 342 + // --- Monitor hotplug detection (every ~5s) --- 343 + monitor_check_tick += 1; 344 + if monitor_check_tick >= 50 { 345 + monitor_check_tick = 0; 346 + let mut current = overlay::monitors::enumerate(); 347 + current.sort_by_key(|m| (m.x, m.y)); 348 + if current != known_monitors { 349 + log::info!( 350 + "overlay: monitor config changed ({} → {} monitor(s)), queuing rebuild", 351 + known_monitors.len(), current.len() 352 + ); 353 + known_monitors = current; 354 + overlay_needs_rebuild = true; 355 + } 356 + } 357 + // Apply rebuild between breaks so the next break uses the 358 + // current monitor layout. During an active break we leave the 359 + // existing overlay in place and rebuild when the break ends. 360 + if overlay_needs_rebuild && break_active.is_none() { 361 + overlay = None; 362 + overlay_needs_rebuild = false; 333 363 } 334 364 }, 335 365 );
+1 -1
src/overlay/mod.rs
··· 59 59 }; 60 60 61 61 let backend = Box::new(multi_slint::MultiSlintBackend::new( 62 - is_dark, session, &all_monitors, cmd_tx, cfg_arc, 62 + is_dark, &all_monitors, cmd_tx, cfg_arc, 63 63 )?); 64 64 65 65 Ok(Self {
+25 -38
src/overlay/multi_slint.rs
··· 11 11 use crate::timer::{BreakMode, ScheduledBreak, TimerCommand}; 12 12 use crate::overlay::fmt_dur; 13 13 use crate::overlay::monitors::MonitorInfo; 14 - use crate::overlay::session::SessionType; 15 14 use crate::overlay::OverlayBackend; 16 15 17 16 struct WindowEntry { 18 17 window: OverlayWindow, 19 18 pos: slint::LogicalPosition, 19 + /// Only needed on non-Linux where we size windows explicitly. 20 + #[cfg(not(target_os = "linux"))] 20 21 size: slint::LogicalSize, 21 22 /// Index of this window in the list of monitors sorted by (x, y). Used as 22 23 /// a fallback when position-based monitor matching fails. ··· 25 26 26 27 pub struct MultiSlintBackend { 27 28 entries: Vec<WindowEntry>, 28 - session: SessionType, 29 29 } 30 30 31 31 impl MultiSlintBackend { 32 32 pub fn new( 33 33 is_dark: bool, 34 - session: SessionType, 35 34 monitors: &[MonitorInfo], 36 35 cmd_tx: tokio::sync::mpsc::UnboundedSender<TimerCommand>, 37 36 cfg_arc: Arc<Mutex<AppConfig>>, ··· 43 42 let mut sorted_monitors: Vec<(usize, &MonitorInfo)> = monitors.iter().enumerate().collect(); 44 43 sorted_monitors.sort_by_key(|(_, m)| (m.x, m.y)); 45 44 46 - if session == SessionType::Wayland { 47 - for (sort_rank, monitor) in &sorted_monitors { 48 - log::debug!( 49 - "overlay window[{}]: {}x{} at ({},{})", 50 - sort_rank, monitor.width, monitor.height, monitor.x, monitor.y 51 - ); 52 - let pos = slint::LogicalPosition { x: monitor.x as f32, y: monitor.y as f32 }; 53 - let size = slint::LogicalSize { 54 - width: monitor.width as f32, 55 - height: monitor.height as f32, 56 - }; 57 - let w = OverlayWindow::new()?; 58 - w.set_is_dark(is_dark); 59 - wire_callbacks(&w, cmd_tx.clone(), cfg_arc.clone()); 60 - w.hide().unwrap_or_default(); 61 - entries.push(WindowEntry { window: w, pos, size, monitor_index: *sort_rank }); 62 - } 63 - } else { 64 - for (sort_rank, monitor) in &sorted_monitors { 65 - log::debug!( 66 - "overlay window[{}]: {}x{} at ({},{})", 67 - sort_rank, monitor.width, monitor.height, monitor.x, monitor.y 68 - ); 69 - let pos = slint::LogicalPosition { x: monitor.x as f32, y: monitor.y as f32 }; 70 - let size = slint::LogicalSize { 71 - width: monitor.width as f32, 72 - height: monitor.height as f32, 73 - }; 74 - let w = OverlayWindow::new()?; 75 - w.set_is_dark(is_dark); 76 - wire_callbacks(&w, cmd_tx.clone(), cfg_arc.clone()); 77 - w.hide().unwrap_or_default(); 78 - entries.push(WindowEntry { window: w, pos, size, monitor_index: *sort_rank }); 79 - } 45 + for (sort_rank, monitor) in &sorted_monitors { 46 + log::debug!( 47 + "overlay window[{}]: {}x{} at ({},{})", 48 + sort_rank, monitor.width, monitor.height, monitor.x, monitor.y 49 + ); 50 + let pos = slint::LogicalPosition { x: monitor.x as f32, y: monitor.y as f32 }; 51 + #[cfg(not(target_os = "linux"))] 52 + let size = slint::LogicalSize { 53 + width: monitor.width as f32, 54 + height: monitor.height as f32, 55 + }; 56 + let w = OverlayWindow::new()?; 57 + w.set_is_dark(is_dark); 58 + wire_callbacks(&w, cmd_tx.clone(), cfg_arc.clone()); 59 + w.hide().unwrap_or_default(); 60 + entries.push(WindowEntry { 61 + window: w, 62 + pos, 63 + #[cfg(not(target_os = "linux"))] 64 + size, 65 + monitor_index: *sort_rank, 66 + }); 80 67 } 81 68 82 - Ok(Self { entries, session }) 69 + Ok(Self { entries }) 83 70 } 84 71 } 85 72