#![allow(non_snake_case, non_upper_case_globals, dead_code)]
use std::ffi::c_void;
use std::ptr;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
use tauri::{AppHandle, Runtime};
use crate::watcher::{emit_if_changed, event_name, MonitorHandle};
type CFRunLoopRef = *mut c_void;
type CFRunLoopSourceRef = *mut c_void;
type CFRunLoopTimerRef = *mut c_void;
type CFStringRef = *const c_void;
type CFAllocatorRef = *const c_void;
type CFRunLoopTimerCallBack = unsafe extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
type CFRunLoopPerformCallBack = unsafe extern "C" fn(info: *mut c_void);
#[repr(C)]
struct CFRunLoopSourceContext {
version: isize,
info: *mut c_void,
retain: *const c_void,
release: *const c_void,
copyDescription: *const c_void,
equal: *const c_void,
hash: *const c_void,
schedule: *const c_void,
cancel: *const c_void,
perform: CFRunLoopPerformCallBack,
}
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
static kCFRunLoopDefaultMode: CFStringRef;
fn CFRunLoopGetCurrent() -> CFRunLoopRef;
fn CFRunLoopRun();
fn CFRunLoopStop(rl: CFRunLoopRef);
fn CFRunLoopWakeUp(rl: CFRunLoopRef);
fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFStringRef);
fn CFRunLoopRemoveSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFStringRef);
fn CFRunLoopSourceCreate(
allocator: CFAllocatorRef,
order: isize,
context: *mut CFRunLoopSourceContext,
) -> CFRunLoopSourceRef;
fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFStringRef);
fn CFRunLoopTimerCreate(
allocator: CFAllocatorRef,
fireDate: f64,
interval: f64,
flags: usize,
order: isize,
callout: CFRunLoopTimerCallBack,
context: *mut c_void,
) -> CFRunLoopTimerRef;
fn CFAbsoluteTimeGetCurrent() -> f64;
fn CFRelease(cf: *const c_void);
}
type IOPSCallback = unsafe extern "C" fn(context: *mut c_void);
#[link(name = "IOKit", kind = "framework")]
extern "C" {
fn IOPSNotificationCreateRunLoopSource(
callback: IOPSCallback,
context: *mut c_void,
) -> CFRunLoopSourceRef;
}
type CGDirectDisplayID = u32;
type CGDisplayChangeSummaryFlags = u32;
type CGDisplayReconfigurationCallBack = unsafe extern "C" fn(
display: CGDirectDisplayID,
flags: CGDisplayChangeSummaryFlags,
userInfo: *mut c_void,
);
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
fn CGDisplayRegisterReconfigurationCallback(
callback: CGDisplayReconfigurationCallBack,
userInfo: *mut c_void,
) -> i32;
fn CGDisplayRemoveReconfigurationCallback(
callback: CGDisplayReconfigurationCallBack,
userInfo: *mut c_void,
) -> i32;
}
type SCNetworkReachabilityRef = *mut c_void;
type SCNetworkReachabilityFlags = u32;
type SCNetworkReachabilityCallBack = unsafe extern "C" fn(
target: SCNetworkReachabilityRef,
flags: SCNetworkReachabilityFlags,
info: *mut c_void,
);
#[repr(C)]
struct SCNetworkReachabilityContext {
version: isize,
info: *mut c_void,
retain: *const c_void,
release: *const c_void,
copyDescription: *const c_void,
}
#[repr(C)]
struct SockaddrIn {
sin_len: u8,
sin_family: u8,
sin_port: u16,
sin_addr: u32,
sin_zero: [u8; 8],
}
#[link(name = "SystemConfiguration", kind = "framework")]
extern "C" {
fn SCNetworkReachabilityCreateWithAddress(
allocator: CFAllocatorRef,
address: *const c_void,
) -> SCNetworkReachabilityRef;
fn SCNetworkReachabilitySetCallback(
target: SCNetworkReachabilityRef,
callout: SCNetworkReachabilityCallBack,
context: *mut SCNetworkReachabilityContext,
) -> u8;
fn SCNetworkReachabilityScheduleWithRunLoop(
target: SCNetworkReachabilityRef,
runLoop: CFRunLoopRef,
runLoopMode: CFStringRef,
) -> u8;
fn SCNetworkReachabilityUnscheduleFromRunLoop(
target: SCNetworkReachabilityRef,
runLoop: CFRunLoopRef,
runLoopMode: CFStringRef,
) -> u8;
}
const KEEPALIVE_INTERVAL: f64 = 1.0e10;
trait Fire: Send + Sync {
fn fire(&self);
}
struct EmitCtx<R: Runtime> {
app: AppHandle<R>,
kind: String,
event: String,
last: Mutex<Option<serde_json::Value>>,
}
impl<R: Runtime> Fire for EmitCtx<R> {
fn fire(&self) {
let Ok(mut last) = self.last.lock() else {
return;
};
emit_if_changed(&self.app, &self.event, &self.kind, &mut last);
}
}
unsafe fn fire_ctx(context: *mut c_void) {
let ctx = &*(context as *const Box<dyn Fire>);
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| ctx.fire()));
}
unsafe extern "C" fn battery_callback(context: *mut c_void) {
fire_ctx(context);
}
unsafe extern "C" fn display_callback(
_display: CGDirectDisplayID,
_flags: CGDisplayChangeSummaryFlags,
user_info: *mut c_void,
) {
fire_ctx(user_info);
}
unsafe extern "C" fn network_callback(
_target: SCNetworkReachabilityRef,
_flags: SCNetworkReachabilityFlags,
info: *mut c_void,
) {
fire_ctx(info);
}
unsafe extern "C" fn noop_timer(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe extern "C" fn stop_perform(_info: *mut c_void) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
enum LoopState {
Pending,
Running { run_loop: usize, stop_source: usize },
}
struct MacRunLoopHandle {
state: Arc<(Mutex<LoopState>, Condvar)>,
join: Option<JoinHandle<()>>,
}
impl MonitorHandle for MacRunLoopHandle {
fn stop(mut self: Box<Self>) {
let (lock, cvar) = &*self.state;
let mut guard = lock.lock().unwrap_or_else(|e| e.into_inner());
while matches!(*guard, LoopState::Pending) {
guard = cvar.wait(guard).unwrap_or_else(|e| e.into_inner());
}
if let LoopState::Running {
run_loop,
stop_source,
} = *guard
{
unsafe {
CFRunLoopSourceSignal(stop_source as CFRunLoopSourceRef);
CFRunLoopWakeUp(run_loop as CFRunLoopRef);
}
}
drop(guard);
if let Some(join) = self.join.take() {
let _ = join.join();
}
}
}
type Teardown = Box<dyn FnOnce()>;
fn register_battery(run_loop: CFRunLoopRef, ctx: *mut c_void) -> Teardown {
let source = unsafe { IOPSNotificationCreateRunLoopSource(battery_callback, ctx) };
if source.is_null() {
return Box::new(|| {});
}
unsafe { CFRunLoopAddSource(run_loop, source, kCFRunLoopDefaultMode) };
let rl = run_loop as usize;
let src = source as usize;
Box::new(move || unsafe {
CFRunLoopRemoveSource(
rl as CFRunLoopRef,
src as CFRunLoopSourceRef,
kCFRunLoopDefaultMode,
);
CFRelease(src as *const c_void);
})
}
fn register_display(_run_loop: CFRunLoopRef, ctx: *mut c_void) -> Teardown {
unsafe { CGDisplayRegisterReconfigurationCallback(display_callback, ctx) };
let ctx_us = ctx as usize;
Box::new(move || unsafe {
CGDisplayRemoveReconfigurationCallback(display_callback, ctx_us as *mut c_void);
})
}
fn register_network(run_loop: CFRunLoopRef, ctx: *mut c_void) -> Teardown {
let addr = SockaddrIn {
sin_len: core::mem::size_of::<SockaddrIn>() as u8,
sin_family: 2, sin_port: 0,
sin_addr: 0,
sin_zero: [0; 8],
};
let reach = unsafe {
SCNetworkReachabilityCreateWithAddress(
ptr::null(),
&addr as *const SockaddrIn as *const c_void,
)
};
if reach.is_null() {
eprintln!(
"device-info: SCNetworkReachabilityCreateWithAddress failed; \
network watch will report the initial value only"
);
return Box::new(|| {});
}
let mut context = SCNetworkReachabilityContext {
version: 0,
info: ctx,
retain: ptr::null(),
release: ptr::null(),
copyDescription: ptr::null(),
};
let ok = unsafe {
let set = SCNetworkReachabilitySetCallback(reach, network_callback, &mut context);
let sched =
SCNetworkReachabilityScheduleWithRunLoop(reach, run_loop, kCFRunLoopDefaultMode);
set != 0 && sched != 0
};
if !ok {
eprintln!(
"device-info: failed to schedule SCNetworkReachability callback; \
network watch will report the initial value only"
);
}
let reach_us = reach as usize;
let rl = run_loop as usize;
Box::new(move || unsafe {
SCNetworkReachabilityUnscheduleFromRunLoop(
reach_us as SCNetworkReachabilityRef,
rl as CFRunLoopRef,
kCFRunLoopDefaultMode,
);
CFRelease(reach_us as *const c_void);
})
}
pub(super) fn try_spawn<R: Runtime>(
app: &AppHandle<R>,
kind: &str,
) -> crate::Result<Option<Box<dyn MonitorHandle>>> {
let register: fn(CFRunLoopRef, *mut c_void) -> Teardown = match kind {
"battery" => register_battery,
"display" => register_display,
"network" => register_network,
_ => return Ok(None),
};
Ok(Some(spawn_runloop(app, kind, register)))
}
fn spawn_runloop<R: Runtime>(
app: &AppHandle<R>,
kind: &str,
register: fn(CFRunLoopRef, *mut c_void) -> Teardown,
) -> Box<dyn MonitorHandle> {
let ctx: Box<dyn Fire> = Box::new(EmitCtx {
app: app.clone(),
kind: kind.to_string(),
event: event_name(kind),
last: Mutex::new(None),
});
let ctx_ptr = Box::into_raw(Box::new(ctx)) as usize;
let state = Arc::new((Mutex::new(LoopState::Pending), Condvar::new()));
let state_thread = state.clone();
let join = std::thread::spawn(move || {
let ctx_void = ctx_ptr as *mut c_void;
let run_loop = unsafe { CFRunLoopGetCurrent() };
let timer = unsafe {
CFRunLoopTimerCreate(
ptr::null(),
CFAbsoluteTimeGetCurrent() + KEEPALIVE_INTERVAL,
KEEPALIVE_INTERVAL,
0,
0,
noop_timer,
ptr::null_mut(),
)
};
unsafe { CFRunLoopAddTimer(run_loop, timer, kCFRunLoopDefaultMode) };
let mut src_ctx = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: ptr::null(),
release: ptr::null(),
copyDescription: ptr::null(),
equal: ptr::null(),
hash: ptr::null(),
schedule: ptr::null(),
cancel: ptr::null(),
perform: stop_perform,
};
let stop_source = unsafe { CFRunLoopSourceCreate(ptr::null(), 0, &mut src_ctx) };
unsafe { CFRunLoopAddSource(run_loop, stop_source, kCFRunLoopDefaultMode) };
let teardown = register(run_loop, ctx_void);
{
let (lock, cvar) = &*state_thread;
*lock.lock().unwrap_or_else(|e| e.into_inner()) = LoopState::Running {
run_loop: run_loop as usize,
stop_source: stop_source as usize,
};
cvar.notify_all();
}
unsafe { fire_ctx(ctx_void) };
unsafe { CFRunLoopRun() };
teardown();
unsafe {
CFRunLoopRemoveSource(run_loop, stop_source, kCFRunLoopDefaultMode);
CFRelease(stop_source);
CFRelease(timer);
};
unsafe { drop(Box::from_raw(ctx_ptr as *mut Box<dyn Fire>)) };
});
Box::new(MacRunLoopHandle {
state,
join: Some(join),
})
}