Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/transport/macos/iokit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use core_foundation::base::*;
use core_foundation::dictionary::*;
use core_foundation::number::*;
use core_foundation::runloop::*;
use core_foundation::set::*;
use core_foundation::string::*;
use std::ops::Deref;
use std::os::raw::c_void;
Expand Down Expand Up @@ -52,7 +53,7 @@ pub struct __IOHIDManager {

#[repr(C)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct IOHIDDeviceRef(*const c_void);
pub struct IOHIDDeviceRef(pub(crate) *const c_void);

unsafe impl Send for IOHIDDeviceRef {}
unsafe impl Sync for IOHIDDeviceRef {}
Expand Down Expand Up @@ -188,6 +189,7 @@ extern "C" {
allocator: CFAllocatorRef,
options: IOHIDManagerOptions,
) -> IOHIDManagerRef;
pub fn IOHIDManagerCopyDevices(manager: IOHIDManagerRef) -> CFSetRef;
pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef);
pub fn IOHIDManagerRegisterDeviceMatchingCallback(
manager: IOHIDManagerRef,
Expand Down
86 changes: 66 additions & 20 deletions src/transport/macos/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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;
Expand Down Expand Up @@ -50,6 +51,7 @@ where
&dyn Fn() -> bool,
) + Send
+ Sync
+ Clone
+ 'static,
{
pub fn new(
Expand Down Expand Up @@ -100,12 +102,23 @@ where
);

let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone);
if rv == 0 {
Ok(())
} else {
Err(io_err(&format!("Couldn't open HID Manager, rv={rv}")))
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) {
Expand All @@ -119,6 +132,26 @@ where
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
Expand All @@ -132,6 +165,27 @@ where
}
}

/// Get all currently-connected devices.
fn get_devices(&self) -> Vec<IOHIDDeviceRef> {
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<IOHIDDeviceRef> = CFSet::wrap_under_create_rule(devices);
let mut refs: Vec<IOHIDDeviceRef> = 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,
Expand Down Expand Up @@ -163,25 +217,17 @@ where
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]));
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 });
}
this.add_device(device_ref);
}

extern "C" fn on_device_removal(
Expand Down
1 change: 1 addition & 0 deletions src/transport/macos/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl Transaction {
&dyn Fn() -> bool,
) + Sync
+ Send
+ Clone
+ 'static,
T: 'static,
{
Expand Down
Loading