2015-04-19 20:16:50 +00:00
|
|
|
#include "IHIDDevice.hpp"
|
2015-09-02 19:09:13 +00:00
|
|
|
#include "boo/inputdev/DeviceToken.hpp"
|
|
|
|
#include "boo/inputdev/DeviceBase.hpp"
|
2015-04-21 04:02:43 +00:00
|
|
|
#include <IOKit/hid/IOHIDLib.h>
|
2017-05-07 21:24:00 +00:00
|
|
|
#include <IOKit/hid/IOHIDDevicePlugin.h>
|
|
|
|
#include <IOKit/hid/IOHIDDevice.h>
|
2015-04-24 00:24:15 +00:00
|
|
|
#include <IOKit/usb/IOUSBLib.h>
|
|
|
|
#include <IOKit/IOCFPlugIn.h>
|
2017-05-07 21:24:00 +00:00
|
|
|
#include "IOKitPointer.hpp"
|
2015-04-23 00:46:32 +00:00
|
|
|
#include <thread>
|
|
|
|
|
2015-04-30 07:01:55 +00:00
|
|
|
namespace boo
|
|
|
|
{
|
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
class HIDDeviceIOKit : public IHIDDevice
|
2015-04-21 04:02:43 +00:00
|
|
|
{
|
2015-09-02 19:09:13 +00:00
|
|
|
DeviceToken& m_token;
|
|
|
|
DeviceBase& m_devImp;
|
2015-04-24 00:24:15 +00:00
|
|
|
|
2017-05-07 21:24:00 +00:00
|
|
|
IUnknownPointer<IOUSBInterfaceInterface> m_usbIntf;
|
2015-04-24 00:24:15 +00:00
|
|
|
uint8_t m_usbIntfInPipe = 0;
|
|
|
|
uint8_t m_usbIntfOutPipe = 0;
|
2017-05-07 21:24:00 +00:00
|
|
|
IUnknownPointer<IOHIDDeviceDeviceInterface> m_hidIntf;
|
2015-04-24 00:24:15 +00:00
|
|
|
bool m_runningTransferLoop = false;
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
const std::string& m_devPath;
|
2015-04-23 00:46:32 +00:00
|
|
|
std::mutex m_initMutex;
|
|
|
|
std::condition_variable m_initCond;
|
2016-10-11 01:20:39 +00:00
|
|
|
std::thread m_thread;
|
|
|
|
|
2015-05-10 07:02:18 +00:00
|
|
|
bool _sendUSBInterruptTransfer(const uint8_t* data, size_t length)
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
if (m_usbIntf)
|
|
|
|
{
|
2017-05-07 21:24:00 +00:00
|
|
|
IOReturn res = m_usbIntf->WritePipe(m_usbIntf.storage(), m_usbIntfOutPipe, (void*)data, length);
|
2015-04-24 00:24:15 +00:00
|
|
|
return res == kIOReturnSuccess;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-05-10 07:02:18 +00:00
|
|
|
size_t _receiveUSBInterruptTransfer(uint8_t* data, size_t length)
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
if (m_usbIntf)
|
|
|
|
{
|
|
|
|
UInt32 readSize = length;
|
2017-05-07 21:24:00 +00:00
|
|
|
IOReturn res = m_usbIntf->ReadPipe(m_usbIntf.storage(), m_usbIntfInPipe, data, &readSize);
|
|
|
|
if (res != kIOReturnSuccess)
|
|
|
|
return 0;
|
|
|
|
return readSize;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _sendHIDReport(const uint8_t* data, size_t length, HIDReportType tp, uint32_t message)
|
|
|
|
{
|
|
|
|
if (m_hidIntf)
|
|
|
|
{
|
|
|
|
IOReturn res = m_hidIntf->setReport(m_hidIntf.storage(), IOHIDReportType(tp), message, data, length,
|
|
|
|
1000, nullptr, nullptr, 0);
|
|
|
|
return res == kIOReturnSuccess;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t _receiveHIDReport(uint8_t* data, size_t length, HIDReportType tp, uint32_t message)
|
|
|
|
{
|
|
|
|
if (m_hidIntf)
|
|
|
|
{
|
|
|
|
CFIndex readSize = length;
|
|
|
|
IOReturn res = m_hidIntf->getReport(m_hidIntf.storage(), IOHIDReportType(tp), message, data, &readSize,
|
|
|
|
1000, nullptr, nullptr, 0);
|
2015-04-24 00:24:15 +00:00
|
|
|
if (res != kIOReturnSuccess)
|
|
|
|
return 0;
|
|
|
|
return readSize;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
static void _threadProcUSBLL(HIDDeviceIOKit* device)
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
char thrName[128];
|
|
|
|
snprintf(thrName, 128, "%s Transfer Thread", device->m_token.getProductName().c_str());
|
|
|
|
pthread_setname_np(thrName);
|
2015-05-03 06:40:20 +00:00
|
|
|
char errStr[256];
|
2015-04-24 00:24:15 +00:00
|
|
|
std::unique_lock<std::mutex> lk(device->m_initMutex);
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* Get the HID element's parent (USB interrupt transfer-interface) */
|
2017-05-07 21:24:00 +00:00
|
|
|
IOObjectPointer<io_iterator_t> devIter;
|
|
|
|
IOObjectPointer<io_registry_entry_t> devEntry = IORegistryEntryFromPath(kIOMasterPortDefault, device->m_devPath.c_str());
|
2015-04-24 00:24:15 +00:00
|
|
|
IORegistryEntryGetChildIterator(devEntry, kIOServicePlane, &devIter);
|
2017-05-07 21:24:00 +00:00
|
|
|
IOObjectPointer<io_object_t> interfaceEntry;
|
|
|
|
while (IOObjectPointer<io_service_t> obj = IOIteratorNext(devIter))
|
2015-04-24 00:24:15 +00:00
|
|
|
{
|
|
|
|
if (IOObjectConformsTo(obj, kIOUSBInterfaceClassName))
|
|
|
|
interfaceEntry = obj;
|
|
|
|
else
|
|
|
|
IOObjectRelease(obj);
|
|
|
|
}
|
|
|
|
if (!interfaceEntry)
|
|
|
|
{
|
2015-05-03 06:40:20 +00:00
|
|
|
snprintf(errStr, 256, "Unable to find interface for %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(),
|
|
|
|
device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
2015-04-24 00:24:15 +00:00
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* IOKit Plugin COM interface (WTF Apple???) */
|
2017-05-07 21:24:00 +00:00
|
|
|
IOCFPluginPointer iodev;
|
2015-04-24 00:24:15 +00:00
|
|
|
SInt32 score;
|
|
|
|
IOReturn err;
|
|
|
|
err = IOCreatePlugInInterfaceForService(interfaceEntry,
|
|
|
|
kIOUSBInterfaceUserClientTypeID,
|
|
|
|
kIOCFPlugInInterfaceID,
|
|
|
|
&iodev,
|
|
|
|
&score);
|
|
|
|
if (err)
|
|
|
|
{
|
2015-05-03 06:40:20 +00:00
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
2015-04-24 00:24:15 +00:00
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* USB interface function-pointer table */
|
2017-05-07 21:24:00 +00:00
|
|
|
IUnknownPointer<IOUSBInterfaceInterface> intf;
|
|
|
|
err = iodev.As(&intf, kIOUSBInterfaceInterfaceID);
|
2015-04-24 00:24:15 +00:00
|
|
|
if (err)
|
|
|
|
{
|
2015-05-03 06:40:20 +00:00
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
2015-04-24 00:24:15 +00:00
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* Obtain exclusive lock on device */
|
|
|
|
device->m_usbIntf = intf;
|
2017-05-07 21:24:00 +00:00
|
|
|
err = intf->USBInterfaceOpen(intf.storage());
|
2015-04-24 00:24:15 +00:00
|
|
|
if (err != kIOReturnSuccess)
|
|
|
|
{
|
|
|
|
if (err == kIOReturnExclusiveAccess)
|
2015-05-03 06:40:20 +00:00
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s: someone else using it\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
|
|
|
}
|
2015-04-24 00:24:15 +00:00
|
|
|
else
|
2015-05-03 06:40:20 +00:00
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
|
|
|
}
|
2015-04-24 00:24:15 +00:00
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* Determine pipe indices for interrupt I/O */
|
|
|
|
UInt8 numEndpoints = 0;
|
2017-05-07 21:24:00 +00:00
|
|
|
err = intf->GetNumEndpoints(intf.storage(), &numEndpoints);
|
2015-04-24 00:24:15 +00:00
|
|
|
for (int i=1 ; i<numEndpoints+1 ; ++i)
|
|
|
|
{
|
|
|
|
UInt8 dir, num, tType, interval;
|
|
|
|
UInt16 mPacketSz;
|
2017-05-07 21:24:00 +00:00
|
|
|
err = intf->GetPipeProperties(intf.storage(), i, &dir, &num, &tType, &mPacketSz, &interval);
|
2015-04-24 00:24:15 +00:00
|
|
|
if (tType == kUSBInterrupt)
|
|
|
|
{
|
|
|
|
if (dir == kUSBIn)
|
|
|
|
device->m_usbIntfInPipe = num;
|
|
|
|
else if (dir == kUSBOut)
|
|
|
|
device->m_usbIntfOutPipe = num;
|
|
|
|
}
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* Return control to main thread */
|
|
|
|
device->m_runningTransferLoop = true;
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* Start transfer loop */
|
2015-05-01 01:11:25 +00:00
|
|
|
device->m_devImp.initialCycle();
|
2015-04-24 00:24:15 +00:00
|
|
|
while (device->m_runningTransferLoop)
|
|
|
|
device->m_devImp.transferCycle();
|
2015-04-26 08:25:44 +00:00
|
|
|
device->m_devImp.finalCycle();
|
|
|
|
|
2015-04-24 00:24:15 +00:00
|
|
|
/* Cleanup */
|
2017-05-07 21:24:00 +00:00
|
|
|
intf->USBInterfaceClose(intf.storage());
|
|
|
|
device->m_usbIntf = nullptr;
|
2015-04-24 00:24:15 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
static void _threadProcBTLL(HIDDeviceIOKit* device)
|
2015-05-01 01:11:25 +00:00
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lk(device->m_initMutex);
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-05-01 01:11:25 +00:00
|
|
|
/* Return control to main thread */
|
|
|
|
device->m_runningTransferLoop = true;
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-05-01 01:11:25 +00:00
|
|
|
/* Start transfer loop */
|
|
|
|
device->m_devImp.initialCycle();
|
|
|
|
while (device->m_runningTransferLoop)
|
|
|
|
device->m_devImp.transferCycle();
|
|
|
|
device->m_devImp.finalCycle();
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-05-01 01:11:25 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2017-05-07 21:24:00 +00:00
|
|
|
static void _hidReportCb(void * _Nullable context,
|
|
|
|
IOReturn,
|
|
|
|
void * _Nullable,
|
|
|
|
IOHIDReportType type,
|
|
|
|
uint32_t reportID,
|
|
|
|
uint8_t * report,
|
|
|
|
CFIndex reportLength)
|
|
|
|
{
|
|
|
|
reinterpret_cast<DeviceBase*>(context)->receivedHIDReport(report, reportLength, HIDReportType(type), reportID);
|
|
|
|
}
|
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
static void _threadProcHID(HIDDeviceIOKit* device)
|
2015-04-23 00:46:32 +00:00
|
|
|
{
|
2017-05-07 21:24:00 +00:00
|
|
|
char thrName[128];
|
|
|
|
snprintf(thrName, 128, "%s Transfer Thread", device->m_token.getProductName().c_str());
|
|
|
|
pthread_setname_np(thrName);
|
|
|
|
char errStr[256];
|
2015-05-01 01:11:25 +00:00
|
|
|
std::unique_lock<std::mutex> lk(device->m_initMutex);
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2017-05-07 21:24:00 +00:00
|
|
|
/* Get the HID element's object (HID device interface) */
|
|
|
|
IOObjectPointer<io_service_t> interfaceEntry = IORegistryEntryFromPath(kIOMasterPortDefault, device->m_devPath.c_str());
|
|
|
|
if (!IOObjectConformsTo(interfaceEntry.get(), "IOHIDDevice"))
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to find interface for %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(),
|
|
|
|
device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* IOKit Plugin COM interface (WTF Apple???) */
|
|
|
|
IOCFPluginPointer iodev;
|
|
|
|
SInt32 score;
|
|
|
|
IOReturn err;
|
|
|
|
err = IOCreatePlugInInterfaceForService(interfaceEntry.get(),
|
|
|
|
kIOHIDDeviceTypeID,
|
|
|
|
kIOCFPlugInInterfaceID,
|
|
|
|
&iodev,
|
|
|
|
&score);
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* HID interface function-pointer table */
|
|
|
|
IUnknownPointer<IOHIDDeviceDeviceInterface> intf;
|
|
|
|
err = iodev.As(&intf, kIOHIDDeviceDeviceInterfaceID);
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open device */
|
|
|
|
device->m_hidIntf = intf;
|
|
|
|
err = intf->open(intf.storage(), kIOHIDOptionsTypeNone);
|
|
|
|
if (err != kIOReturnSuccess)
|
|
|
|
{
|
|
|
|
if (err == kIOReturnExclusiveAccess)
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s: someone else using it\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s\n",
|
|
|
|
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
|
|
|
|
device->m_devImp.deviceError(errStr);
|
|
|
|
}
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Register input buffer */
|
|
|
|
std::unique_ptr<uint8_t[]> buffer;
|
|
|
|
if (size_t bufSize = device->m_devImp.getInputBufferSize())
|
|
|
|
{
|
|
|
|
buffer = std::unique_ptr<uint8_t[]>(new uint8_t[bufSize]);
|
|
|
|
CFTypeRef source;
|
|
|
|
device->m_hidIntf->getAsyncEventSource(device->m_hidIntf.storage(), &source);
|
|
|
|
device->m_hidIntf->setInputReportCallback(device->m_hidIntf.storage(), buffer.get(),
|
|
|
|
bufSize, _hidReportCb, &device->m_devImp, 0);
|
|
|
|
CFRunLoopRef rl = CFRunLoopGetCurrent();
|
|
|
|
CFRunLoopAddSource(rl, CFRunLoopSourceRef(source), kCFRunLoopDefaultMode);
|
|
|
|
CFRunLoopWakeUp(rl);
|
|
|
|
}
|
|
|
|
|
2015-05-01 01:11:25 +00:00
|
|
|
/* Return control to main thread */
|
|
|
|
device->m_runningTransferLoop = true;
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-05-01 01:11:25 +00:00
|
|
|
/* Start transfer loop */
|
|
|
|
device->m_devImp.initialCycle();
|
|
|
|
while (device->m_runningTransferLoop)
|
2017-05-07 21:24:00 +00:00
|
|
|
{
|
|
|
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
2015-05-01 01:11:25 +00:00
|
|
|
device->m_devImp.transferCycle();
|
2017-05-07 21:24:00 +00:00
|
|
|
}
|
2015-05-01 01:11:25 +00:00
|
|
|
device->m_devImp.finalCycle();
|
2017-05-07 21:24:00 +00:00
|
|
|
|
|
|
|
/* Cleanup */
|
|
|
|
intf->close(intf.storage(), kIOHIDOptionsTypeNone);
|
|
|
|
device->m_hidIntf = nullptr;
|
2015-04-23 00:46:32 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-22 21:48:23 +00:00
|
|
|
void _deviceDisconnected()
|
|
|
|
{
|
2015-04-24 00:24:15 +00:00
|
|
|
m_runningTransferLoop = false;
|
2015-04-23 00:46:32 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-21 04:02:43 +00:00
|
|
|
public:
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
HIDDeviceIOKit(DeviceToken& token, DeviceBase& devImp)
|
2015-04-23 00:46:32 +00:00
|
|
|
: m_token(token),
|
2015-04-24 00:24:15 +00:00
|
|
|
m_devImp(devImp),
|
|
|
|
m_devPath(token.getDevicePath())
|
2015-04-21 04:02:43 +00:00
|
|
|
{
|
2015-04-23 00:46:32 +00:00
|
|
|
std::unique_lock<std::mutex> lk(m_initMutex);
|
2017-05-07 21:24:00 +00:00
|
|
|
DeviceType dType = token.getDeviceType();
|
|
|
|
if (dType == DeviceType::USB)
|
2016-10-11 01:20:39 +00:00
|
|
|
m_thread = std::thread(_threadProcUSBLL, this);
|
2017-05-07 21:24:00 +00:00
|
|
|
else if (dType == DeviceType::Bluetooth)
|
2016-10-11 01:20:39 +00:00
|
|
|
m_thread = std::thread(_threadProcBTLL, this);
|
2017-05-07 21:24:00 +00:00
|
|
|
else if (dType == DeviceType::HID)
|
2016-10-11 01:20:39 +00:00
|
|
|
m_thread = std::thread(_threadProcHID, this);
|
2015-04-24 00:24:15 +00:00
|
|
|
else
|
2015-09-02 19:09:13 +00:00
|
|
|
{
|
|
|
|
fprintf(stderr, "invalid token supplied to device constructor\n");
|
|
|
|
return;
|
|
|
|
}
|
2015-04-23 00:46:32 +00:00
|
|
|
m_initCond.wait(lk);
|
2015-04-21 04:02:43 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-09-02 19:09:13 +00:00
|
|
|
~HIDDeviceIOKit()
|
2015-04-21 04:02:43 +00:00
|
|
|
{
|
2015-04-24 00:24:15 +00:00
|
|
|
m_runningTransferLoop = false;
|
2016-10-11 01:20:39 +00:00
|
|
|
if (m_thread.joinable())
|
|
|
|
m_thread.join();
|
2015-04-21 04:02:43 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-22 21:48:23 +00:00
|
|
|
|
2015-04-21 04:02:43 +00:00
|
|
|
};
|
|
|
|
|
2017-05-07 21:24:00 +00:00
|
|
|
std::unique_ptr<IHIDDevice> IHIDDeviceNew(DeviceToken& token, DeviceBase& devImp)
|
2015-04-21 04:02:43 +00:00
|
|
|
{
|
2017-05-07 21:24:00 +00:00
|
|
|
return std::make_unique<HIDDeviceIOKit>(token, devImp);
|
2015-04-21 04:02:43 +00:00
|
|
|
}
|
2015-04-30 07:01:55 +00:00
|
|
|
|
|
|
|
}
|