Result of evaluating the per-tick suppression guards: either no
guard fires, or exactly one wins and dictates the tray icon
(reason) plus whether the event-log records a GuardSuppress
entry (log_as).
Per-tick gate that throttles UserIdle::get_time() once it starts
failing. While the probe succeeds we attempt it every tick; once it
fails we skip an exponentially-growing number of ticks before retrying,
so a windowing system that rejects the call (X11 without
MIT-SCREEN-SAVER, a denied Wayland portal) doesnβt get hammered β and
re-spammed β once per second.
The wall-clock decomposition the guards reason about: minute-of-day
and weekday (days-since-Monday, 0=Mon β¦ 6=Sun), both sampled from
the single Local::now() taken at the top of the tick. Grouped so the
two related time values travel together and evaluate_guards stays
under the positional-argument limit.
What (if anything) to log about a tickβs lock-state transition.
None is the common case: no confidently-known transition this
tick. The wrapper turns the other variants into log::info! calls.
Ceiling for the idle-probe back-off. Once the probe has failed enough
times in a row, we settle at one attempt every five minutes β frequent
enough to recover if the windowing-system extension reappears, rare
enough that a permanently-missing one (e.g. X11 with no MIT-SCREEN-SAVER)
no longer floods stderr with libX11 warnings.
Inter-tick wall-clock gap above which we treat the tick as the first
one after a wake from suspend. Well clear of the 1s cadence and any
scheduler jitter, but far below the smallest useful bedtime interval.
Rate-limit window for repeated UserIdle::get_time failure warnings.
One log line per 60 s is enough to surface a persistent platform-API
breakage without spamming the log file once per tick.
Encoded previous lock state for the transition logger:
0 = unknown / havenβt seen yet (the initial value)
1 = last seen as Some(false) (confidently unlocked)
2 = last seen as Some(true) (confidently locked)
Option<bool> directly is what we want logically, but we need an
atomic so the run-loop closure can mutate the previous-state
across ticks without locking. AtomicU8 is the smallest fit.
Epoch seconds (SystemTime::UNIX_EPOCH) at which the last UserIdle
failure was logged; 0 means βnever warned yetβ (also the at-rest
value before the scheduler boots).
Build and surface a scheduled micro/long break: resolve the per-kind
content from s, deliver it through the configured channel, fire the
BreakStart hook, and log the event. Returns the resolved delivery so
the caller can decide whether to mark an active_break.
Surface a Sleep (bedtime) break: fire the overlay, run the start hook, and
log the event. Timer bookkeeping stays with the caller in run_loop.
Sleep breaks are always overlay; they never go through the delivery-routing
logic used by deliver_scheduled_break.
Pure decision: given the per-tick guard inputs, return which
SuppressReason should fire (if any) and whether the run-loop
should also write a GuardSuppress event for it.
Fire a scheduled micro/long break end to end: deliver it (see
deliver_scheduled_break) and apply the post-fire timer bookkeeping
under the timers lock (see record_scheduled_fire). fixed_key is
Some((today, minute)) for a fixed-time fire (recording the dedupe
key) or None for an interval fire; the fire Instant is stamped here.
Idle seconds the typing-defer check should see. Some only when we
can affirmatively judge activity β a real HID reading this tick, or a
locked session (the user is definitely away). None when idle
detection is unavailable and the lock state is unknown/unlocked, so
the caller skips deferral rather than stalling every break for the
full cap (#67). promoted_idle_secs already folds in the lock
promotion, so the Some branch carries the value the rest of the
scheduler sees. Pure so the unavailable-on-Wayland case is testable.
Seconds to wait before the next idle probe after consecutive_failures
failures in a row. Doubles from 2 s and saturates at
IDLE_PROBE_BACKOFF_MAX_SECS; the first failure alone already stops the
per-tick hammering. consecutive_failures is always >= 1 at the call
site (itβs incremented before this runs), but 0 is handled defensively
and yields the same first-step delay.
Pure decision: given the raw HID-idle seconds and an Option<bool>
lock signal, return the idle seconds the rest of the scheduler
should see. When the OS confidently reports the session as locked,
promote the value past both the micro- and long-break reset
thresholds so every downstream check (screen-time, suppression,
typing-defer) treats the user as idle. None (couldnβt determine
lock state) leaves raw_idle_secs untouched β trust HID alone.
Cheap atomic-load check that the run loop reads at the top of
every tick. Pulled out of run_loop so the early-out condition
is unit-testable without driving the full 1Hz loop body, which
is bound to the production AppHandle<Wry> runtime and a real
Scheduler with its camera/video/logger side threads.
Convert SystemTime::now() to seconds since the Unix epoch. Returns
0 if the system clock is somehow before 1970 β same fallback as
the βnever warnedβ sentinel, which simply means the next warn fires.
Wrapper around idle_secs_with_lock that also emits a single info
line on each lockedβunlocked transition, so the log shows why
idle-based suppression suddenly engaged or disengaged. The
previous-state tracker is tri-valued (unknown / unlocked / locked)
so a flaky probe that returns Some(true) β None β Some(true)
doesnβt generate spurious βunlocked / lockedβ log pairs.
Cell-parameterised variant of promote_idle_for_lock so the
orchestration (load β decide β log β store β promote) is testable
with a local atomic β mirrors user_idle_warn_throttle above.
Resolve this tickβs raw idle seconds, driving the probe back-off.
probe performs the platform UserIdle call, yielding the idle
seconds on success or a display-error string on failure. The back-off
state and the throttled warning are handled here; the only thing the
caller keeps platform-bound is probe itself, so this decision logic
is unit-testable without a windowing system.
Whether the gap between the previous tickβs wall clock and now
indicates a wake from suspend (or a forward clock leap). Pulled out
of the loop body so the threshold logic is unit-testable without
driving the 1Hz loop. A backwards clock step yields Err from
duration_since and is treated as βnot resumedβ.
Resolve the per-kind BreakEvent content for a scheduled micro/long
break. Pure (no I/O) so the field resolution β which fields each kind
draws, the strict-mode postpone lock, the break-health intensity gate
β is unit-testable without a windowing runtime. intensity is the
live value from the stats lock; the helper applies the
break_health_enabled gate. Sleep never reaches here (bedtime path).
Pure decision: which break kinds were due (enabled and past their
interval) at this tick and so are being suppressed by a guard. Split
out from log_suppressions so the per-kind logic is unit-testable
without a Logger.
Surface a UserIdle::get_time failure to the log, at most once per
USER_IDLE_WARN_INTERVAL_SECS. Without this gate the production
code silently fell back to β0 = activeβ forever, so a broken
platform call (X11 down, macOS API change, Wayland portal denied)
would invisibly break idle suppression and screen-time tracking.
backoff_secs is how long the probe is now suppressed for, so the
log explains why the per-second errors stop.