fix(native): Wave-2 audit fixes (D1, D3, D5)

- D1 (HIGH): Linux no-sleep was completely non-functional — the zbus
  ScreenSaver inhibit was bound to a function-local D-Bus connection dropped on
  return, so the screensaver service auto-released it instantly. Keep a
  long-lived Connection in managed state (InhibitState { conn, cookie }) so the
  same connection holds Inhibit and issues UnInhibit; created once, reused.
- D3: tray "Do Not Disturb" desynced from the web manualDndAtom after any reload
  (custom-chrome toggle / logout) — the atom is in-memory and reset while the
  tray stayed checked. Added TrayDndState + a get_tray_dnd command so the web
  hook re-hydrates the atom on mount.
- D5: install_update now calls app.restart() after a successful install so the
  new version actually runs (Linux AppImage kept running the old binary; the UI
  hung on "installing").

CI-compile-verified (windows + linux). Web-side wiring (get_tray_dnd query,
updater terminal state) landed on cinny:lotus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 22:32:37 -04:00
co-authored by Claude Opus 4.8
parent de368b2056
commit 3a48771588
2 changed files with 116 additions and 39 deletions
+29
View File
@@ -138,6 +138,13 @@ async fn install_update(app: tauri::AppHandle) -> Result<(), String> {
.download_and_install(|_chunk, _total| {}, || {})
.await
.map_err(|e| e.to_string())?;
// Only reached on a successful download+install (the `?` above bails
// otherwise). Relaunch so the freshly installed version actually
// runs — without this the UI hangs on "installing", and on a Linux
// AppImage the running process is still the old binary. `restart()`
// exits the current process and never returns, so nothing after it
// runs for the update case.
app.restart();
}
}
Ok(())
@@ -368,6 +375,23 @@ struct TrayUnreadState {
height: u32,
}
/// Holds a clone of the tray "Do Not Disturb" `CheckMenuItem` so `get_tray_dnd`
/// can read its live checkstate. The tray only emits `lotus-dnd-changed` on
/// click, but the web `manualDndAtom` is in-memory and resets on every reload,
/// so the web hook re-hydrates from this on mount. `CheckMenuItem` is a cheap
/// clonable handle to the same underlying menu item.
struct TrayDndState(CheckMenuItem<tauri::Wry>);
/// Return the tray DND toggle's current checkstate so the web side can
/// re-hydrate `manualDndAtom` after a reload. Returns `false` when the tray
/// wasn't created (e.g. missing bundled icon) rather than erroring the call.
#[tauri::command]
fn get_tray_dnd(app: tauri::AppHandle) -> bool {
app.try_state::<TrayDndState>()
.map(|s| s.0.is_checked().unwrap_or(false))
.unwrap_or(false)
}
/// Paint a small white-ringed red "unread" dot into the bottom-right corner of
/// an RGBA buffer, in place. Cross-platform (operates on raw pixels).
fn draw_unread_dot(rgba: &mut [u8], width: u32, height: u32) {
@@ -448,6 +472,7 @@ pub fn run() {
.invoke_handler(tauri::generate_handler![
set_badge_count,
set_tray_unread,
get_tray_dnd,
flash_window,
send_notification,
check_for_update,
@@ -563,6 +588,10 @@ pub fn run() {
width,
height,
});
// Keep a handle to the DND CheckMenuItem so `get_tray_dnd` can
// report its live checkstate for web re-hydration after reload.
app.manage(TrayDndState(dnd_item));
} else {
eprintln!("tray: no bundled window icon; skipping system tray setup");
}