2015-09-02 19:09:13 +00:00
|
|
|
#include "boo/inputdev/IHIDListener.hpp"
|
|
|
|
#include "boo/inputdev/DeviceFinder.hpp"
|
2015-04-21 04:02:43 +00:00
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
|
|
#include <IOKit/hid/IOHIDLib.h>
|
2017-05-07 21:24:00 +00:00
|
|
|
#include <IOKit/hid/IOHIDDevicePlugin.h>
|
|
|
|
#include <IOKit/hid/IOHIDKeys.h>
|
2015-04-24 00:24:15 +00:00
|
|
|
#include <IOKit/IOKitLib.h>
|
|
|
|
#include <IOKit/usb/IOUSBLib.h>
|
|
|
|
#include <IOKit/IOCFPlugIn.h>
|
2017-05-07 21:24:00 +00:00
|
|
|
#include "IOKitPointer.hpp"
|
|
|
|
#include "CFPointer.hpp"
|
2015-04-24 00:24:15 +00:00
|
|
|
|
2015-04-30 07:01:55 +00:00
|
|
|
namespace boo
|
|
|
|
{
|
|
|
|
|
2015-04-29 10:24:39 +00:00
|
|
|
/*
|
|
|
|
* Reference: http://oroboro.com/usb-serial-number-osx/
|
2015-04-24 00:24:15 +00:00
|
|
|
*/
|
|
|
|
|
2017-05-07 21:24:00 +00:00
|
|
|
static bool getUSBStringDescriptor(const IUnknownPointer<IOUSBDeviceInterface182>& usbDevice, UInt8 idx, char* out)
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
UInt16 buffer[128];
|
|
|
|
|
|
|
|
// wow... we're actually forced to make hard coded bus requests. Its like
|
|
|
|
// hard disk programming in the 80's!
|
|
|
|
IOUSBDevRequest request;
|
|
|
|
|
|
|
|
request.bmRequestType = USBmakebmRequestType(kUSBIn,
|
|
|
|
kUSBStandard,
|
|
|
|
kUSBDevice);
|
|
|
|
request.bRequest = kUSBRqGetDescriptor;
|
|
|
|
request.wValue = (kUSBStringDesc << 8) | idx;
|
|
|
|
request.wIndex = 0x409; // english
|
|
|
|
request.wLength = sizeof(buffer);
|
|
|
|
request.pData = buffer;
|
|
|
|
|
2017-05-07 21:24:00 +00:00
|
|
|
kern_return_t err = usbDevice->DeviceRequest(usbDevice.storage(), &request);
|
2015-04-24 00:24:15 +00:00
|
|
|
if (err != 0)
|
|
|
|
{
|
|
|
|
// the request failed... fairly uncommon for the USB disk driver, but not
|
|
|
|
// so uncommon for other devices. This can also be less reliable if your
|
|
|
|
// disk is mounted through an external USB hub. At this level we actually
|
|
|
|
// have to worry about hardware issues like this.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we're mallocing this string just as an example. But you probably will want
|
|
|
|
// to do something smarter, like pre-allocated buffers in the info class, or
|
|
|
|
// use a string class.
|
2016-07-09 21:54:08 +00:00
|
|
|
if (request.wLenDone == 0)
|
|
|
|
return false;
|
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
unsigned count = (request.wLenDone - 1) / 2;
|
|
|
|
unsigned i;
|
|
|
|
for (i=0 ; i<count ; ++i)
|
|
|
|
out[i] = buffer[i+1];
|
|
|
|
out[i] = '\0';
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2015-04-21 04:02:43 +00:00
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
class HIDListenerIOKit : public IHIDListener
|
2015-04-21 04:02:43 +00:00
|
|
|
{
|
2015-09-02 19:09:13 +00:00
|
|
|
DeviceFinder& m_finder;
|
2015-04-21 04:02:43 +00:00
|
|
|
|
2015-04-23 00:46:32 +00:00
|
|
|
CFRunLoopRef m_listenerRunLoop;
|
2015-04-24 00:24:15 +00:00
|
|
|
IONotificationPortRef m_llPort;
|
2017-05-07 21:24:00 +00:00
|
|
|
IOObjectPointer<io_iterator_t> m_llAddNotif, m_llRemoveNotif;
|
|
|
|
IOObjectPointer<io_iterator_t> m_hidAddNotif, m_hidRemoveNotif;
|
2015-04-21 04:02:43 +00:00
|
|
|
bool m_scanningEnabled;
|
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
static void devicesConnectedUSBLL(HIDListenerIOKit* listener,
|
2015-05-01 01:11:25 +00:00
|
|
|
io_iterator_t iterator)
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
2017-05-07 21:24:00 +00:00
|
|
|
while (IOObjectPointer<io_service_t> obj = IOIteratorNext(iterator))
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
io_string_t devPath;
|
2017-05-07 21:24:00 +00:00
|
|
|
if (IORegistryEntryGetPath(obj.get(), kIOServicePlane, devPath) != 0)
|
2015-04-24 00:24:15 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!listener->m_scanningEnabled ||
|
|
|
|
listener->m_finder._hasToken(devPath))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
UInt16 vid, pid;
|
|
|
|
char vstr[128] = {0};
|
|
|
|
char pstr[128] = {0};
|
2017-05-07 21:24:00 +00:00
|
|
|
{
|
|
|
|
IOCFPluginPointer devServ;
|
|
|
|
SInt32 score;
|
|
|
|
IOReturn err;
|
|
|
|
err = IOCreatePlugInInterfaceForService(obj.get(), kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &devServ, &score);
|
|
|
|
if (err != kIOReturnSuccess)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "unable to open IOKit plugin interface\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
IUnknownPointer<IOUSBDeviceInterface182> dev;
|
|
|
|
err = devServ.As(&dev, kIOUSBDeviceInterfaceID182);
|
|
|
|
if (err != kIOReturnSuccess)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "unable to open IOKit device interface\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->GetDeviceVendor(dev.storage(), &vid);
|
|
|
|
dev->GetDeviceProduct(dev.storage(), &pid);
|
|
|
|
|
|
|
|
UInt8 vstridx, pstridx;
|
|
|
|
dev->USBGetManufacturerStringIndex(dev.storage(), &vstridx);
|
|
|
|
dev->USBGetProductStringIndex(dev.storage(), &pstridx);
|
|
|
|
|
|
|
|
getUSBStringDescriptor(dev, vstridx, vstr);
|
|
|
|
getUSBStringDescriptor(dev, pstridx, pstr);
|
|
|
|
}
|
2015-04-24 00:24:15 +00:00
|
|
|
|
2017-05-07 21:24:00 +00:00
|
|
|
if (!listener->m_finder._insertToken(std::make_unique<DeviceToken>(DeviceType::USB,
|
|
|
|
vid, pid, vstr, pstr, devPath)))
|
2015-05-01 01:11:25 +00:00
|
|
|
{
|
|
|
|
/* Matched-insertion failed; see if generic HID interface is available */
|
|
|
|
/* TODO: Do */
|
|
|
|
}
|
2015-04-24 00:24:15 +00:00
|
|
|
|
|
|
|
//printf("ADDED %08X %s\n", obj, devPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
static void devicesDisconnectedUSBLL(HIDListenerIOKit* listener,
|
2015-05-01 01:11:25 +00:00
|
|
|
io_iterator_t iterator)
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
if (CFRunLoopGetCurrent() != listener->m_listenerRunLoop)
|
|
|
|
{
|
|
|
|
CFRunLoopPerformBlock(listener->m_listenerRunLoop, kCFRunLoopDefaultMode, ^{
|
2015-05-01 01:11:25 +00:00
|
|
|
devicesDisconnectedUSBLL(listener, iterator);
|
2015-04-24 00:24:15 +00:00
|
|
|
});
|
|
|
|
CFRunLoopWakeUp(listener->m_listenerRunLoop);
|
|
|
|
return;
|
|
|
|
}
|
2017-05-07 21:24:00 +00:00
|
|
|
while (IOObjectPointer<io_service_t> obj = IOIteratorNext(iterator))
|
|
|
|
{
|
|
|
|
io_string_t devPath;
|
|
|
|
if (IORegistryEntryGetPath(obj.get(), kIOServicePlane, devPath) != 0)
|
|
|
|
continue;
|
|
|
|
listener->m_finder._removeToken(devPath);
|
|
|
|
//printf("REMOVED %08X %s\n", obj, devPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void devicesConnectedHID(HIDListenerIOKit* listener,
|
|
|
|
io_iterator_t iterator)
|
|
|
|
{
|
|
|
|
while (IOObjectPointer<io_service_t> obj = IOIteratorNext(iterator))
|
|
|
|
{
|
|
|
|
io_string_t devPath;
|
|
|
|
if (IORegistryEntryGetPath(obj.get(), kIOServicePlane, devPath) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!listener->m_scanningEnabled ||
|
|
|
|
listener->m_finder._hasToken(devPath))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
unsigned vidv, pidv;
|
|
|
|
char vstr[128] = {0};
|
|
|
|
char pstr[128] = {0};
|
|
|
|
{
|
|
|
|
IOCFPluginPointer devServ;
|
|
|
|
SInt32 score;
|
|
|
|
IOReturn err;
|
|
|
|
err = IOCreatePlugInInterfaceForService(obj.get(), kIOHIDDeviceTypeID, kIOCFPlugInInterfaceID, &devServ, &score);
|
|
|
|
if (err != kIOReturnSuccess)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "unable to open IOKit plugin interface\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
IUnknownPointer<IOHIDDeviceDeviceInterface> dev;
|
|
|
|
err = devServ.As(&dev, kIOHIDDeviceDeviceInterfaceID);
|
|
|
|
if (err != kIOReturnSuccess)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "unable to open IOKit device interface\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Game controllers only */
|
|
|
|
CFPointer<CFNumberRef> usagePage;
|
|
|
|
dev->getProperty(dev.storage(), CFSTR(kIOHIDPrimaryUsagePageKey), &usagePage);
|
|
|
|
CFPointer<CFNumberRef> usage;
|
|
|
|
dev->getProperty(dev.storage(), CFSTR(kIOHIDPrimaryUsageKey), &usage);
|
|
|
|
int usagePageV, usageV;
|
|
|
|
CFNumberGetValue(usagePage.get(), kCFNumberIntType, &usagePageV);
|
|
|
|
CFNumberGetValue(usage.get(), kCFNumberIntType, &usageV);
|
|
|
|
if (usagePageV == kHIDPage_GenericDesktop)
|
|
|
|
{
|
|
|
|
if (usageV != kHIDUsage_GD_Joystick && usageV != kHIDUsage_GD_GamePad)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (usagePageV != kHIDPage_Game)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
CFPointer<CFNumberRef> vid, pid;
|
|
|
|
dev->getProperty(dev.storage(), CFSTR(kIOHIDVendorIDKey), &vid);
|
|
|
|
dev->getProperty(dev.storage(), CFSTR(kIOHIDProductIDKey), &pid);
|
|
|
|
CFNumberGetValue(vid.get(), kCFNumberIntType, &vidv);
|
|
|
|
CFNumberGetValue(pid.get(), kCFNumberIntType, &pidv);
|
|
|
|
|
|
|
|
CFPointer<CFStringRef> vstridx, pstridx;
|
|
|
|
dev->getProperty(dev.storage(), CFSTR(kIOHIDManufacturerKey), &vstridx);
|
|
|
|
dev->getProperty(dev.storage(), CFSTR(kIOHIDProductKey), &pstridx);
|
|
|
|
|
|
|
|
if (vstridx)
|
|
|
|
CFStringGetCString(vstridx.get(), vstr, 128, kCFStringEncodingUTF8);
|
|
|
|
if (pstridx)
|
|
|
|
CFStringGetCString(pstridx.get(), pstr, 128, kCFStringEncodingUTF8);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!listener->m_finder._insertToken(std::make_unique<DeviceToken>(DeviceType::HID,
|
|
|
|
vidv, pidv, vstr, pstr, devPath)))
|
|
|
|
{
|
|
|
|
/* Matched-insertion failed; see if generic HID interface is available */
|
|
|
|
/* TODO: Do */
|
|
|
|
}
|
|
|
|
|
|
|
|
//printf("ADDED %08X %s\n", obj, devPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void devicesDisconnectedHID(HIDListenerIOKit* listener,
|
|
|
|
io_iterator_t iterator)
|
|
|
|
{
|
|
|
|
if (CFRunLoopGetCurrent() != listener->m_listenerRunLoop)
|
|
|
|
{
|
|
|
|
CFRunLoopPerformBlock(listener->m_listenerRunLoop, kCFRunLoopDefaultMode, ^{
|
|
|
|
devicesDisconnectedHID(listener, iterator);
|
|
|
|
});
|
|
|
|
CFRunLoopWakeUp(listener->m_listenerRunLoop);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
while (IOObjectPointer<io_service_t> obj = IOIteratorNext(iterator))
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
io_string_t devPath;
|
2017-05-07 21:24:00 +00:00
|
|
|
if (IORegistryEntryGetPath(obj.get(), kIOServicePlane, devPath) != 0)
|
2015-04-24 00:24:15 +00:00
|
|
|
continue;
|
|
|
|
listener->m_finder._removeToken(devPath);
|
|
|
|
//printf("REMOVED %08X %s\n", obj, devPath);
|
|
|
|
}
|
2015-04-21 04:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2015-09-02 19:09:13 +00:00
|
|
|
HIDListenerIOKit(DeviceFinder& finder)
|
2015-04-21 04:02:43 +00:00
|
|
|
: m_finder(finder)
|
|
|
|
{
|
2015-04-23 00:46:32 +00:00
|
|
|
m_listenerRunLoop = CFRunLoopGetCurrent();
|
2015-04-24 00:24:15 +00:00
|
|
|
m_llPort = IONotificationPortCreate(kIOMasterPortDefault);
|
2015-05-01 01:11:25 +00:00
|
|
|
CFRunLoopSourceRef rlSrc = IONotificationPortGetRunLoopSource(m_llPort);
|
|
|
|
CFRunLoopAddSource(m_listenerRunLoop, rlSrc, kCFRunLoopDefaultMode);
|
|
|
|
m_scanningEnabled = true;
|
2017-05-07 21:24:00 +00:00
|
|
|
|
|
|
|
/* Register HID Matcher */
|
|
|
|
{
|
|
|
|
CFMutableDictionaryRef matchDict = IOServiceMatching("IOHIDDevice");
|
|
|
|
CFRetain(matchDict);
|
|
|
|
|
|
|
|
kern_return_t hidRet =
|
|
|
|
IOServiceAddMatchingNotification(m_llPort, kIOMatchedNotification, matchDict,
|
|
|
|
(IOServiceMatchingCallback)devicesConnectedHID, this, &m_hidAddNotif);
|
|
|
|
if (hidRet == kIOReturnSuccess)
|
|
|
|
devicesConnectedHID(this, m_hidAddNotif.get());
|
|
|
|
|
|
|
|
hidRet =
|
|
|
|
IOServiceAddMatchingNotification(m_llPort, kIOTerminatedNotification, matchDict,
|
|
|
|
(IOServiceMatchingCallback)devicesDisconnectedHID, this, &m_hidRemoveNotif);
|
|
|
|
if (hidRet == kIOReturnSuccess)
|
|
|
|
devicesDisconnectedHID(this, m_hidRemoveNotif.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Register Low-Level USB Matcher */
|
|
|
|
{
|
|
|
|
CFMutableDictionaryRef matchDict = IOServiceMatching(kIOUSBDeviceClassName);
|
|
|
|
CFRetain(matchDict);
|
|
|
|
|
|
|
|
kern_return_t llRet =
|
|
|
|
IOServiceAddMatchingNotification(m_llPort, kIOMatchedNotification, matchDict,
|
|
|
|
(IOServiceMatchingCallback)devicesConnectedUSBLL, this, &m_llAddNotif);
|
|
|
|
if (llRet == kIOReturnSuccess)
|
|
|
|
devicesConnectedUSBLL(this, m_llAddNotif.get());
|
|
|
|
|
|
|
|
llRet =
|
|
|
|
IOServiceAddMatchingNotification(m_llPort, kIOTerminatedNotification, matchDict,
|
|
|
|
(IOServiceMatchingCallback)devicesDisconnectedUSBLL, this, &m_llRemoveNotif);
|
|
|
|
if (llRet == kIOReturnSuccess)
|
|
|
|
devicesDisconnectedUSBLL(this, m_llRemoveNotif.get());
|
|
|
|
}
|
2015-04-24 00:24:15 +00:00
|
|
|
|
2015-04-21 04:02:43 +00:00
|
|
|
m_scanningEnabled = false;
|
|
|
|
}
|
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
~HIDListenerIOKit()
|
2015-04-21 04:02:43 +00:00
|
|
|
{
|
2015-11-01 00:06:56 +00:00
|
|
|
//CFRunLoopRemoveSource(m_listenerRunLoop, IONotificationPortGetRunLoopSource(m_llPort), kCFRunLoopDefaultMode);
|
2015-04-24 00:24:15 +00:00
|
|
|
IONotificationPortDestroy(m_llPort);
|
2015-04-21 04:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Automatic device scanning */
|
|
|
|
bool startScanning()
|
|
|
|
{
|
|
|
|
m_scanningEnabled = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
bool stopScanning()
|
|
|
|
{
|
|
|
|
m_scanningEnabled = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Manual device scanning */
|
|
|
|
bool scanNow()
|
|
|
|
{
|
2017-05-07 21:24:00 +00:00
|
|
|
IOObjectPointer<io_iterator_t> iter;
|
2015-05-01 01:11:25 +00:00
|
|
|
if (IOServiceGetMatchingServices(kIOMasterPortDefault,
|
|
|
|
IOServiceMatching(kIOUSBDeviceClassName), &iter) == kIOReturnSuccess)
|
|
|
|
{
|
2017-05-07 21:24:00 +00:00
|
|
|
devicesConnectedUSBLL(this, iter.get());
|
2015-05-01 01:11:25 +00:00
|
|
|
}
|
2015-04-21 04:02:43 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
IHIDListener* IHIDListenerNew(DeviceFinder& finder)
|
2015-04-21 04:02:43 +00:00
|
|
|
{
|
2015-09-02 19:09:13 +00:00
|
|
|
return new HIDListenerIOKit(finder);
|
2015-04-21 04:02:43 +00:00
|
|
|
}
|
|
|
|
|
2015-04-30 07:01:55 +00:00
|
|
|
}
|