feat(linux): integrate real civil time, boot id, next_wakeup timer, and dbus notifications

This commit is contained in:
2026-10-02 17:00:31 +03:00
parent eee8c6b996
commit 2194c28ab5
6 changed files with 225 additions and 22 deletions
+23 -6
View File
@@ -73,25 +73,35 @@ impl LoopContext<'_> {
/// event in, one decision out — instead of racing a thread.
pub fn step(
events: &Receiver<FocusEvent>,
wait: Duration,
ctx: &LoopContext<'_>,
clock: &dyn MonotonicSource,
wall: CivilTime,
) -> (Action, usize) {
) -> (Action, usize, Duration) {
let mono = Monotonic(clock.now_ms());
let decision = blocker_core::evaluate(&ctx.input(wall, mono));
let wait = match decision.next_wakeup {
Some(wakeup) if wakeup.epoch_minutes > wall.epoch_minutes => {
let diff_mins = (wakeup.epoch_minutes - wall.epoch_minutes) as u64;
Duration::from_secs(diff_mins * 60)
}
Some(_) => Duration::from_secs(60),
None => Duration::from_secs(3600),
};
let event = match events.recv_timeout(wait) {
Ok(event) => event,
// A timeout is the `next_wakeup` firing. Re-evaluate, which is the whole
// point of having armed it.
Err(RecvTimeoutError::Timeout) => {
let _ = evaluate_now(ctx, clock, wall);
return (Action::Idle, 0);
return (Action::Idle, 0, wait);
}
// The watcher thread is gone. Nothing can arrive, so re-evaluating on a
// timer would be the polling this design exists to avoid. Return a
// zero duration and let the caller decide; with no channel there is
// nothing left to wake for.
Err(RecvTimeoutError::Disconnected) => {
return (Action::Idle, 0);
return (Action::Idle, 0, Duration::ZERO);
}
};
@@ -105,7 +115,7 @@ pub fn step(
FocusEvent::Cleared => 0,
};
(action, blocked)
(action, blocked, wait)
}
/// Evaluate, and enforce against whatever just took focus.
@@ -149,7 +159,14 @@ fn enforce_if_blocked(
};
match enforce::apply(ctx.enforcement, pid) {
Ok(()) => 1,
Ok(()) => {
let target_name = window.desktop_id.as_deref().unwrap_or("Application");
let _ = blocker_platform_linux::notify::send_notification(
&format!("Blocker: {target_name} blocked"),
"Focus blocked by active schedule",
);
1
}
Err(e) => {
// Logged, not fatal. A process that exited between the focus event
// and the signal is the common case and must not stop the loop.