/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ extern crate libc; extern crate log; use crate::transport::device_selector::DeviceSelectorEvent; use crate::transport::platform::iokit::*; use crate::util::io_err; use core_foundation::base::*; use core_foundation::runloop::*; use core_foundation::set::*; use runloop::RunLoop; use std::collections::HashMap; use std::os::raw::c_void; use std::sync::mpsc::{channel, Receiver, Sender}; use std::{io, slice}; struct DeviceData { tx: Sender>, runloop: RunLoop, } pub struct Monitor where F: Fn( (IOHIDDeviceRef, Receiver>), Sender, Sender, &dyn Fn() -> bool, ) + Send + Sync + 'static, { manager: IOHIDManagerRef, // Keep alive until the monitor goes away. _matcher: IOHIDDeviceMatcher, map: HashMap, new_device_cb: F, selector_sender: Sender, status_sender: Sender, } impl Monitor where F: Fn( (IOHIDDeviceRef, Receiver>), Sender, Sender, &dyn Fn() -> bool, ) + Send + Sync + Clone + 'static, { pub fn new( new_device_cb: F, selector_sender: Sender, status_sender: Sender, ) -> Self { let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) }; // Match FIDO devices only. let _matcher = IOHIDDeviceMatcher::new(); unsafe { IOHIDManagerSetDeviceMatching(manager, _matcher.dict.as_concrete_TypeRef()) }; Self { manager, _matcher, new_device_cb, map: HashMap::new(), selector_sender, status_sender, } } pub fn start(&mut self) -> io::Result<()> { let context = self as *mut Self as *mut c_void; unsafe { IOHIDManagerRegisterDeviceMatchingCallback( self.manager, Monitor::::on_device_matching, context, ); IOHIDManagerRegisterDeviceRemovalCallback( self.manager, Monitor::::on_device_removal, context, ); IOHIDManagerRegisterInputReportCallback( self.manager, Monitor::::on_input_report, context, ); IOHIDManagerScheduleWithRunLoop( self.manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ); let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone); if rv != 0 { return Err(io_err(&format!("Couldn't open HID Manager, rv={rv}"))); } } // Enumerate all existing devices simultaneously, like what Linux does. This mitigates // against a race condition in DeviceSelector if there is a slow device. let devices = self.get_devices(); let _ = self .selector_sender .send(DeviceSelectorEvent::DevicesAdded(devices.clone())); for device in devices { self.add_device(device); } Ok(()) } pub fn stop(&mut self) { // Remove all devices. while !self.map.is_empty() { let device_ref = *self.map.keys().next().unwrap(); self.remove_device(device_ref); } // Close the manager and its devices. unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) }; } /// Set up a runloop for a new device. fn add_device(&mut self, device_ref: IOHIDDeviceRef) { let selector_sender = self.selector_sender.clone(); let status_sender = self.status_sender.clone(); let (tx, rx) = channel(); let f = self.new_device_cb.clone(); // Create a new per-device runloop. let runloop = RunLoop::new(move |alive| { // Ensure that the runloop is still alive. if alive() { f((device_ref, rx), selector_sender, status_sender, alive); } }); if let Ok(runloop) = runloop { self.map.insert(device_ref, DeviceData { tx, runloop }); } } fn remove_device(&mut self, device_ref: IOHIDDeviceRef) { if let Some(DeviceData { tx, runloop }) = self.map.remove(&device_ref) { let _ = self .selector_sender .send(DeviceSelectorEvent::DeviceRemoved(device_ref)); // Dropping `tx` will make Device::read() fail eventually. drop(tx); // Wait until the runloop stopped. runloop.cancel(); } } /// Get all currently-connected devices. fn get_devices(&self) -> Vec { unsafe { let devices = IOHIDManagerCopyDevices(self.manager); if devices.is_null() { // IOHIDManagerCopyDevices returns null on zero devices (undocumented!) return Vec::with_capacity(0); } let s: CFSet = CFSet::wrap_under_create_rule(devices); let mut refs: Vec = Vec::with_capacity(s.len()); CFSetGetValues( s.as_concrete_TypeRef(), refs.as_mut_ptr() as *mut *const c_void, ); refs.set_len(s.len()); refs } } extern "C" fn on_input_report( context: *mut c_void, _: IOReturn, device_ref: IOHIDDeviceRef, _: IOHIDReportType, _: u32, report: *mut u8, report_len: CFIndex, ) { let this = unsafe { &mut *(context as *mut Self) }; let mut send_failed = false; // Ignore the report if we can't find a device for it. if let Some(DeviceData { tx, .. }) = this.map.get(&device_ref) { let data = unsafe { slice::from_raw_parts(report, report_len as usize).to_vec() }; send_failed = tx.send(data).is_err(); } // Remove the device if sending fails. if send_failed { this.remove_device(device_ref); } } extern "C" fn on_device_matching( context: *mut c_void, _: IOReturn, _: *mut c_void, device_ref: IOHIDDeviceRef, ) { let this = unsafe { &mut *(context as *mut Self) }; // IOHIDManager sends a DeviceMatchingCallback for every already-connected device, but // get_devices will have already handled this. if this.map.contains_key(&device_ref) { debug!("Ignoring duplicate device: {device_ref:?}"); return; } let _ = this .selector_sender .send(DeviceSelectorEvent::DevicesAdded(vec![device_ref])); this.add_device(device_ref); } extern "C" fn on_device_removal( context: *mut c_void, _: IOReturn, _: *mut c_void, device_ref: IOHIDDeviceRef, ) { let this = unsafe { &mut *(context as *mut Self) }; this.remove_device(device_ref); } } impl Drop for Monitor where F: Fn( (IOHIDDeviceRef, Receiver>), Sender, Sender, &dyn Fn() -> bool, ) + Send + Sync + 'static, { fn drop(&mut self) { unsafe { CFRelease(self.manager as *mut c_void) }; } }