diff --git a/desktop/daemon/src/main.rs b/desktop/daemon/src/main.rs index 35bcd73..dd6598c 100644 --- a/desktop/daemon/src/main.rs +++ b/desktop/daemon/src/main.rs @@ -123,7 +123,14 @@ fn main() -> ExitCode { if let Ok(backend) = blocker_platform_linux::kwin::KwinBackend::connect() { println!("backend: kwin — native Wayland windows are visible"); let _keep_connection = watcher_conn; - run_loop(&rx, &schedules, &usage, &context, blocker_platform_linux::Enforcement::Freeze); + run_loop( + &rx, + &schedules, + &usage, + &context, + blocker_platform_linux::Enforcement::Freeze, + &path, + ); backend.shutdown(); return ExitCode::SUCCESS; } @@ -188,25 +195,32 @@ fn main() -> ExitCode { /// charge, and this consumes it. Two blocked threads and no CPU between events. fn run_loop( rx: &std::sync::mpsc::Receiver, - schedules: &[blocker_core::model::Schedule], + schedules_initial: &[blocker_core::model::Schedule], usage: &blocker_core::engine::UsageSnapshot, context: &blocker_core::engine::PlatformContext, enforcement: blocker_platform_linux::Enforcement, + db_path: &std::path::Path, ) { - let ctx = runner::LoopContext { - schedules, - usage, - platform_context: context, - boot_id: boot::current_boot_id(), - enforcement, - }; + let mut current_schedules = schedules_initial.to_vec(); let clock = blocker_platform_linux::SystemMonotonic; println!("enforcement: {enforcement:?}; entering the event loop"); 'events: loop { + let ctx = runner::LoopContext { + schedules: ¤t_schedules, + usage, + platform_context: context, + boot_id: boot::current_boot_id(), + enforcement, + }; let (action, blocked, _) = runner::step(rx, &ctx, &clock, time::now()); if blocked > 0 { println!("blocked an app ({action:?})"); } + if let Ok(store) = blocker_storage::Store::open(db_path) { + if let Ok(reloaded) = store.load_schedules() { + current_schedules = reloaded; + } + } if action == runner::Action::Idle { break 'events; } diff --git a/desktop/daemon/src/runner.rs b/desktop/daemon/src/runner.rs index a1cd953..03919a9 100644 --- a/desktop/daemon/src/runner.rs +++ b/desktop/daemon/src/runner.rs @@ -108,11 +108,13 @@ pub fn step( let action = match &event { FocusEvent::Activated(_) => Action::Focused, FocusEvent::Cleared => Action::Unfocused, + FocusEvent::ReloadSchedules => Action::Idle, }; let blocked = match &event { FocusEvent::Activated(window) => enforce_if_blocked(window, ctx, clock, wall), FocusEvent::Cleared => 0, + FocusEvent::ReloadSchedules => 0, }; (action, blocked, wait) diff --git a/desktop/platform-linux/src/lib.rs b/desktop/platform-linux/src/lib.rs index 6af5a60..70336b4 100644 --- a/desktop/platform-linux/src/lib.rs +++ b/desktop/platform-linux/src/lib.rs @@ -79,6 +79,8 @@ pub enum FocusEvent { Activated(ForegroundWindow), /// The focused window closed, so there is no longer a foreground window. Cleared, + /// Reload active schedules from storage triggered by UI IPC. + ReloadSchedules, } /// Source of focus events. diff --git a/desktop/platform-linux/src/watcher.rs b/desktop/platform-linux/src/watcher.rs index 79efcd4..5332336 100644 --- a/desktop/platform-linux/src/watcher.rs +++ b/desktop/platform-linux/src/watcher.rs @@ -74,6 +74,16 @@ impl Watcher { // thread exits when the connection drops. let _ = self.sink.send(event); } + + /// Instruct the daemon to reload schedules from the database immediately. + fn reload_schedules(&self) { + let _ = self.sink.send(FocusEvent::ReloadSchedules); + } + + /// Ping method to check if the daemon is alive. + fn ping(&self) -> bool { + true + } } /// Acquire the bus name and export [`Watcher`], returning the live connection. @@ -115,3 +125,25 @@ pub fn serve(sink: Sender) -> Result { pub fn release(connection: &Connection) { let _ = connection.release_name(BUS_NAME); } + +/// Tell the running daemon via D-Bus to reload schedules from storage. +pub fn notify_daemon_reload() -> Result<(), zbus::Error> { + let conn = Connection::session()?; + let proxy = + zbus::blocking::Proxy::new(&conn, BUS_NAME, OBJECT_PATH, "vu.valiq.blocker.Watcher")?; + let _: () = proxy.call("ReloadSchedules", &())?; + Ok(()) +} + +/// Query if the daemon is running and responding on D-Bus. +pub fn is_daemon_running() -> bool { + if let Ok(conn) = Connection::session() { + if let Ok(proxy) = + zbus::blocking::Proxy::new(&conn, BUS_NAME, OBJECT_PATH, "vu.valiq.blocker.Watcher") + { + let res: zbus::Result = proxy.call("Ping", &()); + return res.unwrap_or(false); + } + } + false +} diff --git a/desktop/ui/src/main.rs b/desktop/ui/src/main.rs index bfe2937..e0f57d0 100644 --- a/desktop/ui/src/main.rs +++ b/desktop/ui/src/main.rs @@ -88,6 +88,7 @@ impl BlockerApp { } let mut store = blocker_storage::Store::open(&path).map_err(|e| e.to_string())?; store.save_schedules(&self.schedules).map_err(|e| e.to_string())?; + let _ = blocker_platform_linux::watcher::notify_daemon_reload(); Ok(()) } } @@ -174,7 +175,18 @@ impl BlockerApp { Tab::Status => { ui.heading("Current Status"); ui.add_space(12.0); - ui.label(format!("Daemon Status: Connected ({})", self.status_message)); + let daemon_live = blocker_platform_linux::watcher::is_daemon_running(); + if daemon_live { + ui.colored_label( + egui::Color32::from_rgb(0, 200, 100), + "Daemon Status: Running (D-Bus connected)", + ); + } else { + ui.colored_label( + egui::Color32::from_rgb(220, 80, 80), + "Daemon Status: Inactive (not detected on D-Bus)", + ); + } ui.label(format!("Active Schedules: {}", self.schedules.len())); ui.add_space(16.0); if ui.button("Test Block Notification").clicked() {