use libc::c_void;
use std::thread::{self, JoinHandle};
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::oneshot;
pub(crate) struct PowerObserver {
run_loop: usize,
thread: Option<JoinHandle<()>>,
}
impl PowerObserver {
pub(crate) async fn spawn(wake_tx: UnboundedSender<()>) -> Self {
let (rl_tx, rl_rx) = oneshot::channel::<usize>();
let thread = thread::Builder::new()
.name("mousehop-power-observer".into())
.spawn(move || run(wake_tx, rl_tx))
.expect("spawn power observer thread");
let run_loop = rl_rx.await.unwrap_or(0);
Self {
run_loop,
thread: Some(thread),
}
}
}
impl Drop for PowerObserver {
fn drop(&mut self) {
if self.run_loop != 0 {
unsafe { CFRunLoopStop(self.run_loop as *mut c_void) };
}
if let Some(t) = self.thread.take() {
let _ = t.join();
}
}
}
struct PowerCtx {
wake_tx: UnboundedSender<()>,
root_port: u32,
}
extern "C" fn power_callback(
refcon: *mut c_void,
_service: u32,
msg_type: u32,
msg_arg: *mut c_void,
) {
const K_IO_MESSAGE_CAN_SYSTEM_SLEEP: u32 = 0xE000_0270;
const K_IO_MESSAGE_SYSTEM_WILL_SLEEP: u32 = 0xE000_0280;
const K_IO_MESSAGE_SYSTEM_HAS_POWERED_ON: u32 = 0xE000_0300;
if refcon.is_null() {
return;
}
let ctx = unsafe { &*(refcon as *const PowerCtx) };
match msg_type {
K_IO_MESSAGE_CAN_SYSTEM_SLEEP | K_IO_MESSAGE_SYSTEM_WILL_SLEEP => {
unsafe {
IOAllowPowerChange(ctx.root_port, msg_arg as isize);
}
}
K_IO_MESSAGE_SYSTEM_HAS_POWERED_ON => {
log::info!("macos_power: system woke; signaling listener to drop peer conns");
let _ = ctx.wake_tx.send(());
}
_ => {}
}
}
fn run(wake_tx: UnboundedSender<()>, rl_tx: oneshot::Sender<usize>) {
let ctx = Box::into_raw(Box::new(PowerCtx {
wake_tx,
root_port: 0,
}));
let mut notifier_object: u32 = 0;
let mut notification_port: *mut c_void = std::ptr::null_mut();
let root_port = unsafe {
IORegisterForSystemPower(
ctx as *mut c_void,
&mut notification_port,
power_callback,
&mut notifier_object,
)
};
if root_port == 0 || notification_port.is_null() {
log::warn!("macos_power: IORegisterForSystemPower failed; observer inactive");
unsafe {
drop(Box::from_raw(ctx));
}
return;
}
unsafe {
(*ctx).root_port = root_port;
}
let src = unsafe { IONotificationPortGetRunLoopSource(notification_port) };
if src.is_null() {
log::warn!("macos_power: IONotificationPortGetRunLoopSource returned null");
unsafe {
IODeregisterForSystemPower(&mut notifier_object);
IONotificationPortDestroy(notification_port);
drop(Box::from_raw(ctx));
}
return;
}
let run_loop = unsafe { CFRunLoopGetCurrent() };
unsafe {
CFRunLoopAddSource(run_loop, src, kCFRunLoopCommonModes);
}
let _ = rl_tx.send(run_loop as usize);
log::debug!("macos_power: CFRunLoop running");
unsafe {
CFRunLoopRun();
}
log::debug!("macos_power: CFRunLoop returned; cleaning up");
unsafe {
IODeregisterForSystemPower(&mut notifier_object);
IONotificationPortDestroy(notification_port);
drop(Box::from_raw(ctx));
}
}
#[link(name = "IOKit", kind = "framework")]
extern "C" {
fn IORegisterForSystemPower(
refcon: *mut c_void,
port_ref: *mut *mut c_void,
callback: extern "C" fn(*mut c_void, u32, u32, *mut c_void),
notifier: *mut u32,
) -> u32;
fn IODeregisterForSystemPower(notifier: *mut u32) -> i32;
fn IONotificationPortGetRunLoopSource(notify: *mut c_void) -> *mut c_void;
fn IONotificationPortDestroy(notify: *mut c_void);
fn IOAllowPowerChange(kernel_port: u32, notification_id: isize) -> i32;
}
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
fn CFRunLoopGetCurrent() -> *mut c_void;
fn CFRunLoopRun();
fn CFRunLoopStop(rl: *mut c_void);
fn CFRunLoopAddSource(rl: *mut c_void, source: *mut c_void, mode: *const c_void);
static kCFRunLoopCommonModes: *const c_void;
}