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