1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] 6 7 extern crate core_foundation_sys; 8 extern crate libc; 9 10 use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE}; 11 use core_foundation_sys::base::*; 12 use core_foundation_sys::dictionary::*; 13 use core_foundation_sys::number::*; 14 use core_foundation_sys::runloop::*; 15 use core_foundation_sys::string::*; 16 use libc::c_void; 17 use std::ops::Deref; 18 19 type IOOptionBits = u32; 20 21 pub type IOReturn = libc::c_int; 22 23 pub type IOHIDManagerRef = *mut __IOHIDManager; 24 pub type IOHIDManagerOptions = IOOptionBits; 25 26 pub type IOHIDDeviceCallback = extern "C" fn( 27 context: *mut c_void, 28 result: IOReturn, 29 sender: *mut c_void, 30 device: IOHIDDeviceRef, 31 ); 32 33 pub type IOHIDReportType = IOOptionBits; 34 pub type IOHIDReportCallback = extern "C" fn( 35 context: *mut c_void, 36 result: IOReturn, 37 sender: IOHIDDeviceRef, 38 report_type: IOHIDReportType, 39 report_id: u32, 40 report: *mut u8, 41 report_len: CFIndex, 42 ); 43 44 pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0; 45 46 pub const kIOHIDReportTypeOutput: IOHIDReportType = 1; 47 48 #[repr(C)] 49 pub struct __IOHIDManager { 50 __private: c_void, 51 } 52 53 #[repr(C)] 54 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 55 pub struct IOHIDDeviceRef(*const c_void); 56 57 unsafe impl Send for IOHIDDeviceRef {} 58 unsafe impl Sync for IOHIDDeviceRef {} 59 60 pub struct SendableRunLoop(CFRunLoopRef); 61 62 impl SendableRunLoop { new(runloop: CFRunLoopRef) -> Self63 pub fn new(runloop: CFRunLoopRef) -> Self { 64 // Keep the CFRunLoop alive for as long as we are. 65 unsafe { CFRetain(runloop as *mut c_void) }; 66 67 SendableRunLoop(runloop) 68 } 69 } 70 71 unsafe impl Send for SendableRunLoop {} 72 73 impl Deref for SendableRunLoop { 74 type Target = CFRunLoopRef; 75 deref(&self) -> &CFRunLoopRef76 fn deref(&self) -> &CFRunLoopRef { 77 &self.0 78 } 79 } 80 81 impl Drop for SendableRunLoop { drop(&mut self)82 fn drop(&mut self) { 83 unsafe { CFRelease(self.0 as *mut c_void) }; 84 } 85 } 86 87 #[repr(C)] 88 pub struct CFRunLoopObserverContext { 89 pub version: CFIndex, 90 pub info: *mut c_void, 91 pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>, 92 pub release: Option<extern "C" fn(info: *const c_void)>, 93 pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>, 94 } 95 96 impl CFRunLoopObserverContext { new(context: *mut c_void) -> Self97 pub fn new(context: *mut c_void) -> Self { 98 Self { 99 version: 0 as CFIndex, 100 info: context, 101 retain: None, 102 release: None, 103 copyDescription: None, 104 } 105 } 106 } 107 108 pub struct CFRunLoopEntryObserver { 109 observer: CFRunLoopObserverRef, 110 // Keep alive until the observer goes away. 111 context_ptr: *mut CFRunLoopObserverContext, 112 } 113 114 impl CFRunLoopEntryObserver { new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self115 pub fn new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self { 116 let context = CFRunLoopObserverContext::new(context); 117 let context_ptr = Box::into_raw(Box::new(context)); 118 119 let observer = unsafe { 120 CFRunLoopObserverCreate( 121 kCFAllocatorDefault, 122 kCFRunLoopEntry, 123 false as Boolean, 124 0, 125 callback, 126 context_ptr, 127 ) 128 }; 129 130 Self { 131 observer, 132 context_ptr, 133 } 134 } 135 add_to_current_runloop(&self)136 pub fn add_to_current_runloop(&self) { 137 unsafe { 138 CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode) 139 }; 140 } 141 } 142 143 impl Drop for CFRunLoopEntryObserver { drop(&mut self)144 fn drop(&mut self) { 145 unsafe { 146 CFRelease(self.observer as *mut c_void); 147 148 // Drop the CFRunLoopObserverContext. 149 let _ = Box::from_raw(self.context_ptr); 150 }; 151 } 152 } 153 154 pub struct IOHIDDeviceMatcher { 155 dict: CFDictionaryRef, 156 keys: Vec<CFStringRef>, 157 values: Vec<CFNumberRef>, 158 } 159 160 impl IOHIDDeviceMatcher { new() -> Self161 pub fn new() -> Self { 162 let keys = vec![ 163 IOHIDDeviceMatcher::cf_string("DeviceUsage"), 164 IOHIDDeviceMatcher::cf_string("DeviceUsagePage"), 165 ]; 166 167 let values = vec![ 168 IOHIDDeviceMatcher::cf_number(FIDO_USAGE_U2FHID as i32), 169 IOHIDDeviceMatcher::cf_number(FIDO_USAGE_PAGE as i32), 170 ]; 171 172 let dict = unsafe { 173 CFDictionaryCreate( 174 kCFAllocatorDefault, 175 keys.as_ptr() as *const *const libc::c_void, 176 values.as_ptr() as *const *const libc::c_void, 177 keys.len() as CFIndex, 178 &kCFTypeDictionaryKeyCallBacks, 179 &kCFTypeDictionaryValueCallBacks, 180 ) 181 }; 182 183 Self { dict, keys, values } 184 } 185 cf_number(number: i32) -> CFNumberRef186 fn cf_number(number: i32) -> CFNumberRef { 187 let nbox = Box::new(number); 188 let nptr = Box::into_raw(nbox) as *mut libc::c_void; 189 190 unsafe { 191 // Drop when out of scope. 192 let _num = Box::from_raw(nptr as *mut i32); 193 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, nptr) 194 } 195 } 196 cf_string(string: &str) -> CFStringRef197 fn cf_string(string: &str) -> CFStringRef { 198 unsafe { 199 CFStringCreateWithBytes( 200 kCFAllocatorDefault, 201 string.as_ptr(), 202 string.len() as CFIndex, 203 kCFStringEncodingUTF8, 204 false as Boolean, 205 ) 206 } 207 } 208 get(&self) -> CFDictionaryRef209 pub fn get(&self) -> CFDictionaryRef { 210 self.dict 211 } 212 } 213 214 impl Drop for IOHIDDeviceMatcher { drop(&mut self)215 fn drop(&mut self) { 216 unsafe { CFRelease(self.dict as *mut libc::c_void) }; 217 218 for key in &self.keys { 219 unsafe { CFRelease(*key as *mut libc::c_void) }; 220 } 221 222 for value in &self.values { 223 unsafe { CFRelease(*value as *mut libc::c_void) }; 224 } 225 } 226 } 227 228 #[link(name = "IOKit", kind = "framework")] 229 extern "C" { 230 // CFRunLoop CFRunLoopObserverCreate( allocator: CFAllocatorRef, activities: CFOptionFlags, repeats: Boolean, order: CFIndex, callout: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, ) -> CFRunLoopObserverRef231 pub fn CFRunLoopObserverCreate( 232 allocator: CFAllocatorRef, 233 activities: CFOptionFlags, 234 repeats: Boolean, 235 order: CFIndex, 236 callout: CFRunLoopObserverCallBack, 237 context: *mut CFRunLoopObserverContext, 238 ) -> CFRunLoopObserverRef; 239 240 // IOHIDManager IOHIDManagerCreate( allocator: CFAllocatorRef, options: IOHIDManagerOptions, ) -> IOHIDManagerRef241 pub fn IOHIDManagerCreate( 242 allocator: CFAllocatorRef, 243 options: IOHIDManagerOptions, 244 ) -> IOHIDManagerRef; IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef)245 pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef); IOHIDManagerRegisterDeviceMatchingCallback( manager: IOHIDManagerRef, callback: IOHIDDeviceCallback, context: *mut c_void, )246 pub fn IOHIDManagerRegisterDeviceMatchingCallback( 247 manager: IOHIDManagerRef, 248 callback: IOHIDDeviceCallback, 249 context: *mut c_void, 250 ); IOHIDManagerRegisterDeviceRemovalCallback( manager: IOHIDManagerRef, callback: IOHIDDeviceCallback, context: *mut c_void, )251 pub fn IOHIDManagerRegisterDeviceRemovalCallback( 252 manager: IOHIDManagerRef, 253 callback: IOHIDDeviceCallback, 254 context: *mut c_void, 255 ); IOHIDManagerRegisterInputReportCallback( manager: IOHIDManagerRef, callback: IOHIDReportCallback, context: *mut c_void, )256 pub fn IOHIDManagerRegisterInputReportCallback( 257 manager: IOHIDManagerRef, 258 callback: IOHIDReportCallback, 259 context: *mut c_void, 260 ); IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn261 pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn262 pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; IOHIDManagerScheduleWithRunLoop( manager: IOHIDManagerRef, runLoop: CFRunLoopRef, runLoopMode: CFStringRef, )263 pub fn IOHIDManagerScheduleWithRunLoop( 264 manager: IOHIDManagerRef, 265 runLoop: CFRunLoopRef, 266 runLoopMode: CFStringRef, 267 ); 268 269 // IOHIDDevice IOHIDDeviceSetReport( device: IOHIDDeviceRef, reportType: IOHIDReportType, reportID: CFIndex, report: *const u8, reportLength: CFIndex, ) -> IOReturn270 pub fn IOHIDDeviceSetReport( 271 device: IOHIDDeviceRef, 272 reportType: IOHIDReportType, 273 reportID: CFIndex, 274 report: *const u8, 275 reportLength: CFIndex, 276 ) -> IOReturn; 277 } 278 279 //////////////////////////////////////////////////////////////////////// 280 // Tests 281 //////////////////////////////////////////////////////////////////////// 282 283 #[cfg(test)] 284 mod tests { 285 use core_foundation_sys::base::*; 286 use core_foundation_sys::runloop::*; 287 use libc::c_void; 288 use std::ptr; 289 use std::sync::mpsc::{channel, Sender}; 290 use std::thread; 291 use super::*; 292 observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void)293 extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) { 294 let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) }; 295 296 // Send the current runloop to the receiver to unblock it. 297 let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() })); 298 } 299 300 #[test] test_sendable_runloop()301 fn test_sendable_runloop() { 302 let (tx, rx) = channel(); 303 304 let thread = thread::spawn(move || { 305 // Send the runloop to the owning thread. 306 let context = &tx as *const _ as *mut c_void; 307 let obs = CFRunLoopEntryObserver::new(observe, context); 308 obs.add_to_current_runloop(); 309 310 unsafe { 311 // We need some source for the runloop to run. 312 let manager = IOHIDManagerCreate(kCFAllocatorDefault, 0); 313 assert!(!manager.is_null()); 314 315 IOHIDManagerScheduleWithRunLoop( 316 manager, 317 CFRunLoopGetCurrent(), 318 kCFRunLoopDefaultMode, 319 ); 320 IOHIDManagerSetDeviceMatching(manager, ptr::null_mut()); 321 322 let rv = IOHIDManagerOpen(manager, 0); 323 assert_eq!(rv, 0); 324 325 // This will run until `CFRunLoopStop()` is called. 326 CFRunLoopRun(); 327 328 let rv = IOHIDManagerClose(manager, 0); 329 assert_eq!(rv, 0); 330 331 CFRelease(manager as *mut c_void); 332 } 333 }); 334 335 // Block until we enter the CFRunLoop. 336 let runloop: SendableRunLoop = rx.recv().expect("failed to receive runloop"); 337 338 // Stop the runloop. 339 unsafe { CFRunLoopStop(*runloop) }; 340 341 // Stop the thread. 342 thread.join().expect("failed to join the thread"); 343 344 // Try to stop the runloop again (without crashing). 345 unsafe { CFRunLoopStop(*runloop) }; 346 } 347 } 348