feat(native): Linux parity + autostart + tray DND (P6-1; no macOS)
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>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! P5-46 — System power management (call continuity).
|
||||
//! 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
|
||||
@@ -10,12 +10,36 @@
|
||||
//! `run_on_main_thread`) to guarantee the clear cancels the matching set even
|
||||
//! though Tauri commands otherwise run on a pool thread.
|
||||
//!
|
||||
//! Other platforms are a no-op for now (macOS would use `IOPMAssertionCreate`,
|
||||
//! Linux `org.freedesktop.ScreenSaver`/`login1` inhibit) — tracked as a future
|
||||
//! extension; the command stays cross-platform so the web side is unconditional.
|
||||
//! 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")]
|
||||
@@ -39,9 +63,64 @@ pub fn set_call_active(app: AppHandle, active: bool) {
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// No-op on non-Windows platforms (see module docs). Bind the args so the
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user