Skip to main content

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}