Vuo  2.0.0
VuoHidDevices.cc
Go to the documentation of this file.
1 
10 #include "VuoHid.h"
11 #include "VuoTriggerSet.hh"
12 #include "VuoIoReturn.h"
13 #include "VuoEventLoop.h"
14 #include "VuoUsbVendor.h"
15 #include "VuoApp.h"
16 
17 
18 #include <IOKit/IOKitLib.h>
19 #include <IOKit/hid/IOHIDLib.h>
20 #include <IOKit/usb/USB.h>
21 
22 extern "C"
23 {
24 
25 #ifdef VUO_COMPILER
27  "title" : "VuoHidDevices",
28  "dependencies" : [
29  "CoreFoundation.framework",
30  "IOKit.framework",
31  "VuoHidControl",
32  "VuoHidDevice",
33  "VuoHidUsage",
34  "VuoIoReturn",
35  "VuoList_VuoHidControl",
36  "VuoList_VuoHidDevice",
37  "VuoList_VuoInteger",
38  "VuoUsbVendor"
39  ]
40  });
41 #endif
42 }
43 
45 
46 
50 void VuoHid_getDeviceUsage(IOHIDDeviceRef device, uint32_t *usagePage, uint32_t *usage)
51 {
52  CFNumberRef usagePageCF = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDDeviceUsagePageKey));
53  if (usagePageCF)
54  CFNumberGetValue(usagePageCF, kCFNumberSInt32Type, usagePage);
55  else
56  {
57  usagePageCF = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsagePageKey));
58  if (usagePageCF)
59  CFNumberGetValue(usagePageCF, kCFNumberSInt32Type, usagePage);
60  }
61 
62  CFNumberRef usageCF = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDDeviceUsageKey));
63  if (usageCF)
64  CFNumberGetValue(usageCF, kCFNumberSInt32Type, usage);
65  else
66  {
67  usageCF = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey));
68  if (usageCF)
69  CFNumberGetValue(usageCF, kCFNumberSInt32Type, usage);
70  }
71 }
72 
76 void VuoHid_getDeviceDescription(IOHIDDeviceRef device, VuoText *manufacturer, VuoText *product, VuoInteger *vendorID, VuoInteger *productID)
77 {
78  CFNumberRef vendorIDCF = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
79  if (vendorIDCF)
80  {
81  uint32_t vendorID32 = 0;
82  CFNumberGetValue(vendorIDCF, kCFNumberSInt32Type, &vendorID32);
83  *vendorID = vendorID32;
84  }
85 
86  CFStringRef manufacturerCF = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey));
87  if (manufacturerCF)
88  *manufacturer = VuoText_makeFromCFString(manufacturerCF);
89  else
90  {
91  if (*vendorID)
92  {
93  char *vendorText = VuoUsbVendor_getText(*vendorID);
94  *manufacturer = VuoText_make(vendorText);
95  free(vendorText);
96  }
97  }
98 
99 
100  CFNumberRef productIDCF = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
101  if (productIDCF)
102  {
103  uint32_t productID32 = 0;
104  CFNumberGetValue(productIDCF, kCFNumberSInt32Type, &productID32);
105  *productID = productID32;
106  }
107 
108 
109  // When present, use the interface name, which disambiguates endpoints that have multiple interfaces.
110  io_service_t service = IOHIDDeviceGetService(device);
111  io_registry_entry_t parent;
112  IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent);
113  CFStringRef interfaceNameCF = (CFStringRef)IORegistryEntryCreateCFProperty(parent, CFSTR("USB Interface Name"), NULL, 0);
114  if (interfaceNameCF)
115  *product = VuoText_makeFromCFString(interfaceNameCF);
116  else
117  {
118  CFStringRef productCF = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
119  if (productCF)
120  *product = VuoText_makeFromCFString(productCF);
121  else
122  {
123  if (*productID)
124  {
125  char *productText = VuoText_format("0x%04llx", *productID);
126  *product = VuoText_make(productText);
127  free(productText);
128  }
129  }
130  }
131 }
132 
137 {
138  IOHIDDeviceRef device = (IOHIDDeviceRef)d;
139 
140  CFNumberRef locationIDCF = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDLocationIDKey));
141  uint32_t location = 0;
142  if (locationIDCF)
143  CFNumberGetValue(locationIDCF, kCFNumberSInt32Type, &location);
144 
145  io_service_t service = IOHIDDeviceGetService(device);
146  io_registry_entry_t parent;
147  IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent);
148  CFNumberRef interfaceNumberCF = (CFNumberRef)IORegistryEntryCreateCFProperty(parent, CFSTR(kUSBInterfaceNumber), NULL, 0);
149  uint32_t interfaceNumber = 0xff;
150  if (interfaceNumberCF)
151  {
152  CFNumberGetValue(interfaceNumberCF, kCFNumberSInt32Type, &interfaceNumber);
153  CFRelease(interfaceNumberCF);
154  }
155 
156  return (location << 8) + interfaceNumber;
157 }
158 
163 {
164  IOHIDElementRef element = (IOHIDElementRef)e;
165 
166 // uint32_t usagePage = IOHIDElementGetUsagePage(element);
167  uint32_t usage = IOHIDElementGetUsage(element);
168 
169  // Skip vendor-defined pages since they don't provide data in a well-defined format.
170  // For example, in "Apple Internal Keyboard / Trackpad" there are 514 vendor-defined elements, apparently always having value 0.
171 // if (usagePage >= 0xff00)
172 // return false;
173 
174  // Skip out-of-range usages since they don't provide valid data.
175  // For example, in "Apple Mikey HID Driver" the values are less than min.
176  if (usage > 0xffff)
177  return false;
178 
179  // All the interesting data seems to be Misc (integer values) and Button.
180  IOHIDElementType elementType = IOHIDElementGetType(element);
181  if (elementType != kIOHIDElementTypeInput_Misc
182  && elementType != kIOHIDElementTypeInput_Button)
183  return false;
184 
185  return true;
186 }
187 
192 {
193  IOHIDElementRef element = (IOHIDElementRef)e;
194 
195  uint32_t usagePage = IOHIDElementGetUsagePage(element);
196  uint32_t usage = IOHIDElementGetUsage(element);
197  char *usageText = VuoHid_getUsageText(usagePage, usage);
198 
199  VuoInteger min = IOHIDElementGetLogicalMin(element);
200  VuoInteger max = IOHIDElementGetLogicalMax(element);
201  VuoHidControl control = {
202  VuoText_make(usageText),
203  VuoInteger_clamp(0, min, max),
204  min,
205  max
206  };
207  free(usageText);
208 
209  return control;
210 }
211 
216 {
217  VDebugLog("Devices:");
218 
219  IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
220  if (!manager)
221  {
222  VUserLog("Error: Couldn't initialize IOHIDManager.");
223  return NULL;
224  }
225 
226  IOHIDManagerSetDeviceMatching(manager, NULL);
227 
228  IOHIDDeviceRef *devicesIO = NULL;
229  CFIndex deviceCount;
230  {
231  IOReturn ret = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
232  if (ret != kIOReturnSuccess)
233  {
234  if (ret == kIOReturnExclusiveAccess)
235  {
236  // This apparently just means that at least one device is already open for exclusive access,
237  // and other devices were successfully opened.
238  }
239  else
240  {
241  char *text = VuoIoReturn_getText(ret);
242  VUserLog("Error: Couldn't open IOHIDManager: %s", text);
243  free(text);
244  CFRelease(manager);
245  return NULL;
246  }
247  }
248 
249  CFSetRef deviceCF = IOHIDManagerCopyDevices(manager);
250  if (!deviceCF)
251  {
252  VUserLog("Error: Couldn't copy device list.");
253  CFRelease(manager);
254  return NULL;
255  }
256 
257  deviceCount = CFSetGetCount(deviceCF);
258  devicesIO = (IOHIDDeviceRef *)malloc(sizeof(IOHIDDeviceRef) * deviceCount);
259  CFSetGetValues(deviceCF, (const void **)devicesIO);
260  CFRelease(deviceCF);
261  }
262 
264  for (CFIndex deviceIndex = 0; deviceIndex < deviceCount; ++deviceIndex)
265  {
266  uint32_t usagePage = 0;
267  uint32_t usage = 0;
268  VuoHid_getDeviceUsage(devicesIO[deviceIndex], &usagePage, &usage);
269 
270  VuoText manufacturer = NULL;
271  VuoText product = NULL;
272  VuoInteger vendorID = 0;
273  VuoInteger productID = 0;
274  VuoHid_getDeviceDescription(devicesIO[deviceIndex], &manufacturer, &product, &vendorID, &productID);
275  VuoRetain(manufacturer);
276  VuoRetain(product);
277 
278  // Ignore "Touchpad (Apple Inc. Mouse)" and "Touchpad (Apple Inc. Vendor-defined)" since they doesn't seem to output any data.
279  if (vendorID == 0x05ac
280  && ( productID == 0x0262 // MacBookPro10,1
281  || productID == 0x0259) // MacBookPro10,2
282  && ((usagePage == 0x0001 && usage == 0x0002)
283  || (usagePage == 0xff00 && usage == 0x0001))
284  && manufacturer
285  && product
286  && strcmp(manufacturer, "Apple Inc.") == 0
287  && strcmp(product, "Touchpad") == 0)
288  {
289  VuoRelease(manufacturer);
290  VuoRelease(product);
291  continue;
292  }
293 
294  // Provide a clearer name for "Apple Internal Keyboard / Trackpad".
295  if (product && strcmp(product, "Apple Internal Keyboard / Trackpad") == 0)
296  {
297  VuoRelease(product);
298  product = VuoText_make("Apple Internal Trackpad");
299  VuoRetain(product);
300  }
301 
302  char *usageText = VuoHid_getUsageText(usagePage,usage);
303  VuoHidDevice device = {VuoHidDevice_MatchLocation, NULL, 0, NULL, vendorID, productID, usagePage, usage};
304  if (manufacturer && product)
305  device.name = VuoText_make(VuoText_format("%s (%s %s)", product, manufacturer, usageText));
306  else if (manufacturer)
307  device.name = VuoText_make(VuoText_format("%s (%s)", manufacturer, usageText));
308  else if (product)
309  device.name = VuoText_make(VuoText_format("%s (%s)", product, usageText));
310  else
311  device.name = VuoText_make(VuoText_format("%s", usageText));
312  free(usageText);
313 
314  device.location = VuoHid_getLocation(devicesIO[deviceIndex]);
315 
316  VDebugLog("\t%08llx:%02llx \"%s\"", device.location>>8, device.location&0xff, device.name);
317 
318  CFArrayRef elementsCF = IOHIDDeviceCopyMatchingElements(devicesIO[deviceIndex], NULL, kIOHIDOptionsTypeNone);
319  if (elementsCF)
320  {
321  CFIndex elementCount = CFArrayGetCount(elementsCF);
323  for (CFIndex elementIndex = 0; elementIndex < elementCount; ++elementIndex)
324  {
325  IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elementsCF, elementIndex);
326  if (!element)
327  continue;
328 
329  if (!VuoHid_isElementValid(element))
330  continue;
331 
333  }
334 
335  CFRelease(elementsCF);
336 
337  device.controls = controls;
338 
339  if (VuoListGetCount_VuoHidControl(controls))
340  VuoListAppendValue_VuoHidDevice(devices, device);
341  else
342  VDebugLog("\t\tSkipping this device since it doesn't have any valid elements.");
343  }
344  else
345  VDebugLog("\t\tSkipping this device since I can't get its elements (perhaps another process has it open for exclusive access).");
346 
347  VuoRelease(manufacturer);
348  VuoRelease(product);
349  }
350 
351  VuoListSort_VuoHidDevice(devices);
352 
353  free(devicesIO);
354  CFRelease(manager);
355  return devices;
356 }
357 
358 unsigned int VuoHid_useCount = 0;
359 IOHIDManagerRef VuoHid_manager;
362 
366 static void VuoHid_devicesChanged(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
367 {
370 
373 }
374 
380 void VuoHid_use(void)
381 {
382  if (__sync_add_and_fetch(&VuoHid_useCount, 1) == 1)
383  {
386 
387  VuoHid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
388  if (!VuoHid_manager)
389  {
390  VUserLog("Error: Couldn't initialize IOHIDManager.");
391  return;
392  }
393 
394  IOHIDManagerSetDeviceMatching(VuoHid_manager, NULL);
395 
396 
397  IOReturn ret = IOHIDManagerOpen(VuoHid_manager, kIOHIDOptionsTypeNone);
398  if (ret != kIOReturnSuccess)
399  {
400  if (ret == kIOReturnExclusiveAccess)
401  {
402  // This "error" apparently just means that at least one device is already open for exclusive access,
403  // yet other devices may have been successfully opened.
404  }
405  else
406  {
407  char *text = VuoIoReturn_getText(ret);
408  VUserLog("Error: Couldn't open IOHIDManager: %s", text);
409  free(text);
410  CFRelease(VuoHid_manager);
411  VuoHid_manager = NULL;
412  return;
413  }
414  }
415 
416  IOHIDManagerRegisterDeviceMatchingCallback(VuoHid_manager, &VuoHid_devicesChanged, NULL);
417  IOHIDManagerRegisterDeviceRemovalCallback (VuoHid_manager, &VuoHid_devicesChanged, NULL);
418  IOHIDManagerScheduleWithRunLoop(VuoHid_manager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
419 
420  // Wait for the HID Manager to invoke the callback with the existing devices.
423  {
424  VuoEventLoop_processEvent(VuoEventLoop_RunOnce);
425  usleep(USEC_PER_SEC / 10);
426  }
427  });
429  }
430 }
431 
437 void VuoHid_disuse(void)
438 {
439  if (VuoHid_useCount == 0)
440  {
441  VUserLog("Error: Unbalanced VuoHid_use() / _disuse() calls.");
442  return;
443  }
444 
445  if (__sync_sub_and_fetch(&VuoHid_useCount, 1) == 0)
446  if (VuoHid_manager)
447  CFRelease(VuoHid_manager);
448 }
449 
458 {
459  VuoHid_deviceCallbacks.addTrigger(devices);
460  devices(VuoHid_getDeviceList());
461 }
462 
469 {
470  VuoHid_deviceCallbacks.removeTrigger(devices);
471 }