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
14 from hid import HIDDevice
15
16
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
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
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
46
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
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
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
75
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
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
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
102 fields=[]
103 IUNKNOWN_C_GUTS(fields)
104 IOCFPLUGINBASE(fields)
105 IOCFPlugInInterfaceStruct._fields_=fields
106 fields=None
107
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
123 self.ref=ref
124 logging.info("created: %s",self)
125
127 logging.info("releasing: %s",self)
128 self.Release()
129
131 return self.ref is not None
132
134 return 'COMObjectRef(%s)' % self.ref
135
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
161 cfLibraryLocation=find_library('CoreFoundation')
162 logging.info('loading CoreFoundation from: %s',cfLibraryLocation)
163 cf=CDLL(cfLibraryLocation)
164
165
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
181
182
183 kCFRunLoopDefaultMode=c_void_p.in_dll(cf,"kCFRunLoopDefaultMode")
184
185
186 iokitLibraryLocation=find_library('IOKit')
187 logging.info('loading IOKit from: %s',iokitLibraryLocation)
188 iokit=CDLL(iokitLibraryLocation)
189
190
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
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
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
263 '''
264 class representing a HID device on the host (OS X) computer
265 '''
266 - def __init__(self,hidDevice,vendor,product):
275
281
283
284 if self._hidInterface:
285 self._hidInterface=None
286 HIDDevice.close(self)
287
289 '''
290 see if the device is open
291 '''
292 return self._hidInterface is not None
293
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
302 plugInInterface=COMObjectRef(POINTER(POINTER(IOCFPlugInInterfaceStruct))())
303 score=UInt32()
304 IOCreatePlugInInterfaceForService(self._hidDevice, kIOHIDDeviceUserClientTypeID,
305 kIOCFPlugInInterfaceID, byref(plugInInterface.ref), byref(score));
306
307
308
309 hidInterface=POINTER(POINTER(IOHIDDeviceInterface122))()
310 plugInInterface.QueryInterface(CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),byref(hidInterface))
311
312 self._hidInterface=COMObjectRef(hidInterface)
313
314
315 self._hidInterface.open(0)
316 else:
317 loggging.info("device already open")
318
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
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,
336 IOHIDReportCallbackFunction(), None, None
337 )
338
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
349 report_buffer=(c_ubyte*report_buffer_size)()
350
351
352 def callback(target, result, refcon, sender, size):
353
354 if self._callback is not None:
355 report_data="".join([struct.pack('B',b) for b in report_buffer])
356
357
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
364
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
373 self._hidInterface.setInterruptReportHandlerCallback(
374 byref(report_buffer),
375 len(report_buffer),
376 hid_callback,
377 None,None)
378
379
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