cidre 0.11.6

Apple frameworks bindings for rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
use std::{ffi::c_void, mem::transmute};

use crate::{arc, cf};

use super::base::{Device, Error, Notification};

///
/// The interface connection type.  Pass ONE and ONLY ONE of these to AMDeviceNotificationSubscribe(WithOptions).  Not a bitfield (unfortunately).
///
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[repr(i32)]
pub enum IfaceConnectionType {
    Invalid = -1,
    Any = 0,
    /// e.g. USB, Firewire, Bluetooth pairing
    Direct = 1,
    /// e.g. Ethernet, 802.11g or other network
    Inderect = 2,
    /// This can be returned from AMDeviceGetInterfaceType, but should not be passed to AMDeviceNotificationSubscribe(WithOptions).
    /// e.g. Connection to this device is proxied through a paired companion device
    Proxied = 3,
}

/// Various interface connection speeds
/// in kilobits per second.
#[repr(transparent)]
pub struct Speed(pub i32);

impl Speed {
    pub const ANY: Self = Self(0);
    pub const USB_LOW_SPEED: Self = Self(3 * 512);
    pub const USB_FULL_SPEED: Self = Self(12 * 1024);
    pub const USB_HIGH_SPEED: Self = Self(480 * 1024);
    pub const FIREWIRE_400: Self = Self(400 * 1024);
    pub const FIREWIRE_800: Self = Self(800 * 1024);
    pub const _10_BASE_T: Self = Self(10 * 1024);
    pub const _100_BASE_T: Self = Self(100 * 1024);
    pub const GIGABIT: Self = Self(1024 * 1024);
    pub const _80211B: Self = Self(11 * 1024);
    pub const _80211G: Self = Self(54 * 1024);
    pub const _80211N: Self = Self(540 * 1024);
    pub const BLUETOOTH1: Self = Self(1024);
    pub const BLUETOOTH2: Self = Self(21 * 1024);
}

/*
 * Device Action
 */
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[repr(i32)]
pub enum Action {
    /// A device has attached. The device reference belongs to the
    /// client. It must be explicitly released, or else it will leak.
    Attached = 1,
    /// A device has detached. The device object delivered will be
    /// the same as the one delivered in the Attached notification. This
    /// device reference does not need to be released.
    Detached = 2,

    /// This notification is delivered in response to
    ///
    ///   1. A call to am::DeviceNotificationUnsubscribe().
    ///   2. An error occurred on one of the underlying notification systems
    ///     (i.e. usbmuxd or mDNSResponder crashed or stopped responding).
    ///     Unsubcribing and resubscribing may recover the notification system.
    NotificationStopped = 3,

    Paired = 4,
}

#[repr(C)]
pub struct NotificationInfo {
    pub device: *const Device, // test for strong
    pub action: Action,
    pub notification: &'static Notification,
}

