Skip to content

Commit 6754c94

Browse files
committed
Add SCDynamicStoreBuilder and support for callbacks
1 parent 29ae251 commit 6754c94

File tree

3 files changed

+182
-34
lines changed

3 files changed

+182
-34
lines changed

system-configuration-sys/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ license = "MIT/Apache-2.0"
1010
build = "build.rs"
1111

1212
[dependencies]
13-
core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", version = "0.4.4" }
13+
core-foundation-sys = "0.4.6"
1414

1515
[build-dependencies]
1616
bindgen = "0.31"

system-configuration/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repository = "https://github.com/mullvad/system-configuration-rs"
99
license = "MIT/Apache-2.0"
1010

1111
[dependencies]
12-
core-foundation = { git = "https://github.com/servo/core-foundation-rs", version = "0.4.4" }
13-
core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", version = "0.4.4" }
12+
core-foundation = "0.4.6"
13+
core-foundation-sys = "0.4.6"
1414

15-
system-configuration-sys = { path = "../system-configuration-sys" }
15+
system-configuration-sys = { path = "../system-configuration-sys", version = "0.1" }

system-configuration/src/dynamic_store.rs

Lines changed: 178 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,137 @@
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
88

9+
use core_foundation::array::{CFArray, CFArrayRef};
910
use core_foundation::base::TCFType;
1011
use core_foundation::boolean::CFBoolean;
1112
use core_foundation::dictionary::CFDictionary;
1213
use core_foundation::propertylist::{CFPropertyList, CFPropertyListSubClass};
14+
use core_foundation::runloop::CFRunLoopSource;
1315
use core_foundation::string::CFString;
1416
use core_foundation_sys::base::{CFRelease, kCFAllocatorDefault};
1517

16-
use system_configuration_sys::dynamic_store::*;
18+
pub use system_configuration_sys::dynamic_store::*;
1719

20+
use std::os::raw::c_void;
1821
use std::ptr;
1922

23+
/// Struct describing the callback happening when a watched value in the dynamic store is changed.
24+
pub struct SCDynamicStoreCallBackContext<T> {
25+
/// The callback function that will be called when a watched value in the dynamic store is
26+
/// changed.
27+
pub callout: SCDynamicStoreCallBackT<T>,
28+
29+
/// The argument passed to each `callout` call. Can be used to keep state between
30+
/// callbacks.
31+
pub info: T,
32+
}
33+
34+
/// Signature for callback functions getting called when a watched value in the dynamic store is
35+
/// changed.
36+
///
37+
/// This is the safe callback definition, abstracting over the lower level `SCDynamicStoreCallBack`
38+
/// from the `system-configuration-sys` crate.
39+
pub type SCDynamicStoreCallBackT<T> = fn(
40+
store: SCDynamicStore,
41+
changed_keys: CFArray<CFString>,
42+
info: &mut T,
43+
);
44+
45+
/// Builder for [`SCDynamicStore`] sessions.
46+
///
47+
/// [`SCDynamicStore`]: struct.SCDynamicStore.html
48+
pub struct SCDynamicStoreBuilder<T> {
49+
name: CFString,
50+
session_keys: bool,
51+
callback_context: Option<SCDynamicStoreCallBackContext<T>>,
52+
}
53+
54+
impl SCDynamicStoreBuilder<()> {
55+
/// Creates a new builder. `name` is used as the name parameter when creating the
56+
/// [`SCDynamicStore`] session.
57+
///
58+
/// [`SCDynamicStore`]: struct.SCDynamicStore.html
59+
pub fn new<S: Into<CFString>>(name: S) -> Self {
60+
SCDynamicStoreBuilder {
61+
name: name.into(),
62+
session_keys: false,
63+
callback_context: None,
64+
}
65+
}
66+
}
67+
68+
impl<T> SCDynamicStoreBuilder<T> {
69+
/// Set wether or not the created [`SCDynamicStore`] should have session keys or not.
70+
/// See [`SCDynamicStoreCreateWithOptions`] for details.
71+
///
72+
/// Defaults to `false`.
73+
///
74+
/// [`SCDynamicStore`]: struct.SCDynamicStore.html
75+
/// [`SCDynamicStoreCreateWithOptions`]: https://developer.apple.com/documentation/systemconfiguration/1437818-scdynamicstorecreatewithoptions?language=objc
76+
pub fn session_keys(mut self, session_keys: bool) -> Self {
77+
self.session_keys = session_keys;
78+
self
79+
}
80+
81+
/// Set a callback context (callback function and data to pass to each callback call).
82+
///
83+
/// Defaults to having callbacks disabled.
84+
pub fn callback_context<T2>(
85+
self,
86+
callback_context: SCDynamicStoreCallBackContext<T2>,
87+
) -> SCDynamicStoreBuilder<T2> {
88+
SCDynamicStoreBuilder {
89+
name: self.name,
90+
session_keys: self.session_keys,
91+
callback_context: Some(callback_context),
92+
}
93+
}
94+
95+
/// Create the dynamic store session.
96+
pub fn build(mut self) -> SCDynamicStore {
97+
let store_options = self.create_store_options();
98+
if let Some(callback_context) = self.callback_context.take() {
99+
SCDynamicStore::create(
100+
&self.name,
101+
&store_options,
102+
Some(convert_callback::<T>),
103+
&mut self.create_context(callback_context),
104+
)
105+
} else {
106+
SCDynamicStore::create(&self.name, &store_options, None, ptr::null_mut())
107+
}
108+
}
109+
110+
fn create_store_options(&self) -> CFDictionary {
111+
let key = unsafe { CFString::wrap_under_create_rule(kSCDynamicStoreUseSessionKeys) };
112+
let value = CFBoolean::from(self.session_keys);
113+
CFDictionary::from_CFType_pairs(&[(key, value)])
114+
}
115+
116+
fn create_context(
117+
&self,
118+
callback_context: SCDynamicStoreCallBackContext<T>,
119+
) -> SCDynamicStoreContext {
120+
// move the callback context struct to the heap and "forget" it.
121+
// It will later be brought back into the Rust typesystem and freed in
122+
// `release_callback_context`
123+
let info_ptr = Box::into_raw(Box::new(callback_context));
124+
125+
SCDynamicStoreContext {
126+
version: 0,
127+
info: info_ptr as *mut _ as *mut c_void,
128+
retain: None,
129+
release: Some(release_callback_context::<T>),
130+
copyDescription: None,
131+
}
132+
}
133+
}
134+
20135
/// Access to the key-value pairs in the dynamic store of a running system.
136+
///
137+
/// Use the [`SCDynamicStoreBuilder`] to create instances of this.
138+
///
139+
/// [`SCDynamicStoreBuilder`]: struct.SCDynamicStoreBuilder.html
21140
pub struct SCDynamicStore(SCDynamicStoreRef);
22141

