feat(ipc): connect GUI to daemon via D-Bus reload and live status detection

This commit is contained in:
2026-10-02 18:00:29 +03:00
parent 5ba2cea3d8
commit 9e44bd4b32
5 changed files with 72 additions and 10 deletions
+20 -6
View File
@@ -123,7 +123,14 @@ fn main() -> ExitCode {
if let Ok(backend) = blocker_platform_linux::kwin::KwinBackend::connect() { if let Ok(backend) = blocker_platform_linux::kwin::KwinBackend::connect() {
println!("backend: kwin — native Wayland windows are visible"); println!("backend: kwin — native Wayland windows are visible");
let _keep_connection = watcher_conn; 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(); backend.shutdown();
return ExitCode::SUCCESS; return ExitCode::SUCCESS;
} }
@@ -188,25 +195,32 @@ fn main() -> ExitCode {
/// charge, and this consumes it. Two blocked threads and no CPU between events. /// charge, and this consumes it. Two blocked threads and no CPU between events.
fn run_loop( fn run_loop(
rx: &std::sync::mpsc::Receiver<blocker_platform_linux::FocusEvent>, rx: &std::sync::mpsc::Receiver<blocker_platform_linux::FocusEvent>,
schedules: &[blocker_core::model::Schedule], schedules_initial: &[blocker_core::model::Schedule],
usage: &blocker_core::engine::UsageSnapshot, usage: &blocker_core::engine::UsageSnapshot,
context: &blocker_core::engine::PlatformContext, context: &blocker_core::engine::PlatformContext,
enforcement: blocker_platform_linux::Enforcement, enforcement: blocker_platform_linux::Enforcement,
db_path: &std::path::Path,
) { ) {
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 { let ctx = runner::LoopContext {
schedules, schedules: &current_schedules,
usage, usage,
platform_context: context, platform_context: context,
boot_id: boot::current_boot_id(), boot_id: boot::current_boot_id(),
enforcement, enforcement,
}; };
let clock = blocker_platform_linux::SystemMonotonic;
println!("enforcement: {enforcement:?}; entering the event loop");
'events: loop {
let (action, blocked, _) = runner::step(rx, &ctx, &clock, time::now()); let (action, blocked, _) = runner::step(rx, &ctx, &clock, time::now());
if blocked > 0 { if blocked > 0 {
println!("blocked an app ({action:?})"); 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 { if action == runner::Action::Idle {
break 'events; break 'events;
} }
+2
View File
@@ -108,11 +108,13 @@ pub fn step(
let action = match &event { let action = match &event {
FocusEvent::Activated(_) => Action::Focused, FocusEvent::Activated(_) => Action::Focused,
FocusEvent::Cleared => Action::Unfocused, FocusEvent::Cleared => Action::Unfocused,
FocusEvent::ReloadSchedules => Action::Idle,
}; };
let blocked = match &event { let blocked = match &event {
FocusEvent::Activated(window) => enforce_if_blocked(window, ctx, clock, wall), FocusEvent::Activated(window) => enforce_if_blocked(window, ctx, clock, wall),
FocusEvent::Cleared => 0, FocusEvent::Cleared => 0,
FocusEvent::ReloadSchedules => 0,
}; };
(action, blocked, wait) (action, blocked, wait)
+2
View File
@@ -79,6 +79,8 @@ pub enum FocusEvent {
Activated(ForegroundWindow), Activated(ForegroundWindow),
/// The focused window closed, so there is no longer a foreground window. /// The focused window closed, so there is no longer a foreground window.
Cleared, Cleared,
/// Reload active schedules from storage triggered by UI IPC.
ReloadSchedules,
} }
/// Source of focus events. /// Source of focus events.
+32
View File
@@ -74,6 +74,16 @@ impl Watcher {
// thread exits when the connection drops. // thread exits when the connection drops.
let _ = self.sink.send(event); 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. /// Acquire the bus name and export [`Watcher`], returning the live connection.
@@ -115,3 +125,25 @@ pub fn serve(sink: Sender<FocusEvent>) -> Result<Connection, WatcherError> {
pub fn release(connection: &Connection) { pub fn release(connection: &Connection) {
let _ = connection.release_name(BUS_NAME); 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<bool> = proxy.call("Ping", &());
return res.unwrap_or(false);
}
}
false
}
+13 -1
View File
@@ -88,6 +88,7 @@ impl BlockerApp {
} }
let mut store = blocker_storage::Store::open(&path).map_err(|e| e.to_string())?; 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())?; store.save_schedules(&self.schedules).map_err(|e| e.to_string())?;
let _ = blocker_platform_linux::watcher::notify_daemon_reload();
Ok(()) Ok(())
} }
} }
@@ -174,7 +175,18 @@ impl BlockerApp {
Tab::Status => { Tab::Status => {
ui.heading("Current Status"); ui.heading("Current Status");
ui.add_space(12.0); 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.label(format!("Active Schedules: {}", self.schedules.len()));
ui.add_space(16.0); ui.add_space(16.0);
if ui.button("Test Block Notification").clicked() { if ui.button("Test Block Notification").clicked() {