pub enum SafeInfo<'a> {
    Attached(arc::R<Device>),
    Detached(&'a Device),
    NotificationStopped,
    Paired(&'a Device),
}

impl NotificationInfo {
    pub fn safe<'a>(&self) -> SafeInfo<'a> {
        match self.action {
            Action::Attached => SafeInfo::Attached(unsafe { transmute(self.device) }),
            Action::Detached => SafeInfo::Detached(unsafe { &*self.device }),
            Action::NotificationStopped => SafeInfo::NotificationStopped,
            Action::Paired => SafeInfo::Paired(unsafe { &*self.device }),
        }
    }
}

///
/// Notification callback. Ownership of the notification info struct *info* DOES NOT
/// pass to the callback function.
///
/// The Action field of the 'info' parameter describes the notification being sent.
///
/// - kAMDeviceAttached: A device has attached. The device reference belongs to the
///   client. It must be explicitly released, or else it will leak.
/// - kAMDeviceDetached: A device has detached. The device object delivered will be
///   the same as the one delivered in the kAMDeviceAttached notification. This
///   device reference does not need to be released.
/// - kAMDeviceNotificationStopped: This notification is delivered in response to
///   1. A call to AMDeviceNotificationUnsubscribe().
///   2. An error occurred on one of the underlying notification systems
///     (i.e. usbmuxd or mDNSResponder crashed or stopped responding).
///     Unsubcribing and resubscribing may recover the notification system.
///
pub type NotificationCallback<T> = extern "C" fn(info: &NotificationInfo, context: *mut T);

impl Device {
    /// Get a list of currently attached devices.
    /// An array of AMDeviceRefs on success or NULL on failure.
    ///
    /// Synchronously queries for and returns a list of the currently connected devices as
    /// an array of AMDeviceRefs. Devices may be disconnected at any time at which will
    /// cause the corresponding AMDeviceRef to become invalid. If no devices are attached,
    /// returned array will have zero length. Ownership follows the Create Rule.
    ///
    /// AMDCopyArrayOfDevicesMatchingQuery() is similar.
    ///
    /// To deal with devices dynamically coming and going, use AMDeviceNotificationSubscribe() instead.
    ///
    pub fn list() -> Option<arc::R<cf::ArrayOf<Device>>> {
        unsafe { AMDCreateDeviceList() }
    }

    /// use am::DeviceQueryBuilder
    pub unsafe fn copy_array_of_devices_matching_query(
        note: Option<&Notification>,
        query: &cf::Dictionary,
        out_array: *mut Option<arc::R<cf::ArrayOf<Device>>>,
    ) -> Error {
        unsafe { AMDCopyArrayOfDevicesMatchingQuery(note, query, out_array) }
    }
}

pub struct QueryBuilder {
    query: arc::Retained<cf::DictionaryMut>,
}

impl QueryBuilder {
    pub fn new_match_all() -> Self {
        Self::new(&matching::mode::all_value())
    }
    pub fn new_match_any() -> Self {
        Self::new(&matching::mode::any_value())
    }
    pub fn new_match_wildcard() -> Self {
        Self::new(&matching::mode::wildcard_value())
    }

    pub fn new(matching_mode: &cf::String) -> Self {
        let mut query = cf::DictionaryMut::with_capacity(3);

        query.insert(&matching::mode::key(), matching_mode);

        Self { query }
    }

    pub fn udids(&mut self, udids: &[&str]) -> &mut Self {
        let mut array = cf::ArrayMut::with_capacity(udids.len() as _);
        for u in udids {
            let s = cf::String::from_str(u);
            array.push(&s);
        }
        self.query.insert(&matching::criteria::udid_key(), &array);
        self
    }

    pub fn connection(&mut self, connection_type: IfaceConnectionType) -> &mut Self {
        let value = match connection_type {
            IfaceConnectionType::Invalid | IfaceConnectionType::Any => {
                self.query
                    .remove(&matching::criteria::connection_type_key());
                return self;
            }
            IfaceConnectionType::Direct => matching::criteria::usb_value(),
            IfaceConnectionType::Inderect => matching::criteria::network_value(),
            IfaceConnectionType::Proxied => matching::criteria::paired_device_value(),
        };

        self.query
            .insert(&matching::criteria::connection_type_key(), &value);
        self
    }

    pub fn matching_list(
        &self,
        note: Option<&Notification>,
    ) -> Result<arc::R<cf::ArrayOf<Device>>, Error> {
        let mut out_array = None;
        unsafe {
            let query = self.query.copy();
            Device::copy_array_of_devices_matching_query(note, &query, &mut out_array)
                .to_result(out_array)
        }
    }
}

#[link(name = "MobileDevice", kind = "framework")]
unsafe extern "C" {
    fn AMDCreateDeviceList() -> Option<arc::R<cf::ArrayOf<Device>>>;
    fn AMDCopyArrayOfDevicesMatchingQuery<'a>(
        note: Option<&Notification>,
        query: &cf::Dictionary,
        out_array: *mut Option<arc::R<cf::ArrayOf<Device>>>,
    ) -> Error;
}

impl Notification {
    pub unsafe fn subscribe<T>(
        callback: NotificationCallback<T>,
        minimum_interface_speed: Speed,
        connection_type: IfaceConnectionType,
        context: *mut T,
        ref_out: *mut Option<arc::R<Notification>>,
    ) -> Error {
        unsafe {
            AMDeviceNotificationSubscribe(
                transmute(callback),
                minimum_interface_speed,
                connection_type,
                transmute(context),
                ref_out,
            )
        }
    }

    pub fn with<T>(
        callback: NotificationCallback<T>,
        minimum_interface_speed: Speed,
        connection_type: IfaceConnectionType,
        context: *mut T,
    ) -> Result<SubscriptionGuard, Error> {
        let mut notification = None;
        unsafe {
            let res = Self::subscribe(
                callback,
                minimum_interface_speed,
                connection_type,
                context,
                &mut notification,
            );
            if res.is_ok() {
                Ok(SubscriptionGuard(notification))
            } else {
                Err(res)
            }
        }
    }

