plat/power.rs
1// power -- keep the computer awake while the app works: no standby, no screen saver
2//
3// inhibit(reason, what) -> Option<Inhibit> ask the system not to go to sleep on its own
4// (INHIBIT_SUSPEND: downloads, backups, long exports) or
5// also to keep the screen on and the session active
6// (INHIBIT_IDLE: presentations, videos; implies
7// INHIBIT_SUSPEND). The guard releases it when dropped.
8// None: not possible here (no service; the browser:
9// INHIBIT_SUSPEND alone, no Screen Wake Lock)
10// inhibited() -> u32 the INHIBIT_* bits the live guards of this process hold
11//
12// reason is shown by the system (`powercfg /requests`, the desktop's list of inhibitors);
13// "" names the app. The user can still put the computer to sleep, and the opposite
14// direction already reaches the app: UIC_SUSPEND (the system goes to sleep / woke up) and
15// UIC_SESSION_END (logout, shutdown) come from win_api.rs and linux_life.rs.
16//
17// Windows (win_power.rs): a power request with the reason as its text (PowerCreateRequest,
18// PowerSetRequest: INHIBIT_SUSPEND -> PowerRequestSystemRequired, INHIBIT_IDLE -> also
19// PowerRequestDisplayRequired). Linux (linux_power.rs): the Inhibit portal
20// (org.freedesktop.portal.Inhibit, flags suspend 4 / idle 8; also inside Flatpak; a refusal
21// of its backend arrives up to 300 ms after the answer and is waited for; INHIBIT_IDLE falls
22// back to idle alone where a backend only inhibits idle), without it
23// org.freedesktop.ScreenSaver (idle) and org.freedesktop.PowerManagement.Inhibit (suspend;
24// only best effort beside idle) -- all on a session-bus connection of plat that stays open
25// while a guard lives (the desktop ends inhibitions with their connection; it is closed when
26// the last guard goes). A backend may still drop an inhibition later; the guard does not
27// notice. logind's Inhibit is not used: it answers with a file descriptor, which plat's
28// D-Bus client does not receive. Browser and Android (web_power.rs): the Screen Wake Lock
29// (INHIBIT_IDLE only; taken again when the tab is visible again), FLAG_KEEP_SCREEN_ON and a
30// partial WakeLock (INHIBIT_SUSPEND).
31
32use alloc::string::String;
33use core::sync::atomic::{AtomicU32, Ordering};
34
35/// keep the system from going to sleep on its own (the screen may still turn off)
36pub const INHIBIT_SUSPEND: u32 = 1;
37/// keep the screen on and the session active (no screen saver, no lock); implies
38/// INHIBIT_SUSPEND
39pub const INHIBIT_IDLE: u32 = 2;
40
41// live guards per bit (inhibited)
42static LIVE: [AtomicU32; 2] = [AtomicU32::new(0), AtomicU32::new(0)];
43
44/// an inhibition; dropping it lets the system sleep again
45pub struct Inhibit {
46 what: u32,
47 held: native::Held,
48}
49
50impl Inhibit {
51 /// the INHIBIT_* bits this guard holds (INHIBIT_IDLE with INHIBIT_SUSPEND)
52 pub fn what(&self) -> u32 {
53 self.what
54 }
55}
56
57impl Drop for Inhibit {
58 fn drop(&mut self) {
59 native::release(&mut self.held);
60 for (k, live) in LIVE.iter().enumerate() {
61 if self.what & (1 << k) != 0 {
62 live.fetch_sub(1, Ordering::SeqCst);
63 }
64 }
65 }
66}
67
68impl core::fmt::Debug for Inhibit {
69 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
70 write!(f, "Inhibit({})", self.what)
71 }
72}
73
74/// keep the system awake (INHIBIT_SUSPEND) or also the screen on (INHIBIT_IDLE) until the
75/// guard is dropped -> None: not possible here
76pub fn inhibit(reason: &str, what: u32) -> Option<Inhibit> {
77 if what == 0 || what & !(INHIBIT_SUSPEND | INHIBIT_IDLE) != 0 {
78 return None;
79 }
80 let what = if what & INHIBIT_IDLE != 0 { what | INHIBIT_SUSPEND } else { what };
81 let reason: String = if reason.is_empty() { crate::vfs::app_id() } else { reason.chars().filter(|&c| c != '\0').take(256).collect() };
82 let held = native::hold(&reason, what)?;
83 for (k, live) in LIVE.iter().enumerate() {
84 if what & (1 << k) != 0 {
85 live.fetch_add(1, Ordering::SeqCst);
86 }
87 }
88 Some(Inhibit { what, held })
89}
90
91/// the INHIBIT_* bits the live guards of this process hold
92pub fn inhibited() -> u32 {
93 LIVE.iter().enumerate().filter(|(_, l)| l.load(Ordering::SeqCst) != 0).fold(0, |m, (k, _)| m | 1 << k)
94}
95
96#[cfg(windows)]
97#[path = "win_power.rs"]
98mod native;
99
100#[cfg(target_os = "linux")]
101#[path = "linux_power.rs"]
102mod native;
103
104#[cfg(any(target_arch = "wasm32", target_os = "android"))]
105#[path = "web_power.rs"]
106mod native;
107
108#[cfg(not(any(windows, target_os = "linux", target_arch = "wasm32", target_os = "android")))]
109mod native {
110 pub struct Held;
111 pub fn hold(_reason: &str, _what: u32) -> Option<Held> {
112 None
113 }
114 pub fn release(_h: &mut Held) {}
115}