Skip to main content

systemconfiguration/
preferences.rs

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