#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use std::{
ffi::{c_int, c_void},
marker::{PhantomData, PhantomPinned},
ptr::null_mut,
sync::{mpsc, Arc, Barrier},
thread::JoinHandle,
};
use tracing::{debug, trace, warn};
use crate::{Error, EventHandler, SleepQueryResponse};
pub struct Naptime {
run_loop: Option<CFRunLoopRefWrapper>,
run_loop_thread: Option<JoinHandle<()>>,
}
impl Naptime {
pub fn new<E>(event_handler: E) -> Result<Self, Error>
where
E: EventHandler,
{
let event_handler = Box::new(event_handler);
let (tx, rx) = mpsc::channel();
let barrier = Arc::new(Barrier::new(2));
let thread_barrier = barrier.clone();
let run_loop_thread =
std::thread::spawn(move || run_loop_proc(event_handler, tx, thread_barrier));
let run_loop = rx.recv().unwrap()?;
unsafe {
CFRetain(run_loop.0 as *const c_void);
}
barrier.wait();
Ok(Self {
run_loop: Some(run_loop),
run_loop_thread: Some(run_loop_thread),
})
}
}
struct CFRunLoopRefWrapper(CFRunLoopRef);
unsafe impl Send for CFRunLoopRefWrapper {}
unsafe impl Sync for CFRunLoopRefWrapper {}
struct ThreadState {
event_handler: Box<dyn EventHandler>,
root_port: io_connect_t,
}
fn run_loop_proc(
event_handler: Box<dyn EventHandler>,
tx: mpsc::Sender<Result<CFRunLoopRefWrapper, Error>>,
barrier: Arc<Barrier>,
) {
let run_loop = unsafe {
let run_loop = CFRunLoopGetCurrent();
CFRetain(run_loop as *const c_void);
run_loop
};
let mut state = Box::new(ThreadState {
event_handler,
root_port: 0,
});
let state_ptr = &mut *state as *mut ThreadState;
let mut notify_port_ref: IONotificationPortRef = null_mut();
let mut notifier_object: io_object_t = 0;
let root_port = unsafe {
IORegisterForSystemPower(
state_ptr as *mut c_void,
&mut notify_port_ref,
system_power_event_handler,
&mut notifier_object,
)
};
if root_port == 0 {
unsafe { CFRelease(run_loop as *const c_void) };
tx.send(Err(Error(format!(
"IORegisterForSystemPower failed. code={:08x}",
root_port
))))
.unwrap();
return;
}
state.root_port = root_port;
tx.send(Ok(CFRunLoopRefWrapper(run_loop))).unwrap();
drop(tx);
barrier.wait();
drop(barrier);
unsafe {
CFRunLoopAddSource(
run_loop,
IONotificationPortGetRunLoopSource(notify_port_ref),
kCFRunLoopCommonModes,
);
trace!("starting run loop");
CFRunLoopRun();
trace!("loop done!");
CFRunLoopRemoveSource(
run_loop,
IONotificationPortGetRunLoopSource(notify_port_ref),
kCFRunLoopCommonModes,
);
IODeregisterForSystemPower(&mut notifier_object);
IOServiceClose(root_port);
IONotificationPortDestroy(notify_port_ref);
CFRelease(run_loop as *const c_void);
}
trace!("run loop thread exiting");
}
extern "C" fn system_power_event_handler(
refCon: *mut c_void,
_service: io_service_t,
messageType: u32,
messageArgument: *mut c_void,
) {
let state_ptr = refCon as *mut ThreadState;
let mut state = unsafe { Box::from_raw(state_ptr) };
trace!("message: {:08x}", messageType);
match messageType {
kIOMessageCanSystemSleep => {
trace!("kIOMessageCanSystemSleep");
let response = state.event_handler.sleep_query();
match response {
SleepQueryResponse::Allow => {
let ret = unsafe { IOAllowPowerChange(state.root_port, messageArgument) };
if ret != kIOReturnSuccess {
warn!("IOAllowPowerChange failed. ret={:08x}", ret);
}
}
SleepQueryResponse::Deny => {
let ret = unsafe { IOCancelPowerChange(state.root_port, messageArgument) };
if ret != kIOReturnSuccess {
warn!("IOCancelPowerChange failed. ret={:08x}", ret);
}
}
}
}
kIOMessageSystemWillSleep => {
trace!("kIOMessageSystemWillSleep");
state.event_handler.sleep();
let ret = unsafe { IOAllowPowerChange(state.root_port, messageArgument) };
if ret != kIOReturnSuccess {
warn!("IOAllowPowerChange failed. ret={:08x}", ret);
}
}
kIOMessageSystemWillNotSleep => {
trace!("kIOMessageSystemWillNotSleep");
state.event_handler.sleep_failed();
}
kIOMessageSystemWillPowerOn => {
trace!("kIOMessageSystemWillPowerOn");
}
kIOMessageSystemHasPoweredOn => {
trace!("kIOMessageSystemHasPoweredOn");
state.event_handler.wake();
}
_ => {
debug!("unknown message type");
}
}
Box::leak(state);
}
impl Drop for Naptime {
fn drop(&mut self) {
if let Some(CFRunLoopRefWrapper(run_loop)) = self.run_loop.take() {
unsafe {
CFRunLoopStop(run_loop);
CFRelease(run_loop as *const c_void);
}
}
if let Some(thread_handle) = self.run_loop_thread.take() {
thread_handle.join().unwrap();
}
}
}
const fn err_system(x: u32) -> u32 {
(x & 0x3f) << 26
}
const fn err_sub(x: u32) -> u32 {
(x & 0xfff) << 14
}
type natural_t = u32;
type mach_port_t = natural_t;
type io_object_t = mach_port_t;
type io_connect_t = io_object_t;
type io_service_t = io_object_t;
type kern_return_t = c_int;
type CFTypeRef = *const c_void;
#[repr(C)]
struct __CFString(c_void);
type CFStringRef = *const __CFString;
#[repr(C)]
struct __CFRunLoop {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
type CFRunLoopRef = *mut __CFRunLoop;
#[repr(C)]
struct __CFRunLoopSource {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
type CFRunLoopSourceRef = *mut __CFRunLoopSource;
type CFRunLoopMode = CFStringRef;
#[cfg_attr(target_os = "macos", link(name = "CoreFoundation", kind = "framework"))]
extern "C" {
static kCFRunLoopCommonModes: CFStringRef;
fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
fn CFRunLoopRemoveSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
fn CFRunLoopGetCurrent() -> CFRunLoopRef;
fn CFRunLoopRun();
fn CFRunLoopStop(rl: CFRunLoopRef);
fn CFRetain(cf: CFTypeRef) -> CFTypeRef;
fn CFRelease(cf: CFTypeRef);
}
const fn sys_iokit() -> u32 {
err_system(0x38)
}
const fn sub_iokit_common() -> u32 {
err_sub(0)
}
const fn iokit_common_msg(message: u32) -> u32 {
sys_iokit() | sub_iokit_common() | message
}
const kIOMessageCanSystemSleep: u32 = iokit_common_msg(0x270);
const kIOMessageSystemWillSleep: u32 = iokit_common_msg(0x280);
const kIOMessageSystemWillNotSleep: u32 = iokit_common_msg(0x290);
const kIOMessageSystemHasPoweredOn: u32 = iokit_common_msg(0x300);
const kIOMessageSystemWillPowerOn: u32 = iokit_common_msg(0x320);
const kIOReturnSuccess: i32 = 0;
type IOReturn = kern_return_t;
#[repr(C)]
struct IONotificationPort {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
type IONotificationPortRef = *mut IONotificationPort;
type IOServiceInterestCallback = unsafe extern "C" fn(
refcon: *mut c_void,
service: io_service_t,
messageType: u32,
messageArgument: *mut c_void,
);
#[cfg_attr(target_os = "macos", link(name = "IOKit", kind = "framework"))]
extern "C" {
fn IORegisterForSystemPower(
refcon: *mut c_void,
thePortRef: *mut IONotificationPortRef,
callback: IOServiceInterestCallback,
notifier: *mut io_object_t,
) -> io_connect_t;
fn IODeregisterForSystemPower(notifier: *mut io_object_t) -> IOReturn;
fn IONotificationPortGetRunLoopSource(notify: IONotificationPortRef) -> CFRunLoopSourceRef;
fn IONotificationPortDestroy(notify: IONotificationPortRef);
fn IOAllowPowerChange(kernelPort: io_connect_t, notificationID: *const c_void) -> IOReturn;
fn IOCancelPowerChange(kernelPort: io_connect_t, notificationID: *const c_void) -> IOReturn;
fn IOServiceClose(connect: io_connect_t) -> kern_return_t;
}