X-Git-Url: https://code.octet-stream.net/netwatcher/blobdiff_plain/1c34fe3947aaf8af2d773d59bdebf19e17d78527..473c9605820f4531f9d40823338fa4bf8718dd6f:/src/watch_mac.rs diff --git a/src/watch_mac.rs b/src/watch_mac.rs index 547db48..42e7217 100644 --- a/src/watch_mac.rs +++ b/src/watch_mac.rs @@ -1,10 +1,89 @@ -use crate::Update; - -pub struct WatchHandle; - -pub fn watch_interfaces(callback: F) -> WatchHandle { - // stop current worker thread - // post this into a thread that will use it - drop(callback); - WatchHandle -} +use std::sync::Mutex; + +use block2::{Block, RcBlock}; +use objc2::Encoding; + +use crate::{Error, List, Update}; + +// The "objc2" project aims to provide bindings for all frameworks but Network.framework +// isn't ready yet so let's kick it old-school + +#[repr(C)] +struct NwPathMonitor([u8; 0]); +type NwPathMonitorT = *mut NwPathMonitor; +#[repr(C)] +struct NwPath([u8; 0]); +#[repr(C)] +struct DispatchQueue([u8; 0]); +type DispatchQueueT = *mut DispatchQueue; +const QOS_CLASS_BACKGROUND: usize = 0x09; + +unsafe impl objc2::Encode for NwPath { + const ENCODING: Encoding = usize::ENCODING; +} + +#[link(name = "Network", kind = "framework")] +extern "C" { + fn nw_path_monitor_create() -> NwPathMonitorT; + fn nw_path_monitor_set_update_handler( + monitor: NwPathMonitorT, + update_handler: &Block, + ); + fn nw_path_monitor_set_queue(monitor: NwPathMonitorT, queue: DispatchQueueT); + fn nw_path_monitor_start(monitor: NwPathMonitorT); + fn nw_path_monitor_cancel(monitor: NwPathMonitorT); + + fn dispatch_get_global_queue(identifier: usize, flag: usize) -> DispatchQueueT; +} + +pub(crate) struct WatchHandle { + path_monitor: NwPathMonitorT, +} + +impl Drop for WatchHandle { + fn drop(&mut self) { + unsafe { + nw_path_monitor_cancel(self.path_monitor); + } + } +} + +struct CallbackState { + prev_list: List, + callback: Box, +} + +pub(crate) fn watch_interfaces( + callback: F, +) -> Result { + let state = CallbackState { + prev_list: List::default(), + callback: Box::new(callback), + }; + // Blocks are Fn, not FnMut + let state = Mutex::new(state); + let block = RcBlock::new(move |_: NwPath| { + let mut state = state.lock().unwrap(); + let Ok(new_list) = crate::list::list_interfaces() else { + return; + }; + if new_list == state.prev_list { + return; + } + let update = Update { + interfaces: new_list.0.clone(), + diff: new_list.diff_from(&state.prev_list), + }; + (state.callback)(update); + state.prev_list = new_list; + }); + let path_monitor: NwPathMonitorT; + unsafe { + let queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0); + path_monitor = nw_path_monitor_create(); + nw_path_monitor_set_update_handler(path_monitor, &block); + nw_path_monitor_set_queue(path_monitor, queue); + nw_path_monitor_start(path_monitor); + } + Ok(WatchHandle { path_monitor }) +}