use core_foundation::{ array::CFArray, base::{CFType, TCFType, ToVoid}, dictionary::CFDictionary, propertylist::CFPropertyList, runloop::{kCFRunLoopCommonModes, CFRunLoop}, string::CFString, }; use system_configuration::{ dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext}, sys::schema_definitions::kSCPropNetDNSServerAddresses, }; // This example will watch the dynamic store for changes to any DNS setting. As soon as a change // is detected, it will be printed to stdout. fn main() { let callback_context = SCDynamicStoreCallBackContext { callout: my_callback, info: Context { call_count: 0 }, }; let store = SCDynamicStoreBuilder::new("my-watch-dns-store") .callback_context(callback_context) .build(); let watch_keys: CFArray = CFArray::from_CFTypes(&[]); let watch_patterns = CFArray::from_CFTypes(&[CFString::from("(State|Setup):/Network/Service/.*/DNS")]); if store.set_notification_keys(&watch_keys, &watch_patterns) { println!("Registered for notifications"); } else { panic!("Unable to register notifications"); } let run_loop_source = store.create_run_loop_source(); let run_loop = CFRunLoop::get_current(); run_loop.add_source(&run_loop_source, unsafe { kCFRunLoopCommonModes }); println!("Entering run loop"); CFRunLoop::run_current(); } /// This struct acts as a user provided context/payload to each notification callback. /// Here one can store any type of data or state needed in the callback function. #[derive(Debug)] struct Context { call_count: u64, } #[allow(clippy::needless_pass_by_value)] fn my_callback(store: SCDynamicStore, changed_keys: CFArray, context: &mut Context) { context.call_count += 1; println!("Callback call count: {}", context.call_count); for key in changed_keys.iter() { if let Some(addresses) = get_dns(&store, key.clone()) { println!("{} changed DNS to {:?}", *key, addresses); } else { println!("{} removed DNS", *key); } } } fn get_dns(store: &SCDynamicStore, path: CFString) -> Option> { let dns_settings = store .get(path) .and_then(CFPropertyList::downcast_into::)?; let address_array = dns_settings .find(unsafe { kSCPropNetDNSServerAddresses }.to_void()) .map(|ptr| unsafe { CFType::wrap_under_get_rule(*ptr) }) .and_then(CFType::downcast_into::)?; let mut result = Vec::with_capacity(address_array.len() as usize); for address_ptr in &address_array { let address = unsafe { CFType::wrap_under_get_rule(*address_ptr) }.downcast_into::()?; result.push(address.to_string()) } Some(result) }