blob: 444277a6abe29067fca3f3dd6f94de255c4f32dd [file] [log] [blame]
// Copyright 2017 Amagicom AB.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<T> {
/// The callback function that will be called when a watched value in the dynamic store is
/// changed.
pub callout: SCDynamicStoreCallBackT<T>,
/// 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<T> =
fn(store: SCDynamicStore, changed_keys: CFArray<CFString>, info: &mut T);
/// Builder for [`SCDynamicStore`] sessions.
///
/// [`SCDynamicStore`]: struct.SCDynamicStore.html
pub struct SCDynamicStoreBuilder<T> {
name: CFString,
session_keys: bool,
callback_context: Option<SCDynamicStoreCallBackContext<T>>,
}
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<S: Into<CFString>>(name: S) -> Self {
SCDynamicStoreBuilder {
name: name.into(),
session_keys: false,
callback_context: None,
}
}
}
impl<T> SCDynamicStoreBuilder<T> {
/// 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<T2>(
self,
callback_context: SCDynamicStoreCallBackContext<T2>,
) -> SCDynamicStoreBuilder<T2> {
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::<T>),
&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<T>,
) -> 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::<T>),
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<S: Into<CFString>>(&self, pattern: S) -> Option<CFArray<CFString>> {
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<CFDictionary<CFString, CFType>> {
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<S: Into<CFString>>(&self, key: S) -> Option<CFPropertyList> {
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<S: Into<CFString>, 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<S: Into<CFString>>(&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<S: Into<CFString>>(&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<T1, T2>(
&self,
keys: &CFArray<T1>,
patterns: &CFArray<T2>,
) -> 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<T>(
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::<CFString>::wrap_under_get_rule(changed_keys_ref);
let context = &mut *(context_ptr as *mut _ as *mut SCDynamicStoreCallBackContext<T>);
(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<T>(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<T>);
}