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};
pub struct SCDynamicStoreCallBackContext<T> {
pub callout: SCDynamicStoreCallBackT<T>,
pub info: T,
}
pub type SCDynamicStoreCallBackT<T> =
fn(store: SCDynamicStore, changed_keys: CFArray<CFString>, info: &mut T);
pub struct SCDynamicStoreBuilder<T> {
name: CFString,
session_keys: bool,
callback_context: Option<SCDynamicStoreCallBackContext<T>>,
}
impl SCDynamicStoreBuilder<()> {
pub fn new<S: Into<CFString>>(name: S) -> Self {
SCDynamicStoreBuilder {
name: name.into(),
session_keys: false,
callback_context: None,
}
}
}
impl<T> SCDynamicStoreBuilder<T> {
pub fn session_keys(mut self, session_keys: bool) -> Self {
self.session_keys = session_keys;
self
}
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),
}
}
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 {
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! {
SCDynamicStore, SCDynamicStoreRef
}
impl_TCFType!(SCDynamicStore, SCDynamicStoreRef, SCDynamicStoreGetTypeID);
impl SCDynamicStore {
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)
}
}
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
}
}
}
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
}
}
}
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
}
}
}
pub fn set<S: Into<CFString>, V: CFPropertyListSubClass>(&self, key: S, value: V) -> bool {
self.set_raw(key, &value.into_CFPropertyList())
}
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
}
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
}
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
}
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)
}
}
}
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);
}
unsafe extern "C" fn release_callback_context<T>(context_ptr: *const c_void) {
let _context = Box::from_raw(context_ptr as *mut SCDynamicStoreCallBackContext<T>);
}