/* 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 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 + '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 { Ok(()) } else { Err(io_err(&format!("Couldn't open HID Manager, rv={rv}"))) } } } 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) }; } 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(); } } 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) }; let _ = this .selector_sender .send(DeviceSelectorEvent::DevicesAdded(vec![device_ref])); let selector_sender = this.selector_sender.clone(); let status_sender = this.status_sender.clone(); let (tx, rx) = channel(); let f = &this.new_device_cb; // 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 { this.map.insert(device_ref, DeviceData { tx, runloop }); } } 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) }; } }