Package hid :: Module osx
[frames] | no frames]

Source Code for Module hid.osx

  1  ''' 
  2  The OS X HID interface module. 
  3  Dynamically loaded on OS X. 
  4  Refer to the hid module for available functions 
  5  ''' 
  6   
  7  from ctypes import * 
  8  from ctypes.util import find_library 
  9   
 10  import logging 
 11  import struct 
 12   
 13  # common code for OS X and win32 
 14  from hid import HIDDevice 
 15   
 16  # define various types we'll be using (so we can match name from headers) 
 17  mach_port_t=c_void_p 
 18   
 19  io_object_t=mach_port_t 
 20  io_iterator_t=io_object_t 
 21   
 22  SInt32=c_int 
 23  UInt32=c_uint 
 24  UInt64=c_ulonglong 
 25  IOReturn=c_int 
 26  CFRunLoopSourceRef=c_void_p 
 27  CFDictionaryRef=c_void_p 
 28  CFArrayRef=c_void_p 
 29  AbsoluteTime=UInt64 
 30  CFTimeInterval=c_double 
 31   
 32  # 128 bit identifier 
33 -class CFUUIDBytes(Structure):
34 _fields_ = [ ('bytes0_15', c_ubyte * 16) ]
35 REFIID=CFUUIDBytes 36 37 IOHIDCallbackFunction=CFUNCTYPE(None,c_void_p,IOReturn,c_void_p,c_void_p) 38 IOHIDElementCookie=c_void_p 39 IOHIDElementType=c_int # enum 40 IOHIDElementCallbackFunction=CFUNCTYPE(None,c_void_p,IOReturn,c_void_p,c_void_p,IOHIDElementCookie) 41 42 IOHIDQueueInterface=c_void_p 43 IOHIDOutputTransactionInterface=c_void_p 44 45 IOHIDReportType=c_int # enum 46 # enum values for IOHIDReportType 47 kIOHIDReportTypeInput=0 48 kIOHIDReportTypeOutput=1 49 kIOHIDReportTypeFeature=2 50 kIOHIDReportTypeCount=3 51 # 52 53 IOHIDReportCallbackFunction=CFUNCTYPE(None,c_void_p,IOReturn,c_void_p,c_void_p,UInt32) 54
55 -class IOHIDEventStruct(Structure):
56 _fields_=[ 57 ('type',IOHIDElementType), 58 ('elementCookie',IOHIDElementCookie), 59 ('value',SInt32), 60 ('timestamp',AbsoluteTime), 61 ('longValueSize',UInt32), 62 ('longValue',c_void_p) 63 ]
64 65 ######################################################## 66 # COM interface structures
67 -def IUNKNOWN_C_GUTS(fields):
68 fields.append( ('_reserved', c_void_p) ) 69 fields.append( ('QueryInterface',CFUNCTYPE(c_void_p,c_void_p,REFIID,c_void_p)) ) 70 fields.append( ('AddRef',CFUNCTYPE(c_ulong,c_void_p)) ) 71 fields.append( ('Release',CFUNCTYPE(c_ulong,c_void_p)) )
72
73 -def IOCFPLUGINBASE(fields):
74 pass
75
76 -def IOHIDDEVICEINTERFACE_FUNCS_100(fields):
77 fields.append( ('createAsyncEventSource',CFUNCTYPE(IOReturn,c_void_p,POINTER(CFRunLoopSourceRef))) ) 78 fields.append( ('getAsyncEventSource',CFUNCTYPE(CFRunLoopSourceRef,c_void_p)) ) 79 fields.append( ('createAsyncPort',CFUNCTYPE(IOReturn,c_void_p,mach_port_t)) ) 80 fields.append( ('getAsyncPort',CFUNCTYPE(mach_port_t,c_void_p)) ) 81 fields.append( ('open',CFUNCTYPE(IOReturn,c_void_p,UInt32)) ) 82 fields.append( ('close',CFUNCTYPE(IOReturn,c_void_p)) ) 83 fields.append( ('setRemovalCallback',CFUNCTYPE(IOReturn,c_void_p, IOHIDCallbackFunction,c_void_p,c_void_p) ) ) 84 fields.append( ('getElementValue',CFUNCTYPE(IOReturn,c_void_p,IOHIDElementCookie,IOHIDEventStruct)) ) 85 fields.append( ('setElementValue',CFUNCTYPE(IOReturn,c_void_p,IOHIDElementCookie,IOHIDEventStruct, UInt32,IOHIDElementCallbackFunction,c_void_p,c_void_p) ) ) 86 fields.append( ('queryElementValue',CFUNCTYPE(IOReturn,c_void_p,IOHIDElementCookie,IOHIDEventStruct, UInt32,IOHIDElementCallbackFunction,c_void_p,c_void_p) ) ) 87 fields.append( ('startAllQueues',CFUNCTYPE(IOReturn,c_void_p) ) ) 88 fields.append( ('stopAllQueues',CFUNCTYPE(IOReturn,c_void_p) ) ) 89 fields.append( ('allocQueue',CFUNCTYPE(IOHIDQueueInterface,c_void_p) ) ) 90 fields.append( ('allocOutputTransaction',CFUNCTYPE(IOHIDOutputTransactionInterface,c_void_p) ) )
91
92 -def IOHIDDEVICEINTERFACE_FUNCS_121(fields):
93 fields.append( ('setReport',CFUNCTYPE(IOReturn,c_void_p,IOHIDReportType,UInt32,c_void_p,UInt32,UInt32,IOHIDReportCallbackFunction,c_void_p,c_void_p)) ) 94 fields.append( ('getReport',CFUNCTYPE(IOReturn,c_void_p,IOHIDReportType,UInt32,c_void_p,UInt32,UInt32,IOHIDReportCallbackFunction,c_void_p,c_void_p)) )
95
96 -def IOHIDDEVICEINTERFACE_FUNCS_122(fields):
97 fields.append( ('copyMatchingElements',CFUNCTYPE(IOReturn,c_void_p,CFDictionaryRef,CFArrayRef)) ) 98 fields.append( ('setInterruptReportHandlerCallback',CFUNCTYPE(IOReturn,c_void_p,c_void_p,UInt32,IOHIDReportCallbackFunction,c_void_p,c_void_p)) )
99
100 -class IOCFPlugInInterfaceStruct(Structure):
101 pass
102 fields=[] 103 IUNKNOWN_C_GUTS(fields) 104 IOCFPLUGINBASE(fields) 105 IOCFPlugInInterfaceStruct._fields_=fields 106 fields=None 107
108 -class IOHIDDeviceInterface122(Structure):
109 pass
110 fields=[] 111 IUNKNOWN_C_GUTS(fields) 112 IOHIDDEVICEINTERFACE_FUNCS_100(fields) 113 IOHIDDEVICEINTERFACE_FUNCS_121(fields) 114 IOHIDDEVICEINTERFACE_FUNCS_122(fields) 115 IOHIDDeviceInterface122._fields_=fields 116 fields=None 117 ######################################################## 118 119 ######################################################## 120 # class to handle COM object references
121 -class COMObjectRef:
122 - def __init__(self,ref):
123 self.ref=ref 124 logging.info("created: %s",self)
125
126 - def __del__(self):
127 logging.info("releasing: %s",self) 128 self.Release()
129
130 - def __nonzero__(self):
131 return self.ref is not None
132
133 - def __str__(self):
134 return 'COMObjectRef(%s)' % self.ref
135
136 - def __getattr__(self,name):
137 ''' 138 return a function on the com object 139 (takes care of passing in the ref as the first arg) 140 ''' 141 fn=getattr(self.ref.contents.contents,name) 142 return lambda *arg: fn(self.ref,*arg)
143 144 ######################################################## 145 146 KERN_SUCCESS=0 147 148 kIOReturnSuccess=0 149 kIOHIDDeviceKey="IOHIDDevice" 150 kIOHIDVendorIDKey="VendorID" 151 kIOHIDProductIDKey="ProductID" 152 153 kIOMasterPortDefault=None 154 155 kCFAllocatorDefault=None 156 kCFStringEncodingASCII = 0x0600 157 kCFNumberIntType=9 158 kNilOptions='' 159 160 # load the CoreFoundation Library 161 cfLibraryLocation=find_library('CoreFoundation') 162 logging.info('loading CoreFoundation from: %s',cfLibraryLocation) 163 cf=CDLL(cfLibraryLocation) 164 165 # CoreFoundation Functions we'll be using 166 CFDictionaryGetValue=cf.CFDictionaryGetValue 167 CFStringCreateWithCString=cf.CFStringCreateWithCString 168 CFNumberGetValue=cf.CFNumberGetValue 169 CFRelease=cf.CFRelease 170 CFUUIDGetConstantUUIDWithBytes=cf.CFUUIDGetConstantUUIDWithBytes 171 CFUUIDGetUUIDBytes=cf.CFUUIDGetUUIDBytes 172 CFUUIDGetUUIDBytes.restype=CFUUIDBytes 173 CFRunLoopAddSource=cf.CFRunLoopAddSource 174 CFRunLoopGetCurrent=cf.CFRunLoopGetCurrent 175 CFRunLoopRunInMode=cf.CFRunLoopRunInMode 176 CFRunLoopRunInMode.argtypes = [ c_void_p, CFTimeInterval, c_int ] 177 178 # CFSTR was a macro so we'll use a function instead
179 -def CFSTR(cstr):
180 return CFStringCreateWithCString(kCFAllocatorDefault,cstr,kCFStringEncodingASCII)
181 182 # CoreFoundation constant 183 kCFRunLoopDefaultMode=c_void_p.in_dll(cf,"kCFRunLoopDefaultMode") 184 185 # Load IOKit 186 iokitLibraryLocation=find_library('IOKit') 187 logging.info('loading IOKit from: %s',iokitLibraryLocation) 188 iokit=CDLL(iokitLibraryLocation) 189 190 # IOKit functions we'll be using 191 IOIteratorNext=iokit.IOIteratorNext 192 IOIteratorNext.restype=io_object_t 193 IOObjectRelease=iokit.IOObjectRelease 194 IOServiceMatching=iokit.IOServiceMatching 195 IOServiceGetMatchingServices=iokit.IOServiceGetMatchingServices 196 IOCreatePlugInInterfaceForService=iokit.IOCreatePlugInInterfaceForService 197 198 # constants 199 kIOHIDDeviceUserClientTypeID = CFUUIDGetConstantUUIDWithBytes(None, 200 0xFA, 0x12, 0xFA, 0x38, 0x6F, 0x1A, 0x11, 0xD4, 201 0xBA, 0x0C, 0x00, 0x05, 0x02, 0x8F, 0x18, 0xD5) 202 203 kIOCFPlugInInterfaceID = CFUUIDGetConstantUUIDWithBytes(None, 204 0xC2, 0x44, 0xE8, 0x58, 0x10, 0x9C, 0x11, 0xD4, 205 0x91, 0xD4, 0x00, 0x50, 0xE4, 0xC6, 0x42, 0x6F) 206 207 kIOHIDDeviceInterfaceID = CFUUIDGetConstantUUIDWithBytes(None, 208 0x78, 0xBD, 0x42, 0x0C, 0x6F, 0x14, 0x11, 0xD4, 209 0x94, 0x74, 0x00, 0x05, 0x02, 0x8F, 0x18, 0xD5) 210
211 -def find_hid_devices():
212 ''' 213 query the host computer for all available HID devices 214 and returns a list of any found 215 ''' 216 devices=[] 217 218 hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey); 219 220 hidObjectIterator=io_iterator_t() 221 222 result=IOServiceGetMatchingServices(kIOMasterPortDefault,hidMatchDictionary,byref(hidObjectIterator)) 223 if result != kIOReturnSuccess or not hidObjectIterator: 224 raise RuntimeError("Can't obtain an IO iterator") 225 226 try: 227 while True: 228 hidDevice = IOIteratorNext(hidObjectIterator) 229 if not hidDevice: 230 break 231 232 dev=OSXHIDDevice(hidDevice,0,0) 233 234 hidProperties=c_void_p() 235 result = iokit.IORegistryEntryCreateCFProperties(hidDevice,byref(hidProperties),kCFAllocatorDefault,kNilOptions) 236 if result == KERN_SUCCESS and hidProperties: 237 vendor,product=0,0 238 vendorRef = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDVendorIDKey)); 239 productRef = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductIDKey)); 240 241 if vendorRef: 242 vendor=c_int() 243 CFNumberGetValue(vendorRef,kCFNumberIntType,byref(vendor)) 244 CFRelease(vendorRef) 245 vendor=vendor.value 246 247 if productRef: 248 product=c_int() 249 CFNumberGetValue(productRef,kCFNumberIntType,byref(product)) 250 CFRelease(productRef) 251 product=product.value 252 253 dev.vendor=vendor 254 dev.product=product 255 256 logging.info("find_hid_devices: found device vendor=0x%04x product=0x%04x",dev.vendor,dev.product) 257 devices.append(dev) 258 finally: 259 IOObjectRelease(hidObjectIterator) 260 return devices
261
262 -class OSXHIDDevice(HIDDevice):
263 ''' 264 class representing a HID device on the host (OS X) computer 265 '''
266 - def __init__(self,hidDevice,vendor,product):
267 ''' 268 create the hid device wrapper 269 hidDevice is a handle from the OS 270 ''' 271 HIDDevice.__init__(self,vendor,product) 272 self.IOObjectRelease=IOObjectRelease # need to hold onto reference to release function 273 self._hidDevice=hidDevice 274 self._hidInterface=None
275
276 - def __del__(self):
277 HIDDevice.__del__(self) 278 if self._hidDevice: 279 logging.info("releasing HID device: %s"%self) 280 self.IOObjectRelease(self._hidDevice)
281
282 - def close(self):
283 # should be sufficient to get callback thread to quit 284 if self._hidInterface: 285 self._hidInterface=None 286 HIDDevice.close(self)
287
288 - def is_open(self):
289 ''' 290 see if the device is open 291 ''' 292 return self._hidInterface is not None
293
294 - def open(self):
295 ''' 296 open the HID device - must be called prior to registering callbacks 297 or setting reports 298 ''' 299 if not self.is_open(): 300 logging.info("opening hid device") 301 # plugInInterface initialised to NULL 302 plugInInterface=COMObjectRef(POINTER(POINTER(IOCFPlugInInterfaceStruct))()) 303 score=UInt32() 304 IOCreatePlugInInterfaceForService(self._hidDevice, kIOHIDDeviceUserClientTypeID, 305 kIOCFPlugInInterfaceID, byref(plugInInterface.ref), byref(score)); 306 307 308 # query to get the HID interface 309 hidInterface=POINTER(POINTER(IOHIDDeviceInterface122))() 310 plugInInterface.QueryInterface(CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),byref(hidInterface)) 311 312 self._hidInterface=COMObjectRef(hidInterface) 313 314 # open the HID device 315 self._hidInterface.open(0) 316 else: 317 loggging.info("device already open")
318
319 - def set_report(self,report_data,report_id=0):
320 ''' 321 "set" a report - send the data to the device (which must have been opened previously) 322 ''' 323 HIDDevice.set_report(self,report_data,report_id) 324 325 # copy data into a ctypes buffer 326 report_buffer=(c_ubyte*len(report_data))() 327 for i,c in enumerate(report_data): 328 report_buffer[i]=struct.unpack('B',c)[0] 329 330 self._hidInterface.setReport( 331 kIOHIDReportTypeOutput, 332 report_id, 333 report_buffer, 334 len(report_buffer), 335 100, # 100ms 336 IOHIDReportCallbackFunction(), None, None # NULL callback 337 )
338
339 - def _run_interrupt_callback_loop(self,report_buffer_size):
340 ''' 341 run on a thread to handle reading events from the device 342 ''' 343 if not self.is_open(): 344 raise RuntimeError("device not open") 345 346 logging.info("starting _run_interrupt_callback_loop") 347 348 # create the report buffer 349 report_buffer=(c_ubyte*report_buffer_size)() # TODO should query device to find report size 350 351 # create the callback function that actually calls the user defined callback 352 def callback(target, result, refcon, sender, size): 353 # copy data out of report buffer 354 if self._callback is not None: 355 report_data="".join([struct.pack('B',b) for b in report_buffer]) 356 # zero buffer after to ensure we don't get weird-ness 357 # if it's not fully written to later 358 for i in range(len(report_buffer)): 359 report_buffer[i]=0 360 logging.info('interrupt_report_callback(%r)',report_data) 361 self._callback(self,report_data)
362 363 # make sure we hold into the callback, so it doesn't get gc-ed 364 # (leads to weird behaviour) 365 hid_callback=IOHIDReportCallbackFunction(callback) 366 367 port=mach_port_t() 368 self._hidInterface.createAsyncPort(byref(port)) 369 eventSource=CFRunLoopSourceRef() 370 self._hidInterface.createAsyncEventSource(byref(eventSource)) 371 372 # set the C callback 373 self._hidInterface.setInterruptReportHandlerCallback( 374 byref(report_buffer), 375 len(report_buffer), 376 hid_callback, 377 None,None) 378 379 # kick off the queues etc 380 self._hidInterface.startAllQueues() 381 CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode) 382 383 logging.info("running CFRunLoopRunInMode") 384 385 while self._running and self.is_open(): 386 CFRunLoopRunInMode(kCFRunLoopDefaultMode,0.1,False)
387 388 __all__ = ['find_hid_devices','OSXHIDDevice'] 389