Skip to main content

systemconfiguration/
preferences.rs

1use std::{
2    ffi::c_void,
3    ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign},
4    ptr::NonNull,
5    sync::{Arc, Mutex},
6};
7
8use crate::{bridge, error::Result, ffi, network_services::NetworkService, PropertyList};
9
10struct CallbackState {
11    callback: Box<dyn FnMut(PreferencesNotification) + Send>,
12}
13
14unsafe extern "C" fn preferences_callback(notification_type: u32, info: *mut c_void) {
15    if info.is_null() {
16        return;
17    }
18
19    let mutex = unsafe { &*info.cast::<Mutex<CallbackState>>() };
20    if let Ok(mut state) = mutex.lock() {
21        (state.callback)(PreferencesNotification::from_raw(notification_type));
22    }
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct PreferencesNotification(u32);
27
28impl PreferencesNotification {
29    pub const COMMIT: Self = Self(1 << 0);
30    pub const APPLY: Self = Self(1 << 1);
31
32    pub const fn from_raw(raw: u32) -> Self {
33        Self(raw)
34    }
35
36    pub const fn raw_value(self) -> u32 {
37        self.0
38    }
39
40    pub const fn contains(self, other: Self) -> bool {
41        self.0 & other.0 == other.0
42    }
43}
44
45impl BitOr for PreferencesNotification {
46    type Output = Self;
47
48    fn bitor(self, rhs: Self) -> Self::Output {
49        Self(self.0 | rhs.0)
50    }
51}
52
53impl BitOrAssign for PreferencesNotification {
54    fn bitor_assign(&mut self, rhs: Self) {
55        self.0 |= rhs.0;
56    }
57}
58
59impl BitAnd for PreferencesNotification {
60    type Output = Self;
61
62    fn bitand(self, rhs: Self) -> Self::Output {
63        Self(self.0 & rhs.0)
64    }
65}
66
67impl BitAndAssign for PreferencesNotification {
68    fn bitand_assign(&mut self, rhs: Self) {
69        self.0 &= rhs.0;
70    }
71}
72
73#[derive(Clone)]
74pub struct Preferences {
75    raw: bridge::OwnedHandle,
76    callback_state: Option<Arc<Mutex<CallbackState>>>,
77}
78
79impl std::fmt::Debug for Preferences {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.debug_struct("Preferences").finish_non_exhaustive()
82    }
83}
84
85impl Preferences {
86    pub fn type_id() -> u64 {
87        unsafe { ffi::preferences::sc_preferences_get_type_id() }
88    }
89
90    pub fn new(name: &str, prefs_id: Option<&str>) -> Result<Self> {
91        Self::create(name, prefs_id)
92    }
93
94    pub fn new_with_authorization(name: &str, prefs_id: Option<&str>) -> Result<Self> {
95        unsafe { Self::create_with_authorization(name, prefs_id, None) }
96    }
97
98    /// # Safety
99    ///
100    /// `authorization` must be a valid `AuthorizationRef` obtained from a
101    /// compatible Security-framework binding, and it must remain valid for the
102    /// lifetime requirements imposed by `SCPreferences`.
103    pub unsafe fn new_with_authorization_raw(
104        name: &str,
105        prefs_id: Option<&str>,
106        authorization: Option<NonNull<c_void>>,
107    ) -> Result<Self> {
108        unsafe { Self::create_with_authorization(name, prefs_id, authorization) }
109    }
110
111    pub fn new_with_callback<F>(name: &str, prefs_id: Option<&str>, callback: F) -> Result<Self>
112    where
113        F: FnMut(PreferencesNotification) + Send + 'static,
114    {
115        let mut preferences = Self::new(name, prefs_id)?;
116        preferences.set_callback(callback)?;
117        Ok(preferences)
118    }
119
120    pub fn new_with_authorization_and_callback<F>(
121        name: &str,
122        prefs_id: Option<&str>,
123        callback: F,
124    ) -> Result<Self>
125    where
126        F: FnMut(PreferencesNotification) + Send + 'static,
127    {
128        let mut preferences = Self::new_with_authorization(name, prefs_id)?;
129        preferences.set_callback(callback)?;
130        Ok(preferences)
131    }
132
133    /// # Safety
134    ///
135    /// `authorization` must be a valid `AuthorizationRef` obtained from a
136    /// compatible Security-framework binding, and it must remain valid for the
137    /// lifetime requirements imposed by `SCPreferences`.
138    pub unsafe fn new_with_authorization_raw_and_callback<F>(
139        name: &str,
140        prefs_id: Option<&str>,
141        authorization: Option<NonNull<c_void>>,
142        callback: F,
143    ) -> Result<Self>
144    where
145        F: FnMut(PreferencesNotification) + Send + 'static,
146    {
147        let mut preferences =
148            unsafe { Self::create_with_authorization(name, prefs_id, authorization) }?;
149        preferences.set_callback(callback)?;
150        Ok(preferences)
151    }
152
153    fn create(name: &str, prefs_id: Option<&str>) -> Result<Self> {
154        let name = bridge::cstring(name, "sc_preferences_create")?;
155        let prefs_id = bridge::optional_cstring(prefs_id, "sc_preferences_create")?;
156        let raw = unsafe {
157            ffi::preferences::sc_preferences_create(
158                name.as_ptr(),
159                prefs_id
160                    .as_ref()
161                    .map_or(std::ptr::null(), |value| value.as_ptr()),
162            )
163        };
164        let raw = bridge::owned_handle_or_last("sc_preferences_create", raw)?;
165        Ok(Self {
166            raw,
167            callback_state: None,
168        })
169    }
170
171    unsafe fn create_with_authorization(
172        name: &str,
173        prefs_id: Option<&str>,
174        authorization: Option<NonNull<c_void>>,
175    ) -> Result<Self> {
176        let name = bridge::cstring(name, "sc_preferences_create_with_authorization")?;
177        let prefs_id =
178            bridge::optional_cstring(prefs_id, "sc_preferences_create_with_authorization")?;
179        let raw = unsafe {
180            ffi::preferences::sc_preferences_create_with_authorization(
181                name.as_ptr(),
182                prefs_id
183                    .as_ref()
184                    .map_or(std::ptr::null(), |value| value.as_ptr()),
185                authorization.map_or(std::ptr::null_mut(), NonNull::as_ptr),
186            )
187        };
188        let raw = bridge::owned_handle_or_last("sc_preferences_create_with_authorization", raw)?;
189        Ok(Self {
190            raw,
191            callback_state: None,
192        })
193    }
194
195    pub fn set_callback<F>(&mut self, callback: F) -> Result<()>
196    where
197        F: FnMut(PreferencesNotification) + Send + 'static,
198    {
199        let state = Arc::new(Mutex::new(CallbackState {
200            callback: Box::new(callback),
201        }));
202        self.set_callback_state(Some(state))
203    }
204
205    pub fn clear_callback(&mut self) -> Result<()> {
206        self.set_callback_state(None)
207    }
208
209    fn set_callback_state(&mut self, callback: Option<Arc<Mutex<CallbackState>>>) -> Result<()> {
210        let ok = unsafe {
211            ffi::preferences::sc_preferences_set_callback(
212                self.raw.as_ptr(),
213                callback
214                    .as_ref()
215                    .map(|_| preferences_callback as unsafe extern "C" fn(u32, *mut c_void)),
216                callback.as_ref().map_or(std::ptr::null_mut(), |state| {
217                    Arc::as_ptr(state).cast_mut().cast::<c_void>()
218                }),
219            )
220        };
221        bridge::bool_result("sc_preferences_set_callback", ok)?;
222        self.callback_state = callback;
223        Ok(())
224    }
225
226    pub fn schedule_with_run_loop_current(&self) -> Result<()> {
227        let ok = unsafe {
228            ffi::preferences::sc_preferences_schedule_with_run_loop_current(self.raw.as_ptr())
229        };
230        bridge::bool_result("sc_preferences_schedule_with_run_loop_current", ok)
231    }
232
233    pub fn unschedule_from_run_loop_current(&self) -> Result<()> {
234        let ok = unsafe {
235            ffi::preferences::sc_preferences_unschedule_from_run_loop_current(self.raw.as_ptr())
236        };
237        bridge::bool_result("sc_preferences_unschedule_from_run_loop_current", ok)
238    }
239
240    pub fn set_dispatch_queue_global(&self) -> Result<()> {
241        let ok = unsafe {
242            ffi::preferences::sc_preferences_set_dispatch_queue_global(self.raw.as_ptr())
243        };
244        bridge::bool_result("sc_preferences_set_dispatch_queue_global", ok)
245    }
246
247    pub fn clear_dispatch_queue(&self) -> Result<()> {
248        let ok =
249            unsafe { ffi::preferences::sc_preferences_clear_dispatch_queue(self.raw.as_ptr()) };
250        bridge::bool_result("sc_preferences_clear_dispatch_queue", ok)
251    }
252
253    pub fn lock(&self, wait: bool) -> Result<()> {
254        let ok =
255            unsafe { ffi::preferences::sc_preferences_lock(self.raw.as_ptr(), u8::from(wait)) };
256        bridge::bool_result("sc_preferences_lock", ok)
257    }
258
259    pub fn commit_changes(&self) -> Result<()> {
260        let ok = unsafe { ffi::preferences::sc_preferences_commit_changes(self.raw.as_ptr()) };
261        bridge::bool_result("sc_preferences_commit_changes", ok)
262    }
263
264    pub fn apply_changes(&self) -> Result<()> {
265        let ok = unsafe { ffi::preferences::sc_preferences_apply_changes(self.raw.as_ptr()) };
266        bridge::bool_result("sc_preferences_apply_changes", ok)
267    }
268
269    pub fn unlock(&self) -> Result<()> {
270        let ok = unsafe { ffi::preferences::sc_preferences_unlock(self.raw.as_ptr()) };
271        bridge::bool_result("sc_preferences_unlock", ok)
272    }
273
274    pub fn synchronize(&self) {
275        unsafe { ffi::preferences::sc_preferences_synchronize(self.raw.as_ptr()) };
276    }
277
278    pub fn signature(&self) -> Option<String> {
279        bridge::take_optional_string(unsafe {
280            ffi::preferences::sc_preferences_copy_signature(self.raw.as_ptr())
281        })
282    }
283
284    pub fn copy_key_list(&self) -> Vec<String> {
285        bridge::take_string_array(unsafe {
286            ffi::preferences::sc_preferences_copy_key_list(self.raw.as_ptr())
287        })
288    }
289
290    pub fn get_value(&self, key: &str) -> Result<Option<PropertyList>> {
291        let key = bridge::cstring(key, "sc_preferences_get_value")?;
292        let raw =
293            unsafe { ffi::preferences::sc_preferences_get_value(self.raw.as_ptr(), key.as_ptr()) };
294        Ok(unsafe { bridge::OwnedHandle::from_raw(raw) }.map(PropertyList::from_owned_handle))
295    }
296
297    pub fn add_value(&self, key: &str, value: &PropertyList) -> Result<()> {
298        let key = bridge::cstring(key, "sc_preferences_add_value")?;
299        let ok = unsafe {
300            ffi::preferences::sc_preferences_add_value(
301                self.raw.as_ptr(),
302                key.as_ptr(),
303                value.as_ptr(),
304            )
305        };
306        bridge::bool_result("sc_preferences_add_value", ok)
307    }
308
309    pub fn set_value(&self, key: &str, value: &PropertyList) -> Result<()> {
310        let key = bridge::cstring(key, "sc_preferences_set_value")?;
311        let ok = unsafe {
312            ffi::preferences::sc_preferences_set_value(
313                self.raw.as_ptr(),
314                key.as_ptr(),
315                value.as_ptr(),
316            )
317        };
318        bridge::bool_result("sc_preferences_set_value", ok)
319    }
320
321    pub fn remove_value(&self, key: &str) -> Result<()> {
322        let key = bridge::cstring(key, "sc_preferences_remove_value")?;
323        let ok = unsafe {
324            ffi::preferences::sc_preferences_remove_value(self.raw.as_ptr(), key.as_ptr())
325        };
326        bridge::bool_result("sc_preferences_remove_value", ok)
327    }
328
329    pub fn path_create_unique_child(&self, prefix: &str) -> Result<Option<String>> {
330        let prefix = bridge::cstring(prefix, "sc_preferences_path_create_unique_child")?;
331        Ok(bridge::take_optional_string(unsafe {
332            ffi::preferences::sc_preferences_path_create_unique_child(
333                self.raw.as_ptr(),
334                prefix.as_ptr(),
335            )
336        }))
337    }
338
339    pub fn path_get_value(&self, path: &str) -> Result<Option<PropertyList>> {
340        let path = bridge::cstring(path, "sc_preferences_path_get_value")?;
341        let raw = unsafe {
342            ffi::preferences::sc_preferences_path_get_value(self.raw.as_ptr(), path.as_ptr())
343        };
344        Ok(unsafe { bridge::OwnedHandle::from_raw(raw) }.map(PropertyList::from_owned_handle))
345    }
346
347    pub fn path_get_link(&self, path: &str) -> Result<Option<String>> {
348        let path = bridge::cstring(path, "sc_preferences_path_get_link")?;
349        Ok(bridge::take_optional_string(unsafe {
350            ffi::preferences::sc_preferences_path_get_link(self.raw.as_ptr(), path.as_ptr())
351        }))
352    }
353
354    pub fn path_set_value(&self, path: &str, value: &PropertyList) -> Result<()> {
355        let path = bridge::cstring(path, "sc_preferences_path_set_value")?;
356        let ok = unsafe {
357            ffi::preferences::sc_preferences_path_set_value(
358                self.raw.as_ptr(),
359                path.as_ptr(),
360                value.as_ptr(),
361            )
362        };
363        bridge::bool_result("sc_preferences_path_set_value", ok)
364    }
365
366    pub fn path_set_link(&self, path: &str, link: &str) -> Result<()> {
367        let path = bridge::cstring(path, "sc_preferences_path_set_link")?;
368        let link = bridge::cstring(link, "sc_preferences_path_set_link")?;
369        let ok = unsafe {
370            ffi::preferences::sc_preferences_path_set_link(
371                self.raw.as_ptr(),
372                path.as_ptr(),
373                link.as_ptr(),
374            )
375        };
376        bridge::bool_result("sc_preferences_path_set_link", ok)
377    }
378
379    pub fn path_remove_value(&self, path: &str) -> Result<()> {
380        let path = bridge::cstring(path, "sc_preferences_path_remove_value")?;
381        let ok = unsafe {
382            ffi::preferences::sc_preferences_path_remove_value(self.raw.as_ptr(), path.as_ptr())
383        };
384        bridge::bool_result("sc_preferences_path_remove_value", ok)
385    }
386
387    pub fn set_computer_name(&self, name: Option<&str>) -> Result<()> {
388        let name = bridge::optional_cstring(name, "sc_preferences_set_computer_name")?;
389        let ok = unsafe {
390            ffi::preferences::sc_preferences_set_computer_name(
391                self.raw.as_ptr(),
392                name.as_ref()
393                    .map_or(std::ptr::null(), |value| value.as_ptr()),
394            )
395        };
396        bridge::bool_result("sc_preferences_set_computer_name", ok)
397    }
398
399    pub fn set_local_host_name(&self, name: Option<&str>) -> Result<()> {
400        let name = bridge::optional_cstring(name, "sc_preferences_set_local_host_name")?;
401        let ok = unsafe {
402            ffi::preferences::sc_preferences_set_local_host_name(
403                self.raw.as_ptr(),
404                name.as_ref()
405                    .map_or(std::ptr::null(), |value| value.as_ptr()),
406            )
407        };
408        bridge::bool_result("sc_preferences_set_local_host_name", ok)
409    }
410
411    pub fn network_services(&self) -> Vec<NetworkService> {
412        NetworkService::copy_all(self)
413    }
414
415    pub(crate) fn as_ptr(&self) -> bridge::RawHandle {
416        self.raw.as_ptr()
417    }
418}