Files
codex/codex-rs/utils/sleep-inhibitor/src/macos.rs
Yaroslav Volovich 5b71246001 fix: simplify macOS sleep inhibitor FFI (#12340)
Summary
- simplify the macOS sleep inhibitor FFI by replacing `dlopen` / `dlsym`
/ `transmute` with normal IOKit extern calls and `SAFETY` comments
- switch to cfg-selected platform implementations
(`imp::SleepInhibitor`) instead of `Box<dyn ...>`
- check in minimal IOKit bindings generated with `bindgen` and include
them from the macOS backend
- enable direct IOKit linkage in Bazel macOS builds by registering
`IOKit` in the Bazel `osx.framework(...)` toolchain extension list
- update `Cargo.lock` and `MODULE.bazel.lock` after removing the
build-time `bindgen` dependency path

Testing
- `just fmt`
- `cargo clippy -p codex-utils-sleep-inhibitor --all-targets -- -D
warnings`
- `cargo test -p codex-utils-sleep-inhibitor`
- `bazel test //codex-rs/utils/sleep-inhibitor:all --test_output=errors`
- `just bazel-lock-update`
- `just bazel-lock-check`

Context
- follow-up to #11711 addressing Ryan's review comments
- `bindgen` is used to generate the checked-in bindings file, but not at
build time
2026-02-20 09:52:21 -08:00

108 lines
3.3 KiB
Rust

use core_foundation::base::TCFType;
use core_foundation::string::CFString;
use tracing::warn;
#[allow(
dead_code,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
clippy::all
)]
mod iokit {
#[link(name = "IOKit", kind = "framework")]
unsafe extern "C" {}
include!("iokit_bindings.rs");
}
type IOPMAssertionID = iokit::IOPMAssertionID;
type IOPMAssertionLevel = iokit::IOPMAssertionLevel;
type IOReturn = iokit::IOReturn;
const ASSERTION_REASON: &str = "Codex is running an active turn";
// Apple exposes this assertion type as a `CFSTR(...)` macro, so bindgen cannot generate a
// reusable `CFStringRef` constant for it.
const ASSERTION_TYPE_PREVENT_USER_IDLE_SYSTEM_SLEEP: &str = "PreventUserIdleSystemSleep";
#[derive(Debug, Default)]
pub(crate) struct SleepInhibitor {
assertion: Option<MacSleepAssertion>,
}
impl SleepInhibitor {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn acquire(&mut self) {
if self.assertion.is_some() {
return;
}
match MacSleepAssertion::create(ASSERTION_REASON) {
Ok(assertion) => {
self.assertion = Some(assertion);
}
Err(error) => {
warn!(
iokit_error = error,
"Failed to create macOS sleep-prevention assertion"
);
}
}
}
pub(crate) fn release(&mut self) {
self.assertion = None;
}
}
#[derive(Debug)]
struct MacSleepAssertion {
id: IOPMAssertionID,
}
impl MacSleepAssertion {
fn create(name: &str) -> Result<Self, IOReturn> {
let assertion_type = CFString::new(ASSERTION_TYPE_PREVENT_USER_IDLE_SYSTEM_SLEEP);
let assertion_name = CFString::new(name);
let mut id: IOPMAssertionID = 0;
// `core-foundation` and the generated bindings each declare an opaque `__CFString` type,
// so we cast to the bindgen pointer aliases before crossing the FFI boundary.
let assertion_type_ref: iokit::CFStringRef = assertion_type.as_concrete_TypeRef().cast();
let assertion_name_ref: iokit::CFStringRef = assertion_name.as_concrete_TypeRef().cast();
let result = unsafe {
// SAFETY: `assertion_type_ref` and `assertion_name_ref` are valid `CFStringRef`s and
// `&mut id` is a valid out-pointer for `IOPMAssertionCreateWithName` to initialize.
iokit::IOPMAssertionCreateWithName(
assertion_type_ref,
iokit::kIOPMAssertionLevelOn as IOPMAssertionLevel,
assertion_name_ref,
&mut id,
)
};
if result == iokit::kIOReturnSuccess as IOReturn {
Ok(Self { id })
} else {
Err(result)
}
}
}
impl Drop for MacSleepAssertion {
fn drop(&mut self) {
let result = unsafe {
// SAFETY: `self.id` was returned by `IOPMAssertionCreateWithName` and this `Drop`
// implementation releases it exactly once when the owning assertion is dropped.
iokit::IOPMAssertionRelease(self.id)
};
if result != iokit::kIOReturnSuccess as IOReturn {
warn!(
iokit_error = result,
"Failed to release macOS sleep-prevention assertion"
);
}
}
}