22f8e1566c
Rounds out the native app on Linux (Windows features kept; macOS stays no-op): - power.rs: no-sleep during calls on Linux via a zbus org.freedesktop.ScreenSaver Inhibit/UnInhibit (cookie held in ScreenSaverInhibit managed state). - set_badge_count: Linux launcher badge via the Unity com.canonical.Unity.LauncherEntry.Update D-Bus signal (best-effort; app_uri = cinny.desktop per Tauri's mainBinaryName naming). - tauri-plugin-autostart registered (+ autostart:allow-enable/disable/is-enabled capabilities); web toggles it from Settings. - Tray "Do Not Disturb" CheckMenuItem → emits lotus-dnd-changed to the web, which ORs it into the notification quiet-gate. zbus 5 (Linux target dep; blocking-api default). CI-compile-verified (windows+linux); reviewer confirmed no build-breakers. Runtime to check on Linux: DND toggle polarity, badge .desktop id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
128 lines
5.0 KiB
Rust
128 lines
5.0 KiB
Rust
//! P5-46 / P6-1 — System power management (call continuity).
|
|
//!
|
|
//! Prevents the system from sleeping / turning off the display while a voice or
|
|
//! video call is active, then releases the request when the call ends. The web
|
|
//! client calls `set_call_active(true|false)` from `useTauriCallPower` as the
|
|
//! call-embed atom transitions.
|
|
//!
|
|
//! Windows: `SetThreadExecutionState`. The request is per-thread and persists
|
|
//! until cleared, so we run every set/clear on the **main thread** (via
|
|
//! `run_on_main_thread`) to guarantee the clear cancels the matching set even
|
|
//! though Tauri commands otherwise run on a pool thread.
|
|
//!
|
|
//! Linux (P6-1): `org.freedesktop.ScreenSaver` `Inhibit`/`UnInhibit` over the
|
|
//! session bus (zbus, blocking API). The inhibit cookie returned by `Inhibit`
|
|
//! is stored in Tauri managed state (`ScreenSaverInhibit`) so the later
|
|
//! `UnInhibit` can release exactly that request. All D-Bus failures are logged
|
|
//! and swallowed — a missing/absent screensaver service must never break a call.
|
|
//!
|
|
//! macOS is out of scope for P6-1 (would use `IOPMAssertionCreate`) and stays a
|
|
//! no-op; the command stays cross-platform so the web side is unconditional.
|
|
|
|
use tauri::AppHandle;
|
|
|
|
/// Holds the current `org.freedesktop.ScreenSaver` inhibit cookie (Linux only).
|
|
/// `None` when no inhibit is active. Registered as Tauri managed state in
|
|
/// `setup()` and read by `set_call_active` via `AppHandle::state()`.
|
|
#[cfg(target_os = "linux")]
|
|
pub struct ScreenSaverInhibit(pub std::sync::Mutex<Option<u32>>);
|
|
|
|
/// Register the Linux screensaver-inhibit managed state. No-op elsewhere.
|
|
/// Called once from `native::setup()`.
|
|
pub fn setup(app: &AppHandle) -> tauri::Result<()> {
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
use tauri::Manager;
|
|
app.manage(ScreenSaverInhibit(std::sync::Mutex::new(None)));
|
|
}
|
|
#[cfg(not(target_os = "linux"))]
|
|
let _ = app;
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn set_call_active(app: AppHandle, active: bool) {
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
let _ = app.run_on_main_thread(move || {
|
|
use windows::Win32::System::Power::{
|
|
SetThreadExecutionState, ES_CONTINUOUS, ES_DISPLAY_REQUIRED, ES_SYSTEM_REQUIRED,
|
|
};
|
|
let flags = if active {
|
|
ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
|
|
} else {
|
|
// Clearing to ES_CONTINUOUS alone releases the sleep/display
|
|
// requirement while leaving no lingering per-thread state.
|
|
ES_CONTINUOUS
|
|
};
|
|
// Safety: FFI call with no pointers; returns the previous state,
|
|
// which we don't need.
|
|
unsafe {
|
|
SetThreadExecutionState(flags);
|
|
}
|
|
});
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
use tauri::Manager;
|
|
|
|
// Serialize access to the stored cookie for the duration of the D-Bus
|
|
// round-trip. This command is the only touch point, so holding the lock
|
|
// across the (short, blocking) call cannot deadlock.
|
|
let state = app.state::<ScreenSaverInhibit>();
|
|
let mut cookie_guard = match state.0.lock() {
|
|
Ok(guard) => guard,
|
|
Err(e) => {
|
|
eprintln!("power: ScreenSaverInhibit mutex poisoned: {e}");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let conn = match zbus::blocking::Connection::session() {
|
|
Ok(conn) => conn,
|
|
Err(e) => {
|
|
eprintln!("power: D-Bus session connection failed: {e}");
|
|
return;
|
|
}
|
|
};
|
|
let proxy = match zbus::blocking::Proxy::new(
|
|
&conn,
|
|
"org.freedesktop.ScreenSaver",
|
|
"/org/freedesktop/ScreenSaver",
|
|
"org.freedesktop.ScreenSaver",
|
|
) {
|
|
Ok(proxy) => proxy,
|
|
Err(e) => {
|
|
eprintln!("power: ScreenSaver proxy init failed: {e}");
|
|
return;
|
|
}
|
|
};
|
|
|
|
if active {
|
|
// Only take a new inhibit if one isn't already held, so repeated
|
|
// set_call_active(true) calls don't leak cookies.
|
|
if cookie_guard.is_none() {
|
|
let res: zbus::Result<u32> =
|
|
proxy.call("Inhibit", &("Lotus Chat", "In a Lotus Chat call"));
|
|
match res {
|
|
Ok(cookie) => *cookie_guard = Some(cookie),
|
|
Err(e) => eprintln!("power: ScreenSaver Inhibit failed: {e}"),
|
|
}
|
|
}
|
|
} else if let Some(cookie) = cookie_guard.take() {
|
|
let res: zbus::Result<()> = proxy.call("UnInhibit", &(cookie,));
|
|
if let Err(e) = res {
|
|
eprintln!("power: ScreenSaver UnInhibit failed: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
|
{
|
|
// No-op on other platforms (see module docs). Bind the args so the
|
|
// signature stays identical cross-platform and no unused warnings fire.
|
|
let _ = (&app, active);
|
|
}
|
|
}
|