// Copyright 2017 Amagicom AB. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Bindings to [`SCDynamicStore`]. //! //! See the examples directory for examples how to use this module. //! //! [`SCDynamicStore`]: https://developer.apple.com/documentation/systemconfiguration/scdynamicstore?language=objc use crate::sys::{ dynamic_store::{ kSCDynamicStoreUseSessionKeys, SCDynamicStoreCallBack, SCDynamicStoreContext, SCDynamicStoreCopyKeyList, SCDynamicStoreCopyValue, SCDynamicStoreCreateRunLoopSource, SCDynamicStoreCreateWithOptions, SCDynamicStoreGetTypeID, SCDynamicStoreRef, SCDynamicStoreRemoveValue, SCDynamicStoreSetNotificationKeys, SCDynamicStoreSetValue, }, dynamic_store_copy_specific::SCDynamicStoreCopyProxies, }; use core_foundation::{ array::{CFArray, CFArrayRef}, base::{kCFAllocatorDefault, CFType, TCFType}, boolean::CFBoolean, dictionary::CFDictionary, propertylist::{CFPropertyList, CFPropertyListSubClass}, runloop::CFRunLoopSource, string::CFString, }; use std::{ffi::c_void, ptr}; /// Struct describing the callback happening when a watched value in the dynamic store is changed. pub struct SCDynamicStoreCallBackContext { /// The callback function that will be called when a watched value in the dynamic store is /// changed. pub callout: SCDynamicStoreCallBackT, /// The argument passed to each `callout` call. Can be used to keep state between /// callbacks. pub info: T, } /// Signature for callback functions getting called when a watched value in the dynamic store is /// changed. /// /// This is the safe callback definition, abstracting over the lower level `SCDynamicStoreCallBack` /// from the `system-configuration-sys` crate. pub type SCDynamicStoreCallBackT = fn(store: SCDynamicStore, changed_keys: CFArray, info: &mut T); /// Builder for [`SCDynamicStore`] sessions. /// /// [`SCDynamicStore`]: struct.SCDynamicStore.html pub struct SCDynamicStoreBuilder { name: CFString, session_keys: bool, callback_context: Option>, } impl SCDynamicStoreBuilder<()> { /// Creates a new builder. `name` is used as the name parameter when creating the /// [`SCDynamicStore`] session. /// /// [`SCDynamicStore`]: struct.SCDynamicStore.html pub fn new>(name: S) -> Self { SCDynamicStoreBuilder { name: name.into(), session_keys: false, callback_context: None, } } } impl SCDynamicStoreBuilder { /// Set wether or not the created [`SCDynamicStore`] should have session keys or not. /// See [`SCDynamicStoreCreateWithOptions`] for details. /// /// Defaults to `false`. /// /// [`SCDynamicStore`]: struct.SCDynamicStore.html /// [`SCDynamicStoreCreateWithOptions`]: https://developer.apple.com/documentation/systemconfiguration/1437818-scdynamicstorecreatewithoptions?language=objc pub fn session_keys(mut self, session_keys: bool) -> Self { self.session_keys = session_keys; self } /// Set a callback context (callback function and data to pass to each callback call). /// /// Defaults to having callbacks disabled. pub fn callback_context( self, callback_context: SCDynamicStoreCallBackContext, ) -> SCDynamicStoreBuilder { SCDynamicStoreBuilder { name: self.name, session_keys: self.session_keys, callback_context: Some(callback_context), } } /// Create the dynamic store session. pub fn build(mut self) -> SCDynamicStore { let store_options = self.create_store_options(); if let Some(callback_context) = self.callback_context.take() { SCDynamicStore::create( &self.name, &store_options, Some(convert_callback::), &mut self.create_context(callback_context), ) } else { SCDynamicStore::create(&self.name, &store_options, None, ptr::null_mut()) } } fn create_store_options(&self) -> CFDictionary { let key = unsafe { CFString::wrap_under_create_rule(kSCDynamicStoreUseSessionKeys) }; let value = CFBoolean::from(self.session_keys); let typed_dict = CFDictionary::from_CFType_pairs(&[(key, value)]); unsafe { CFDictionary::wrap_under_get_rule(typed_dict.as_concrete_TypeRef()) } } fn create_context( &self, callback_context: SCDynamicStoreCallBackContext, ) -> SCDynamicStoreContext { // move the callback context struct to the heap and "forget" it. // It will later be brought back into the Rust typesystem and freed in // `release_callback_context` let info_ptr = Box::into_raw(Box::new(callback_context)); SCDynamicStoreContext { version: 0, info: info_ptr as *mut _ as *mut c_void, retain: None, release: Some(release_callback_context::), copyDescription: None, } } } declare_TCFType! { /// Access to the key-value pairs in the dynamic store of a running system. /// /// Use the [`SCDynamicStoreBuilder`] to create instances of this. /// /// [`SCDynamicStoreBuilder`]: struct.SCDynamicStoreBuilder.html SCDynamicStore, SCDynamicStoreRef } impl_TCFType!(SCDynamicStore, SCDynamicStoreRef, SCDynamicStoreGetTypeID); impl SCDynamicStore { /// Creates a new session used to interact with the dynamic store maintained by the System /// Configuration server. fn create( name: &CFString, store_options: &CFDictionary, callout: SCDynamicStoreCallBack, context: *mut SCDynamicStoreContext, ) -> Self { unsafe { let store = SCDynamicStoreCreateWithOptions( kCFAllocatorDefault, name.as_concrete_TypeRef(), store_options.as_concrete_TypeRef(), callout, context, ); SCDynamicStore::wrap_under_create_rule(store) } } /// Returns the keys that represent the current dynamic store entries that match the specified /// pattern. Or `None` if an error occured. /// /// `pattern` - A regular expression pattern used to match the dynamic store keys. pub fn get_keys>(&self, pattern: S) -> Option> { let cf_pattern = pattern.into(); unsafe { let array_ref = SCDynamicStoreCopyKeyList( self.as_concrete_TypeRef(), cf_pattern.as_concrete_TypeRef(), ); if !array_ref.is_null() { Some(CFArray::wrap_under_create_rule(array_ref)) } else { None } } } /// Returns the key-value pairs that represent the current internet proxy settings. Or `None` if /// no proxy settings have been defined or if an error occured. pub fn get_proxies(&self) -> Option> { unsafe { let dictionary_ref = SCDynamicStoreCopyProxies(self.as_concrete_TypeRef()); if !dictionary_ref.is_null() { Some(CFDictionary::wrap_under_create_rule(dictionary_ref)) } else { None } } } /// If the given key exists in the store, the associated value is returned. /// /// Use `CFPropertyList::downcast_into` to cast the result into the correct type. pub fn get>(&self, key: S) -> Option { let cf_key = key.into(); unsafe { let dict_ref = SCDynamicStoreCopyValue(self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef()); if !dict_ref.is_null() { Some(CFPropertyList::wrap_under_create_rule(dict_ref)) } else { None } } } /// Sets the value of the given key. Overwrites existing values. /// Returns `true` on success, false on failure. pub fn set, V: CFPropertyListSubClass>(&self, key: S, value: V) -> bool { self.set_raw(key, &value.into_CFPropertyList()) } /// Sets the value of the given key. Overwrites existing values. /// Returns `true` on success, false on failure. pub fn set_raw>(&self, key: S, value: &CFPropertyList) -> bool { let cf_key = key.into(); let success = unsafe { SCDynamicStoreSetValue( self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef(), value.as_concrete_TypeRef(), ) }; success != 0 } /// Removes the value of the specified key from the dynamic store. pub fn remove>(&self, key: S) -> bool { let cf_key = key.into(); let success = unsafe { SCDynamicStoreRemoveValue(self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef()) }; success != 0 } /// Specifies a set of keys and key patterns that should be monitored for changes. pub fn set_notification_keys( &self, keys: &CFArray, patterns: &CFArray, ) -> bool { let success = unsafe { SCDynamicStoreSetNotificationKeys( self.as_concrete_TypeRef(), keys.as_concrete_TypeRef(), patterns.as_concrete_TypeRef(), ) }; success != 0 } /// Creates a run loop source object that can be added to the application's run loop. pub fn create_run_loop_source(&self) -> CFRunLoopSource { unsafe { let run_loop_source_ref = SCDynamicStoreCreateRunLoopSource( kCFAllocatorDefault, self.as_concrete_TypeRef(), 0, ); CFRunLoopSource::wrap_under_create_rule(run_loop_source_ref) } } } /// The raw callback used by the safe `SCDynamicStore` to convert from the `SCDynamicStoreCallBack` /// to the `SCDynamicStoreCallBackT` unsafe extern "C" fn convert_callback( store_ref: SCDynamicStoreRef, changed_keys_ref: CFArrayRef, context_ptr: *mut c_void, ) { let store = SCDynamicStore::wrap_under_get_rule(store_ref); let changed_keys = CFArray::::wrap_under_get_rule(changed_keys_ref); let context = &mut *(context_ptr as *mut _ as *mut SCDynamicStoreCallBackContext); (context.callout)(store, changed_keys, &mut context.info); } // Release function called by core foundation on release of the dynamic store context. unsafe extern "C" fn release_callback_context(context_ptr: *const c_void) { // Bring back the context object from raw ptr so it is correctly freed. let _context = Box::from_raw(context_ptr as *mut SCDynamicStoreCallBackContext); }