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: run idle poller on plain thread, init gtk

+45 -27
+1
Cargo.lock
··· 2944 2944 "auto-launch", 2945 2945 "dirs 5.0.1", 2946 2946 "env_logger", 2947 + "gtk", 2947 2948 "image", 2948 2949 "log", 2949 2950 "muda 0.15.3",
+1
Cargo.toml
··· 44 44 45 45 [target.'cfg(target_os = "linux")'.dependencies] 46 46 zbus = { version = "4", default-features = false, features = ["tokio"] } 47 + gtk = "0.18" 47 48 48 49 [target.'cfg(target_os = "windows")'.dependencies] 49 50 windows = { version = "0.58", features = [
+28 -27
src/app.rs
··· 42 42 Profile::from_config(id, prof_cfg) 43 43 } 44 44 45 - /// Spawns the idle detection polling loop. 45 + /// Spawns the idle detection polling loop on a plain OS thread (not tokio) 46 + /// so blocking DBus/Win32 calls work without nesting runtimes. 46 47 pub fn spawn_idle_poller( 47 48 cmd_tx: mpsc::UnboundedSender<TimerCommand>, 48 49 threshold: Duration, ··· 51 52 if !enabled { 52 53 return; 53 54 } 54 - tokio::spawn(async move { 55 - let mut detector = idle::create(); 56 - let mut was_idle = false; 57 - let mut idle_start: Option<std::time::Instant> = None; 55 + std::thread::Builder::new() 56 + .name("ioma-idle".into()) 57 + .spawn(move || { 58 + let mut detector = idle::create(); 59 + let mut was_idle = false; 60 + let mut idle_start: Option<std::time::Instant> = None; 58 61 59 - loop { 60 - tokio::time::sleep(Duration::from_secs(5)).await; 62 + loop { 63 + std::thread::sleep(Duration::from_secs(5)); 61 64 62 - match detector.idle_duration() { 63 - Ok(Some(idle_dur)) if idle_dur >= threshold => { 64 - if !was_idle { 65 - idle_start = Some(std::time::Instant::now()); 66 - was_idle = true; 67 - let _ = cmd_tx.send(TimerCommand::IdleDetected { duration: idle_dur }); 65 + match detector.idle_duration() { 66 + Ok(Some(idle_dur)) if idle_dur >= threshold => { 67 + if !was_idle { 68 + idle_start = Some(std::time::Instant::now()); 69 + was_idle = true; 70 + let _ = cmd_tx.send(TimerCommand::IdleDetected { duration: idle_dur }); 71 + } 68 72 } 69 - } 70 - Ok(_) => { 71 - if was_idle { 72 - // User returned — tell the timer how long they were gone. 73 - let gone = idle_start 74 - .map(|t| t.elapsed()) 75 - .unwrap_or(threshold); 76 - was_idle = false; 77 - idle_start = None; 78 - let _ = cmd_tx.send(TimerCommand::Resume); 79 - let _ = gone; // used in future for smarter cycle resets 73 + Ok(_) => { 74 + if was_idle { 75 + let gone = idle_start.map(|t| t.elapsed()).unwrap_or(threshold); 76 + was_idle = false; 77 + idle_start = None; 78 + let _ = cmd_tx.send(TimerCommand::Resume); 79 + let _ = gone; 80 + } 80 81 } 82 + Err(e) => log::debug!("Idle detection error: {e}"), 81 83 } 82 - Err(e) => log::debug!("Idle detection error: {e}"), 83 84 } 84 - } 85 - }); 85 + }) 86 + .expect("failed to spawn idle poller thread"); 86 87 }
+15
src/main.rs
··· 14 14 15 15 use slint::ComponentHandle; 16 16 17 + #[cfg(target_os = "linux")] 18 + fn init_gtk() { 19 + gtk::init().expect("GTK init failed"); 20 + } 21 + 17 22 use app::{active_profile, spawn_idle_poller, AppState}; 18 23 use config::AppConfig; 19 24 use overlay::OverlayManager; ··· 23 28 24 29 fn main() -> anyhow::Result<()> { 25 30 env_logger::init(); 31 + 32 + // GTK must be initialized on the main thread before any GTK/tray operations. 33 + #[cfg(target_os = "linux")] 34 + init_gtk(); 26 35 27 36 let cfg = config::load().unwrap_or_else(|e| { 28 37 log::warn!("Could not load config ({e}), using defaults"); ··· 116 125 slint::TimerMode::Repeated, 117 126 Duration::from_millis(100), 118 127 move || { 128 + // Pump GTK events so the tray icon/menu stays responsive on Linux. 129 + #[cfg(target_os = "linux")] 130 + while gtk::events_pending() { 131 + gtk::main_iteration_do(false); 132 + } 133 + 119 134 // --- Tray events --- 120 135 let mut open_settings = false; 121 136 if tray.process_events(&cmd_tx, &mut open_settings) {