    pub unsafe fn unsubscribe(&self) -> Error {
        unsafe { AMDeviceNotificationUnsubscribe(self) }
    }
}

pub struct SubscriptionGuard(Option<arc::R<Notification>>);

impl SubscriptionGuard {
    pub fn note(&self) -> Option<&Notification> {
        self.0.as_deref()
    }
}

impl Drop for SubscriptionGuard {
    fn drop(&mut self) {
        // AMDeviceNotificationUnsubscribe decrease ref count.
        // So we need to do trick here
        if let Some(r) = self.0.take() {
            let res = unsafe { r.unsubscribe() };
            if res.is_err() {
                #[cfg(debug_assertions)]
                eprintln!("error: {res:?}");
                // put it back
                self.0 = Some(r)
            } else {
                std::mem::forget(r)
            }
        }
    }
}

#[link(name = "MobileDevice", kind = "framework")]
unsafe extern "C" {
    fn AMDeviceNotificationSubscribe(
        callback: NotificationCallback<c_void>,
        minimum_interface_speed: Speed,
        connection_type: IfaceConnectionType,
        context: *mut c_void,
        ref_out: *mut Option<arc::R<Notification>>,
    ) -> Error;

    fn AMDeviceNotificationUnsubscribe(notification: &Notification) -> Error;

}

pub mod matching {
    pub mod mode {
        use crate::{arc, cf};

        /// This key determines how the matching works. (Required)
        #[inline]
        pub fn key() -> arc::R<cf::String> {
            "MatchingMode".into()
        }

        /// If a device matches ANY of the criteria it will be part of the returned array.
        #[inline]
        pub fn any_value() -> arc::R<cf::String> {
            "MatchAny".into()
        }

        /// Only if a device matches ALL of the criteria will it be part of the returned array.
        #[inline]
        pub fn all_value() -> arc::R<cf::String> {
            "MatchAll".into()
        }

        /// Ignore all criteria, just return all devices.
        #[inline]
        pub fn wildcard_value() -> arc::R<cf::String> {
            "MatchWildcard".into()
        }
    }

    pub mod criteria {
        use crate::{arc, cf};

        /// Value is an array of CFStrings of device UDIDs, as returned
        /// by AMDeviceCopyDeviceIdentifier(). Case IN-sensitive.
        #[inline]
        pub fn udid_key() -> arc::R<cf::String> {
            "MatchUDID".into()
        }

        /// Value must be either kAMDCriteriaUSBKey or kAMDCriteriaNetworkKey.
        #[inline]
        pub fn connection_type_key() -> arc::R<cf::String> {
            "MatchConnectionType".into()
        }

        #[inline]
        pub fn usb_value() -> arc::R<cf::String> {
            "MatchConnectionTypeUSB".into()
        }

        #[inline]
        pub fn network_value() -> arc::R<cf::String> {
            "MatchConnectionTypeNetwork".into()
        }

        #[inline]
        pub fn paired_device_value() -> arc::R<cf::String> {
            "MatchConnectionTypePairedDevice".into()
        }
    }
}

#[cfg(test)]
mod tests {
    use std::ffi::c_void;

    use crate::{
        am::{
            self,
            device::discovery::{NotificationInfo, SafeInfo, matching},
        },
        cf,
    };

    #[test]
    fn notification_drop() {
        extern "C" fn callback(info: &NotificationInfo, _context: *mut c_void) {
            match info.safe() {
                SafeInfo::Attached(device) => {
                    println!("attached");
                    println!("{:?}", device.connected().unwrap().name().to_string());
                    device.show()
                }
                SafeInfo::Detached(device) => {
                    println!("detached");
                    device.show()
                }
                SafeInfo::NotificationStopped => {
                    println!("stopped")
                }
                SafeInfo::Paired(device) => {
                    println!("paired");
                    device.show()
                }
            }
        }

        let _subscription = am::DeviceNotification::with(
            callback,
            am::DeviceSpeed::ANY,
            am::DeviceIfaceConnectionType::Inderect,
            std::ptr::null_mut(),
        )
        .unwrap();

        cf::RunLoop::run_in_mode(cf::RunLoopMode::default(), 0.5, false);
    }

    #[test]
    fn filters() {
        let list = am::DeviceQueryBuilder::new(&matching::mode::wildcard_value())
            // .connection(am::DeviceInterfaceConnectionType::Direct)
            .matching_list(None)
            .unwrap();

        list.show();
    }
}