mirror of
https://github.com/openai/codex.git
synced 2026-04-29 00:55:38 +00:00
## Background - follow-up to previous macOS-only PR: https://github.com/openai/codex/pull/11711 - follow-up macOS refactor PR (current structural approach used here): https://github.com/openai/codex/pull/12340 ## Summary - extend `codex-utils-sleep-inhibitor` with Linux and Windows backends while preserving existing macOS behavior - Linux backend: - use `systemd-inhibit` (`--what=idle --mode=block`) when available - fall back to `gnome-session-inhibit` (`--inhibit idle`) when available - keep no-op behavior if neither backend exists on host - Windows backend: - use Win32 power request handles (`PowerCreateRequest` + `PowerSetRequest` / `PowerClearRequest`) with `PowerRequestSystemRequired` - make `prevent_idle_sleep` Experimental on macOS/Linux/Windows; keep under development on other targets ## Testing - `just fmt` - `cargo test -p codex-utils-sleep-inhibitor` - `cargo test -p codex-core features::tests::` - `cargo test -p codex-tui chatwidget::tests::` - `just fix -p codex-utils-sleep-inhibitor` - `just fix -p codex-core` ## Semantics and API references - Goal remains: prevent idle system sleep while a turn is running. - Linux: - `systemd-inhibit` / login1 inhibitor model: - https://www.freedesktop.org/software/systemd/man/latest/systemd-inhibit.html - https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html - https://systemd.io/INHIBITOR_LOCKS/ - xdg-desktop-portal Inhibit (relevant for sandboxed apps): - https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html - Windows: - `PowerCreateRequest`: - https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-powercreaterequest - `PowerSetRequest`: - https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-powersetrequest - `PowerClearRequest`: - https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-powerclearrequest - `SetThreadExecutionState` (alternative baseline API): - https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setthreadexecutionstate ## Chromium vs this PR - Chromium Linux backend: - https://github.com/chromium/chromium/blob/main/services/device/wake_lock/power_save_blocker/power_save_blocker_linux.cc - Chromium Windows backend: - https://github.com/chromium/chromium/blob/main/services/device/wake_lock/power_save_blocker/power_save_blocker_win.cc - Electron powerSaveBlocker entry point: - https://github.com/electron/electron/blob/main/shell/browser/api/electron_api_power_save_blocker.cc ## Why we differ from Chromium - Linux implementation mechanism: - Chromium uses in-process D-Bus APIs plus UI-integrated screen-saver suspension. - This PR uses command-based inhibitor backends (`systemd-inhibit`, `gnome-session-inhibit`) instead of linking a Linux D-Bus client in this crate. - Reason: keep `codex-utils-sleep-inhibitor` dependency-light and avoid Linux CI/toolchain fragility from new native D-Bus linkage, while preserving the same runtime intent (hold an inhibitor while a turn runs). - Linux UI integration scope: - Chromium also uses `display::Screen::SuspendScreenSaver()` in its UI stack. - Codex `codex-rs` does not have that display abstraction in this crate, so this PR scopes Linux behavior to process-level sleep inhibition only. - Windows wake-lock type breadth: - Chromium supports both display/system wake-lock types and extra display-specific handling for some pre-Win11 scenarios. - Codex’s feature is scoped to turn execution continuity (not forcing display on), so this PR uses `PowerRequestSystemRequired` only.
102 lines
2.7 KiB
Rust
102 lines
2.7 KiB
Rust
//! Cross-platform helper for preventing idle sleep while a turn is running.
|
|
//!
|
|
//! Platform-specific behavior:
|
|
//! - macOS: Uses native IOKit power assertions instead of spawning `caffeinate`.
|
|
//! - Linux: Spawns `systemd-inhibit` or `gnome-session-inhibit` while active.
|
|
//! - Windows: Uses `PowerCreateRequest` + `PowerSetRequest` with
|
|
//! `PowerRequestSystemRequired`.
|
|
//! - Other platforms: No-op backend.
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
|
mod dummy;
|
|
#[cfg(target_os = "linux")]
|
|
mod linux_inhibitor;
|
|
#[cfg(target_os = "macos")]
|
|
mod macos;
|
|
#[cfg(target_os = "windows")]
|
|
mod windows_inhibitor;
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
|
use dummy as imp;
|
|
#[cfg(target_os = "linux")]
|
|
use linux_inhibitor as imp;
|
|
#[cfg(target_os = "macos")]
|
|
use macos as imp;
|
|
#[cfg(target_os = "windows")]
|
|
use windows_inhibitor as imp;
|
|
|
|
/// Keeps the machine awake while a turn is in progress when enabled.
|
|
#[derive(Debug)]
|
|
pub struct SleepInhibitor {
|
|
enabled: bool,
|
|
platform: imp::SleepInhibitor,
|
|
}
|
|
|
|
impl SleepInhibitor {
|
|
pub fn new(enabled: bool) -> Self {
|
|
Self {
|
|
enabled,
|
|
platform: imp::SleepInhibitor::new(),
|
|
}
|
|
}
|
|
|
|
/// Update the active turn state; turns sleep prevention on/off as needed.
|
|
pub fn set_turn_running(&mut self, turn_running: bool) {
|
|
if !self.enabled {
|
|
self.release();
|
|
return;
|
|
}
|
|
|
|
if turn_running {
|
|
self.acquire();
|
|
} else {
|
|
self.release();
|
|
}
|
|
}
|
|
|
|
fn acquire(&mut self) {
|
|
self.platform.acquire();
|
|
}
|
|
|
|
fn release(&mut self) {
|
|
self.platform.release();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::SleepInhibitor;
|
|
|
|
#[test]
|
|
fn sleep_inhibitor_toggles_without_panicking() {
|
|
let mut inhibitor = SleepInhibitor::new(true);
|
|
inhibitor.set_turn_running(true);
|
|
inhibitor.set_turn_running(false);
|
|
}
|
|
|
|
#[test]
|
|
fn sleep_inhibitor_disabled_does_not_panic() {
|
|
let mut inhibitor = SleepInhibitor::new(false);
|
|
inhibitor.set_turn_running(true);
|
|
inhibitor.set_turn_running(false);
|
|
}
|
|
|
|
#[test]
|
|
fn sleep_inhibitor_multiple_true_calls_are_idempotent() {
|
|
let mut inhibitor = SleepInhibitor::new(true);
|
|
inhibitor.set_turn_running(true);
|
|
inhibitor.set_turn_running(true);
|
|
inhibitor.set_turn_running(true);
|
|
inhibitor.set_turn_running(false);
|
|
}
|
|
|
|
#[test]
|
|
fn sleep_inhibitor_can_toggle_multiple_times() {
|
|
let mut inhibitor = SleepInhibitor::new(true);
|
|
inhibitor.set_turn_running(true);
|
|
inhibitor.set_turn_running(false);
|
|
inhibitor.set_turn_running(true);
|
|
inhibitor.set_turn_running(false);
|
|
}
|
|
}
|