Files
cinny-desktop/src-tauri/src/native/power.rs
T

128 lines
5.0 KiB
Rust
Raw Normal View History

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