feat(windows): indicate device battery in the tray - #964
Conversation
Greptile SummaryThe PR adds Windows tray battery rows, hover text, selectable battery glyphs, and one-shot low-battery notifications, backed by live configuration settings and a shared battery-attention predicate.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the prior alert rearming, shell-update retry, and unauthenticated pointer-decoding issues are addressed in the current code.
|
| Filename | Overview |
|---|---|
| crates/openlogi-agent/src/tray_windows.rs | Adds Windows tray battery surfaces and replaces externally forgeable pointer payloads with synchronized process-local mailboxes while retrying rejected shell updates. |
| crates/openlogi-agent/src/main.rs | Integrates tray battery reconciliation and continuously evaluates alert state even while notifications are disabled. |
| crates/openlogi-agent-core/src/battery_alert.rs | Implements per-device low and critical battery transition tracking with recovery, charging, disappearance, and unknown-reading handling. |
| crates/openlogi-agent/src/tray_battery.rs | Derives menu rows and bounded tooltip text while supporting cache invalidation after failed shell handoffs. |
| crates/openlogi-agent/src/tray_glyph.rs | Selects the desired brand or battery glyph and makes failed handoffs retryable through cache invalidation. |
| crates/openlogi-core/src/config/settings.rs | Adds persisted application settings for battery alerts and tray icon style with backward-compatible defaults. |
Sequence Diagram
sequenceDiagram
participant Inventory as Inventory watcher
participant Agent as Agent core
participant State as Battery/tray state
participant Tray as Windows tray thread
participant Shell as Windows Shell
Inventory->>Agent: Device inventory snapshot
Agent->>State: Derive rows, tooltip, glyph, and alerts
State-->>Agent: Changed tray state and new alerts
Agent->>Tray: Post payload-free wakeup
Tray->>State: Read process-local mailbox
Tray->>Shell: NIM_MODIFY icon, tooltip, or balloon
alt Shell update fails
Tray->>State: Invalidate deduplication cache
Inventory->>Agent: Next reconciliation snapshot
end
Reviews (10): Last reviewed commit: "fix(windows): stop trusting the tray win..." | Re-trigger Greptile
0c36f29 to
36ede56
Compare
d658198 to
7829e36
Compare
|
Rebased onto master (`8c80c6e`) after #960/#961 landed — one README conflict where the button-remapping bullet was reworded next to the new battery bullet; kept master's wording. Also dropped Worth flagging for review: this PR still introduces |
|
Want your agent to iterate on Greptile's feedback? Start a greploop in Codex and it will work through the open comments and keep going until this PR reviews clean. |
7829e36 to
8fe71d1
Compare
|
Dropped Before deleting it I checked what the spec held that the code did not. Two of its three recorded risks were already documented better in the modules themselves —
That is the kind of thing worth having in the file someone opens when a user reports that alerts never fire. Also rebased onto Gate green — fmt, |
b123284 to
1cf4390
Compare
The tray icon, menu, and tooltip now report per-device battery, and a low-battery balloon fires once per drained device rather than on every inventory tick. - openlogi-core gains device display names, the low-battery state machine, and the `battery_alerts` / `tray_icon_style` settings behind the existing app-settings schema. - The agent publishes a tray battery row snapshot and picks the glyph battery, filtering states that did not change so the shell is not asked to redraw an identical icon. - The Windows tray draws the battery glyph over the brand mark from vendored lucide icons (0BSD, checked in under assets/lucide), restoring the plain mark when the glyph is switched off. - The GUI exposes both settings under General, in all 22 locales. - Alerts ignore a level OpenLogi derived itself, so a computed estimate cannot raise a balloon the device never reported. Squashed from 18 commits to port across the openlogi-gui -> openlogi-desktop split; the pre-squash history is on backup/tray-battery-pre-squash.
The tray glyph, the GUI device card, and the low-battery alert each carried their own rule for "this battery is low". They agreed when the tray was written, and stopped agreeing as soon as the card was redesigned to decide by percentage while the tray still read the firmware's level bucket: an MX Master reporting `Good` at 15% drew an attention tone on the card and a healthy glyph in the tray, for the same device, at the same moment. Move the rule to `BatteryInfo::needs_attention` in openlogi-core, which all three now call. The alert's `LOW_PERCENTAGE` becomes an alias of the same constant — an alert for a device neither surface is flagging would read as a bug. Above the threshold the tray keeps its finer-grained glyphs, which the card has no equivalent for; a firmware `Low` up there is the 0x1000 bucket being eager (it calls everything from 20% to 49% low) and now draws as the comfortable charge it is.
`update_tray_battery` skipped `BatteryAlerts::evaluate` entirely when the setting was off, which froze the fold rather than silencing it. Turning alerts back on then replayed a world that no longer existed: a device that drained past the threshold while muted fired immediately, and one that had already alerted and since recovered stayed marked as alerted, so its next real crossing was swallowed. Fold on every tick and gate only the notification — the same reasoning the glyph beside it already documents for publishing unconditionally. Also drops a line from `evaluate`'s docs claiming it never fires above `LOW_PERCENTAGE`. `alert_level` honours a firmware `Critical` at any reading, which `a_firmware_critical_marker_fires_above_the_low_threshold` has always asserted; the module header explains why. Only the summary was wrong.
1cf4390 to
b6a8085
Compare
|
Self-review pass on the rebased branch turned up two defects; both fixed in The alert bookkeeping froze while alerts were muted.
Rebased onto master ( |
`PostMessageW` targets a window any same-integrity process on the desktop can find, and UIPI filters nothing between equals, so a `WPARAM` arriving at `wnd_proc` is attacker-supplied data. The balloon and tooltip arms took theirs straight to `Box::from_raw`: a forged `WM_APP+2` was a free of an arbitrary integer, and the safety comment's argument — "we posted it exactly once" — was a description of our own callers rather than anything the code enforced. The payloads move to a process-local mailbox and the messages become bare wakeups, so a forged post now drains an empty queue and does nothing. The glyph message already carried a scalar, validated through `BatteryGlyph::from_index`; only its doc needed correcting. Balloons queue and the tooltip replaces, which the new test pins along with the mailbox being where payloads live. Also invalidates the dedup caches when `Shell_NotifyIconW(NIM_MODIFY)` rejects an update. The post-failure and no-window paths already did this; the shell-rejection path did not, so an icon or hover text the shell refused stayed refused — every later identical snapshot was filtered as a repeat, leaving the stale value up until the next real change or an Explorer restart.
|
Worked through Greptile's open findings; all three are addressed in The two unresolved P1s were both right and both the same shape: the tray's dedup caches were invalidated when there was no window and when The security finding was also right, and the fix goes further than its thread suggested. Rather than validate the pointer (which cannot be done), the payloads move to a process-local mailbox and the messages become bare wakeups. A forged post now drains an empty queue and does nothing, and there is no The glyph message already carried a plain scalar validated through Gate green on the final tree: fmt, workspace clippy, workspace tests, rustdoc, and Linux/macOS cross-lints. Merges clean against |
Summary
The Windows tray now reports per-device battery three ways: rows in the right-click menu, an optional battery glyph in place of the brand mark, and hover text that mirrors the menu wording. A low-battery balloon fires once per drained device rather than on every inventory tick.
Two new
[app_settings]keys drive it, both read live (no agent restart):battery_alerts(defaulttrue) andtray_icon_style(brand|battery, defaultbrand). Both are exposed as switches under Settings → General, Windows-gated for this milestone.This is the Windows milestone; the macOS menu-bar item gets the same surfaces next, which is why the shared modules (
tray_battery,tray_glyph,battery_alert) are platform-neutral with only the Windows callers wired up. The rationale for each design choice lives in those modules' own docs.Fixes #959
Related, and deliberately not closed by this PR: #669 asks for a macOS desktop widget showing battery — a different surface, out of scope here.
Changes
openlogi-core— device display names; thebattery_alerts/tray_icon_stylesettings onAppSettings, withTrayIconStyleexported fromconfig;BatteryInfo::needs_attention, the one rule for "this battery is low".openlogi-agent-core— newbattery_alertmodule: the low-battery state machine. Fires on→ LowandLow → Critical, never twice for the same severity, never on a level the firmware reports asUnknown, never for a device on power. Charging, recovering, or vanishing re-arms a device.Orchestratorgains livebattery_alerts()/tray_icon_style()reads.openlogi-agent—tray_battery(menu-row snapshot + tooltip, a pull surface read when the menu opens, so no idle wakeups),tray_glyph(icon selection, warning on exactly the readings the GUI device card warns on), andnotify(one-shot alerts, raised by the agent because the GUI is normally closed).tray_windowsgrows balloon / tooltip / glyph message arms; the glyph and tooltip are both replayed when Explorer restarts and drops the icon. Battery glyphs render from Lucide SVGs vendored underassets/lucide(0BSD).openlogi-desktop— two switches under General, Windows-gated.openlogi-ui— four new strings across all 22 locales.docs/CONFIGURATION.mdanddocs/config.example.toml; a README feature bullet.Rebased past #983, which redesigned the GUI battery indicator. That redesign moved the card's low-battery decision to a percentage while the tray still read the firmware's level bucket, so the two could disagree about the same device — a
Goodbucket at 15% drew an attention tone on the card and a healthy glyph in the tray. The second commit lifts the rule intoopenlogi-coreand points the card, the tray, and the alert threshold at it, which is what the drift test now checks. The tray keeps its finer-grained glyphs above the warning line, where the card has nothing to disagree with.One dependency edge is added:
resvg, pinned to the versiongpuialready resolves to, soCargo.lockgrows exactly one line and no gpui pin moves.run()in the agent crossed the 100-line budget with the new bookkeeping, so its autostart-and-permissions preamble is extracted asreconcile_startup().Testing
Run on the final tree (Windows 11,
RUSTFLAGS="-D warnings"):36 unit tests cover the alert state machine, row/tooltip derivation, and glyph selection, including two that hold the tray's low state to the same predicate the device card uses, so the two cannot drift apart silently.
Two notes, both pre-existing on master and verified against a clean
origin/masterworktree, not introduced here:xtask ci_yml_runs_what_this_runner_runsfails on a Windows checkout — it string-matches against.github/workflows/ci.yml, which has CRLF endings locally (no.gitattributesforces LF). Linux CI sees LF and passes. fix(xtask,permissions): make the local gate runnable on Windows #970 fixes it.--exclude openlogi-permissionson Windows:lib.rslinksPermissionStatus, which iscfg-gated to macOS/Linux. CI's rustdoc job is Linux-only. fix(xtask,permissions): make the local gate runnable on Windows #970 fixes this one too.cargo deny checkis red on master today (advisories/licenses/sources) — findings are byte-identical with and without this branch. fix(deps): bump crates for disclosed RUSTSEC advisories #956 is the open fix.Not runtime-tested on hardware. The tray glyph, balloon, and tooltip need a Windows desktop with a battery-reporting device. To verify: enable both switches in Settings → General, then check the right-click menu lists each online device, hover text matches those rows, the icon shows the lowest-charge device's glyph, and a balloon fires once as a device crosses 20% and again at 10%. Restarting Explorer should leave both the glyph and the tooltip intact.