//! 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>); /// 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::(); 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 = 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); } }