23142
impl Drop for SCDynamicStore {
@@ -31,39 +150,19 @@ impl_TCFType!(SCDynamicStore, SCDynamicStoreRef, SCDynamicStoreGetTypeID);
31150
impl SCDynamicStore {
32151
/// Creates a new session used to interact with the dynamic store maintained by the System
33152
/// Configuration server.
34-
pub fn create<S: Into<CFString>>(name: S) -> Self {
35-
let cf_name = name.into();
153+
fn create(
154+
name: &CFString,
155+
store_options: &CFDictionary,
156+
callout: SCDynamicStoreCallBack,
157+
context: *mut SCDynamicStoreContext,
158+
) -> Self {
36159
unsafe {
37-
let store = SCDynamicStoreCreate(
38-
kCFAllocatorDefault,
39-
cf_name.as_concrete_TypeRef(),
40-
None,
41-
ptr::null_mut(),
42-
);
43-
SCDynamicStore::wrap_under_create_rule(store)
44-
}
45-
}
46-
47-
/// Creates a new session used to interact with the dynamic store maintained by the System
48-
/// Configuration server. Uses [`SCDynamicStoreCreateWithOptions`] underneath and sets
49-
/// `kSCDynamicStoreUseSessionKeys` to true.
50-
///
51-
/// [`SCDynamicStoreCreateWithOptions`]: https://developer.apple.com/documentation/systemconfiguration/1437818-scdynamicstorecreatewithoptions?language=objc
52-
pub fn create_with_session_keys<S: Into<CFString>>(name: S) -> Self {
53-
let cf_name = name.into();
54-
unsafe {
55-
let store_options = CFDictionary::from_CFType_pairs(&[
56-
(
57-
CFString::wrap_under_create_rule(kSCDynamicStoreUseSessionKeys),
58-
CFBoolean::true_value(),
59-
),
60-
]);
61160
let store = SCDynamicStoreCreateWithOptions(
62161
kCFAllocatorDefault,
63-
cf_name.as_concrete_TypeRef(),
162+
name.as_concrete_TypeRef(),
64163
store_options.as_concrete_TypeRef(),
65-
None,
66-
ptr::null_mut(),
164+
callout,
165+
context,
67166
);
68167
SCDynamicStore::wrap_under_create_rule(store)
69168
}
@@ -108,4 +207,53 @@ impl SCDynamicStore {
108207
};
109208
success != 0
110209
}
210+
211+
/// Specifies a set of keys and key patterns that should be monitored for changes.
212+
pub fn set_notification_keys<T1, T2>(
213+
&self,
214+
keys: &CFArray<T1>,
215+
patterns: &CFArray<T2>,
216+
) -> bool {
217+
let success = unsafe {
218+
SCDynamicStoreSetNotificationKeys(
219+
self.as_concrete_TypeRef(),
220+
keys.as_concrete_TypeRef(),
221+
patterns.as_concrete_TypeRef(),
222+
)
223+
};
224+
success != 0
225+
}
226+
227+
/// Creates a run loop source object that can be added to the application's run loop.
228+
pub fn create_run_loop_source(&self) -> CFRunLoopSource {
229+
unsafe {
230+
let run_loop_source_ref = SCDynamicStoreCreateRunLoopSource(
231+
kCFAllocatorDefault,
232+
self.as_concrete_TypeRef(),
233+
0,
234+
);
235+
CFRunLoopSource::wrap_under_create_rule(run_loop_source_ref)
236+
}
237+
}
238+
}
239+
240+
241+
/// The raw callback used by the safe `SCDynamicStore` to convert from the `SCDynamicStoreCallBack`
242+
/// to the `SCDynamicStoreCallBackT`
243+
unsafe extern "C" fn convert_callback<T>(
244+
store_ref: SCDynamicStoreRef,
245+
changed_keys_ref: CFArrayRef,
246+
context_ptr: *mut c_void,
247+
) {
248+
let store = SCDynamicStore::wrap_under_get_rule(store_ref);
249+
let changed_keys = CFArray::<CFString>::wrap_under_get_rule(changed_keys_ref);
250+
let context = &mut *(context_ptr as *mut _ as *mut SCDynamicStoreCallBackContext<T>);
251+
252+
(context.callout)(store, changed_keys, &mut context.info);
253+
}
254+
255+
// Release function called by core foundation on release of the dynamic store context.
256+
unsafe extern "C" fn release_callback_context<T>(context_ptr: *const c_void) {
257+
// Bring back the context object from raw ptr so it is correctly freed.
258+
let _context = Box::from_raw(context_ptr as *mut SCDynamicStoreCallBackContext<T>);
111259
}

0 commit comments

Comments
 (0)