use std::{ffi::c_void, mem::transmute};
use crate::{arc, cf};
use super::base::{Device, Error, Notification};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[repr(i32)]
pub enum IfaceConnectionType {
Invalid = -1,
Any = 0,
Direct = 1,
Inderect = 2,
Proxied = 3,
}
#[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);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[repr(i32)]
pub enum Action {
Attached = 1,
Detached = 2,
NotificationStopped = 3,
Paired = 4,
}
#[repr(C)]
pub struct NotificationInfo {
pub device: *const Device, 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 }),
}
}
}
pub type NotificationCallback<T> = extern "C" fn(info: &NotificationInfo, context: *mut T);
impl Device {
pub fn list() -> Option<arc::R<cf::ArrayOf<Device>>> {
unsafe { AMDCreateDeviceList() }
}
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) {
if let Some(r) = self.0.take() {
let res = unsafe { r.unsubscribe() };
if res.is_err() {
#[cfg(debug_assertions)]
eprintln!("error: {res:?}");
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};
#[inline]
pub fn key() -> arc::R<cf::String> {
"MatchingMode".into()
}
#[inline]
pub fn any_value() -> arc::R<cf::String> {
"MatchAny".into()
}
#[inline]
pub fn all_value() -> arc::R<cf::String> {
"MatchAll".into()
}
#[inline]
pub fn wildcard_value() -> arc::R<cf::String> {
"MatchWildcard".into()
}
}
pub mod criteria {
use crate::{arc, cf};
#[inline]
pub fn udid_key() -> arc::R<cf::String> {
"MatchUDID".into()
}
#[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())
.matching_list(None)
.unwrap();
list.show();
}
}