feat(linux): integrate real civil time, boot id, next_wakeup timer, and dbus notifications
This commit is contained in:
@@ -38,6 +38,7 @@ mod runner;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use blocker_platform_linux::kwin::BUS_NAME;
|
||||
use blocker_platform_linux::{boot, time};
|
||||
use std::process::ExitCode;
|
||||
|
||||
/// `~/.local/share/blocker/blocker.db`, honouring `XDG_DATA_HOME`.
|
||||
@@ -152,7 +153,7 @@ fn main() -> ExitCode {
|
||||
schedules: &schedules,
|
||||
usage: &usage,
|
||||
platform_context: &context,
|
||||
boot_id: blocker_core::engine::BootId(0),
|
||||
boot_id: boot::current_boot_id(),
|
||||
enforcement: blocker_platform_linux::Enforcement::Freeze,
|
||||
};
|
||||
let clock = blocker_platform_linux::SystemMonotonic;
|
||||
@@ -161,8 +162,7 @@ fn main() -> ExitCode {
|
||||
'events: loop {
|
||||
// The wait IS the next wakeup. An idle system parks here rather
|
||||
// than ticking, which is how the section 5 idle budget is met.
|
||||
let (action, blocked) =
|
||||
runner::step(&rx, std::time::Duration::from_secs(3600), &ctx, &clock, now());
|
||||
let (action, blocked, _) = runner::step(&rx, &ctx, &clock, time::now());
|
||||
if blocked > 0 {
|
||||
println!("blocked an app ({action:?})");
|
||||
}
|
||||
@@ -199,14 +199,13 @@ fn run_loop(
|
||||
schedules,
|
||||
usage,
|
||||
platform_context: context,
|
||||
boot_id: blocker_core::engine::BootId(0),
|
||||
boot_id: boot::current_boot_id(),
|
||||
enforcement,
|
||||
};
|
||||
let clock = blocker_platform_linux::SystemMonotonic;
|
||||
println!("enforcement: {enforcement:?}; entering the event loop");
|
||||
'events: loop {
|
||||
let (action, blocked) =
|
||||
runner::step(rx, std::time::Duration::from_secs(3600), &ctx, &clock, now());
|
||||
let (action, blocked, _) = runner::step(rx, &ctx, &clock, time::now());
|
||||
if blocked > 0 {
|
||||
println!("blocked an app ({action:?})");
|
||||
}
|
||||
@@ -215,13 +214,3 @@ fn run_loop(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Civil time, from the platform layer.
|
||||
///
|
||||
/// A placeholder reading rather than a clock read: the loop's correctness
|
||||
/// depends on `next_wakeup` arithmetic, and wiring a real timezone-aware source
|
||||
/// is part of the next PR. Deliberately returns a fixed instant so the loop is
|
||||
/// obviously not yet time-driven, instead of appearing to work by accident.
|
||||
fn now() -> blocker_core::engine::CivilTime {
|
||||
blocker_core::engine::CivilTime { epoch_minutes: 0, utc_offset_minutes: 0 }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user