2015-04-19 20:16:50 +00:00
|
|
|
#include "IHIDDevice.hpp"
|
2015-08-18 22:43:30 +00:00
|
|
|
#include "boo/inputdev/DeviceToken.hpp"
|
|
|
|
#include "boo/inputdev/DeviceBase.hpp"
|
2015-04-29 07:56:16 +00:00
|
|
|
#include <thread>
|
|
|
|
#include <mutex>
|
|
|
|
#include <condition_variable>
|
|
|
|
|
2017-12-29 07:54:26 +00:00
|
|
|
#include <cstdio>
|
2015-04-29 07:56:16 +00:00
|
|
|
#include <libudev.h>
|
|
|
|
#include <stropts.h>
|
|
|
|
#include <linux/usb/ch9.h>
|
|
|
|
#include <linux/usbdevice_fs.h>
|
2017-05-09 03:37:12 +00:00
|
|
|
#include <linux/input.h>
|
|
|
|
#include <linux/hidraw.h>
|
2015-04-29 07:56:16 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <unistd.h>
|
2017-12-29 07:54:26 +00:00
|
|
|
#include <cstring>
|
2017-09-16 01:55:41 +00:00
|
|
|
#include "boo/inputdev/HIDParser.hpp"
|
2015-04-29 07:56:16 +00:00
|
|
|
|
2015-04-29 10:24:39 +00:00
|
|
|
namespace boo
|
|
|
|
{
|
|
|
|
|
2015-04-30 07:01:55 +00:00
|
|
|
udev* GetUdev();
|
2015-04-29 07:56:16 +00:00
|
|
|
|
2015-04-29 10:24:39 +00:00
|
|
|
/*
|
|
|
|
* Reference: http://tali.admingilde.org/linux-docbook/usb/ch07s06.html
|
2015-04-29 07:56:16 +00:00
|
|
|
*/
|
|
|
|
|
2015-08-18 19:40:26 +00:00
|
|
|
class HIDDeviceUdev final : public IHIDDevice
|
2015-04-29 07:56:16 +00:00
|
|
|
{
|
2015-08-18 19:40:26 +00:00
|
|
|
DeviceToken& m_token;
|
2017-09-16 01:55:41 +00:00
|
|
|
std::shared_ptr<DeviceBase> m_devImp;
|
2015-04-29 07:56:16 +00:00
|
|
|
|
|
|
|
int m_devFd = 0;
|
|
|
|
unsigned m_usbIntfInPipe = 0;
|
|
|
|
unsigned m_usbIntfOutPipe = 0;
|
|
|
|
bool m_runningTransferLoop = false;
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2017-11-13 06:13:32 +00:00
|
|
|
std::string_view m_devPath;
|
2015-04-29 07:56:16 +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-29 07:56:16 +00:00
|
|
|
{
|
|
|
|
if (m_devFd)
|
|
|
|
{
|
|
|
|
usbdevfs_bulktransfer xfer =
|
|
|
|
{
|
2016-02-18 14:25:47 +00:00
|
|
|
m_usbIntfOutPipe | USB_DIR_OUT,
|
2015-04-29 07:56:16 +00:00
|
|
|
(unsigned)length,
|
2016-02-18 14:14:59 +00:00
|
|
|
30,
|
2015-04-29 07:56:16 +00:00
|
|
|
(void*)data
|
|
|
|
};
|
|
|
|
int ret = ioctl(m_devFd, USBDEVFS_BULK, &xfer);
|
2015-04-30 07:01:55 +00:00
|
|
|
if (ret != (int)length)
|
2015-04-29 07:56:16 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2015-05-15 01:16:36 +00:00
|
|
|
|
2015-05-10 07:02:18 +00:00
|
|
|
size_t _receiveUSBInterruptTransfer(uint8_t* data, size_t length)
|
2015-04-29 07:56:16 +00:00
|
|
|
{
|
|
|
|
if (m_devFd)
|
|
|
|
{
|
|
|
|
usbdevfs_bulktransfer xfer =
|
|
|
|
{
|
|
|
|
m_usbIntfInPipe | USB_DIR_IN,
|
|
|
|
(unsigned)length,
|
2016-02-18 14:14:59 +00:00
|
|
|
30,
|
2015-04-29 07:56:16 +00:00
|
|
|
data
|
|
|
|
};
|
|
|
|
return ioctl(m_devFd, USBDEVFS_BULK, &xfer);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2017-09-16 01:55:41 +00:00
|
|
|
static void _threadProcUSBLL(std::shared_ptr<HIDDeviceUdev> device)
|
2015-04-29 07:56:16 +00:00
|
|
|
{
|
2017-05-12 02:52:45 +00:00
|
|
|
int i;
|
2015-05-03 06:40:20 +00:00
|
|
|
char errStr[256];
|
2015-04-29 07:56:16 +00:00
|
|
|
std::unique_lock<std::mutex> lk(device->m_initMutex);
|
2017-11-13 07:19:49 +00:00
|
|
|
udev_device* udevDev = udev_device_new_from_syspath(GetUdev(), device->m_devPath.data());
|
2015-04-29 07:56:16 +00:00
|
|
|
|
2015-05-03 06:40:20 +00:00
|
|
|
/* Get device file */
|
2015-04-30 23:17:46 +00:00
|
|
|
const char* dp = udev_device_get_devnode(udevDev);
|
2015-08-19 05:47:46 +00:00
|
|
|
int fd = open(dp, O_RDWR);
|
|
|
|
if (fd < 0)
|
2015-04-30 07:01:55 +00:00
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s: %s\n",
|
2017-11-13 07:19:49 +00:00
|
|
|
device->m_token.getProductName().data(), dp, strerror(errno));
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->deviceError(errStr);
|
2015-04-30 07:01:55 +00:00
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
2015-04-30 23:17:46 +00:00
|
|
|
udev_device_unref(udevDev);
|
2015-04-30 07:01:55 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-08-19 05:47:46 +00:00
|
|
|
device->m_devFd = fd;
|
2015-05-10 07:02:18 +00:00
|
|
|
usb_device_descriptor devDesc = {};
|
2015-08-19 05:47:46 +00:00
|
|
|
read(fd, &devDesc, 1);
|
|
|
|
read(fd, &devDesc.bDescriptorType, devDesc.bLength-1);
|
2015-04-29 07:56:16 +00:00
|
|
|
if (devDesc.bNumConfigurations)
|
|
|
|
{
|
2015-05-10 07:02:18 +00:00
|
|
|
usb_config_descriptor confDesc = {};
|
2015-08-19 05:47:46 +00:00
|
|
|
read(fd, &confDesc, 1);
|
|
|
|
read(fd, &confDesc.bDescriptorType, confDesc.bLength-1);
|
2015-04-29 07:56:16 +00:00
|
|
|
if (confDesc.bNumInterfaces)
|
|
|
|
{
|
2015-05-10 07:02:18 +00:00
|
|
|
usb_interface_descriptor intfDesc = {};
|
2015-08-19 05:47:46 +00:00
|
|
|
read(fd, &intfDesc, 1);
|
|
|
|
read(fd, &intfDesc.bDescriptorType, intfDesc.bLength-1);
|
2015-04-29 07:56:16 +00:00
|
|
|
for (i=0 ; i<intfDesc.bNumEndpoints+1 ; ++i)
|
|
|
|
{
|
2015-05-10 07:02:18 +00:00
|
|
|
usb_endpoint_descriptor endpDesc = {};
|
2015-08-19 05:47:46 +00:00
|
|
|
read(fd, &endpDesc, 1);
|
|
|
|
read(fd, &endpDesc.bDescriptorType, endpDesc.bLength-1);
|
2015-04-29 07:56:16 +00:00
|
|
|
if ((endpDesc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
|
|
|
|
{
|
|
|
|
if ((endpDesc.bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
|
|
|
|
device->m_usbIntfInPipe = endpDesc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
|
|
|
|
else if ((endpDesc.bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
|
|
|
|
device->m_usbIntfOutPipe = endpDesc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Request that kernel disconnects existing driver */
|
|
|
|
usbdevfs_ioctl disconnectReq = {
|
|
|
|
0,
|
|
|
|
USBDEVFS_DISCONNECT,
|
|
|
|
NULL
|
|
|
|
};
|
2015-08-19 05:47:46 +00:00
|
|
|
ioctl(fd, USBDEVFS_IOCTL, &disconnectReq);
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-29 07:56:16 +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-29 07:56:16 +00:00
|
|
|
/* Start transfer loop */
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->initialCycle();
|
2015-04-29 07:56:16 +00:00
|
|
|
while (device->m_runningTransferLoop)
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->transferCycle();
|
|
|
|
device->m_devImp->finalCycle();
|
2015-04-29 07:56:16 +00:00
|
|
|
|
|
|
|
/* Cleanup */
|
2015-08-19 05:47:46 +00:00
|
|
|
close(fd);
|
2015-04-30 07:01:55 +00:00
|
|
|
device->m_devFd = 0;
|
2015-04-30 23:17:46 +00:00
|
|
|
udev_device_unref(udevDev);
|
2015-04-29 07:56:16 +00:00
|
|
|
}
|
2015-04-30 23:17:46 +00:00
|
|
|
|
2017-09-16 01:55:41 +00:00
|
|
|
static void _threadProcBTLL(std::shared_ptr<HIDDeviceUdev> device)
|
2015-04-30 23:17:46 +00:00
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lk(device->m_initMutex);
|
2017-11-13 07:19:49 +00:00
|
|
|
udev_device* udevDev = udev_device_new_from_syspath(GetUdev(), device->m_devPath.data());
|
2015-04-30 23:17:46 +00:00
|
|
|
|
|
|
|
/* Return control to main thread */
|
|
|
|
device->m_runningTransferLoop = true;
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
|
|
|
|
/* Start transfer loop */
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->initialCycle();
|
2015-04-30 23:17:46 +00:00
|
|
|
while (device->m_runningTransferLoop)
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->transferCycle();
|
|
|
|
device->m_devImp->finalCycle();
|
2015-04-30 23:17:46 +00:00
|
|
|
|
|
|
|
udev_device_unref(udevDev);
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2017-09-16 01:55:41 +00:00
|
|
|
int m_reportDescSz;
|
|
|
|
|
|
|
|
static void _threadProcHID(std::shared_ptr<HIDDeviceUdev> device)
|
2015-04-29 07:56:16 +00:00
|
|
|
{
|
2017-05-09 03:37:12 +00:00
|
|
|
char errStr[256];
|
2015-04-30 23:17:46 +00:00
|
|
|
std::unique_lock<std::mutex> lk(device->m_initMutex);
|
2017-11-13 07:19:49 +00:00
|
|
|
udev_device* udevDev = udev_device_new_from_syspath(GetUdev(), device->m_devPath.data());
|
2015-04-30 23:17:46 +00:00
|
|
|
|
2017-05-09 03:37:12 +00:00
|
|
|
/* Get device file */
|
|
|
|
const char* dp = udev_device_get_devnode(udevDev);
|
|
|
|
int fd = open(dp, O_RDWR | O_NONBLOCK);
|
|
|
|
if (fd < 0)
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to open %s@%s: %s\n",
|
2017-11-13 07:19:49 +00:00
|
|
|
device->m_token.getProductName().data(), dp, strerror(errno));
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->deviceError(errStr);
|
2017-05-09 03:37:12 +00:00
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
udev_device_unref(udevDev);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
device->m_devFd = fd;
|
|
|
|
|
2015-04-30 23:17:46 +00:00
|
|
|
/* Return control to main thread */
|
|
|
|
device->m_runningTransferLoop = true;
|
|
|
|
lk.unlock();
|
|
|
|
device->m_initCond.notify_one();
|
|
|
|
|
2017-09-16 01:55:41 +00:00
|
|
|
/* Report descriptor size */
|
|
|
|
int reportDescSize;
|
|
|
|
if (ioctl(fd, HIDIOCGRDESCSIZE, &reportDescSize) == -1)
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to ioctl(HIDIOCGRDESCSIZE) %s@%s: %s\n",
|
2017-11-13 07:19:49 +00:00
|
|
|
device->m_token.getProductName().data(), dp, strerror(errno));
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->deviceError(errStr);
|
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get report descriptor */
|
|
|
|
hidraw_report_descriptor reportDesc;
|
|
|
|
reportDesc.size = reportDescSize;
|
|
|
|
if (ioctl(fd, HIDIOCGRDESC, &reportDesc) == -1)
|
|
|
|
{
|
|
|
|
snprintf(errStr, 256, "Unable to ioctl(HIDIOCGRDESC) %s@%s: %s\n",
|
2017-11-13 07:19:49 +00:00
|
|
|
device->m_token.getProductName().data(), dp, strerror(errno));
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->deviceError(errStr);
|
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
size_t readSz = HIDParser::CalculateMaxInputReportSize(reportDesc.value, reportDesc.size);
|
2017-05-09 03:37:12 +00:00
|
|
|
std::unique_ptr<uint8_t[]> readBuf(new uint8_t[readSz]);
|
|
|
|
|
2015-04-30 23:17:46 +00:00
|
|
|
/* Start transfer loop */
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->initialCycle();
|
2015-04-30 23:17:46 +00:00
|
|
|
while (device->m_runningTransferLoop)
|
2017-05-09 03:37:12 +00:00
|
|
|
{
|
2017-05-11 19:12:44 +00:00
|
|
|
fd_set readset;
|
|
|
|
FD_ZERO(&readset);
|
|
|
|
FD_SET(fd, &readset);
|
|
|
|
struct timeval timeout = {0, 10000};
|
|
|
|
if (select(fd + 1, &readset, nullptr, nullptr, &timeout) > 0)
|
2017-05-09 03:37:12 +00:00
|
|
|
{
|
2017-05-11 19:12:44 +00:00
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
ssize_t sz = read(fd, readBuf.get(), readSz);
|
|
|
|
if (sz < 0)
|
|
|
|
break;
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->receivedHIDReport(readBuf.get(), sz,
|
|
|
|
HIDReportType::Input, readBuf[0]);
|
2017-05-11 19:12:44 +00:00
|
|
|
}
|
2017-05-09 03:37:12 +00:00
|
|
|
}
|
2017-09-18 02:59:46 +00:00
|
|
|
if (device->m_runningTransferLoop)
|
|
|
|
device->m_devImp->transferCycle();
|
2017-05-09 03:37:12 +00:00
|
|
|
}
|
2017-09-16 01:55:41 +00:00
|
|
|
device->m_devImp->finalCycle();
|
2015-04-29 07:56:16 +00:00
|
|
|
|
2017-05-09 03:37:12 +00:00
|
|
|
/* Cleanup */
|
|
|
|
close(fd);
|
|
|
|
device->m_devFd = 0;
|
2015-04-30 23:17:46 +00:00
|
|
|
udev_device_unref(udevDev);
|
2015-04-29 07:56:16 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-29 07:56:16 +00:00
|
|
|
void _deviceDisconnected()
|
|
|
|
{
|
|
|
|
m_runningTransferLoop = false;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2017-09-16 01:55:41 +00:00
|
|
|
std::vector<uint8_t> _getReportDescriptor()
|
|
|
|
{
|
|
|
|
/* Report descriptor size */
|
|
|
|
int reportDescSize;
|
|
|
|
if (ioctl(m_devFd, HIDIOCGRDESCSIZE, &reportDescSize) == -1)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
/* Get report descriptor */
|
|
|
|
hidraw_report_descriptor reportDesc;
|
|
|
|
reportDesc.size = reportDescSize;
|
|
|
|
if (ioctl(m_devFd, HIDIOCGRDESC, &reportDesc) == -1)
|
|
|
|
return {};
|
|
|
|
std::vector<uint8_t> ret(reportDesc.size, '\0');
|
|
|
|
memmove(ret.data(), reportDesc.value, reportDesc.size);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-05-09 03:37:12 +00:00
|
|
|
bool _sendHIDReport(const uint8_t* data, size_t length, HIDReportType tp, uint32_t message)
|
2015-04-29 07:56:16 +00:00
|
|
|
{
|
2015-05-15 01:16:36 +00:00
|
|
|
if (m_devFd)
|
|
|
|
{
|
2017-05-09 03:37:12 +00:00
|
|
|
if (tp == HIDReportType::Feature)
|
|
|
|
{
|
|
|
|
int ret = ioctl(m_devFd, HIDIOCSFEATURE(length), data);
|
|
|
|
if (ret < 0)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (tp == HIDReportType::Output)
|
|
|
|
{
|
|
|
|
ssize_t ret = write(m_devFd, data, length);
|
|
|
|
if (ret < 0)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2015-05-15 01:16:36 +00:00
|
|
|
}
|
2015-04-29 07:56:16 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-05-15 01:16:36 +00:00
|
|
|
|
2017-05-09 03:37:12 +00:00
|
|
|
size_t _receiveHIDReport(uint8_t *data, size_t length, HIDReportType tp, uint32_t message)
|
2015-05-15 01:16:36 +00:00
|
|
|
{
|
|
|
|
if (m_devFd)
|
|
|
|
{
|
2017-05-09 03:37:12 +00:00
|
|
|
if (tp == HIDReportType::Feature)
|
|
|
|
{
|
|
|
|
data[0] = message;
|
|
|
|
int ret = ioctl(m_devFd, HIDIOCGFEATURE(length), data);
|
|
|
|
if (ret < 0)
|
|
|
|
return 0;
|
|
|
|
return length;
|
|
|
|
}
|
2015-05-15 01:16:36 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-29 07:56:16 +00:00
|
|
|
public:
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2017-09-16 01:55:41 +00:00
|
|
|
HIDDeviceUdev(DeviceToken& token, const std::shared_ptr<DeviceBase>& devImp)
|
2015-04-29 07:56:16 +00:00
|
|
|
: m_token(token),
|
|
|
|
m_devImp(devImp),
|
|
|
|
m_devPath(token.getDevicePath())
|
|
|
|
{
|
2017-05-09 03:37:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void _startThread()
|
|
|
|
{
|
2015-04-29 07:56:16 +00:00
|
|
|
std::unique_lock<std::mutex> lk(m_initMutex);
|
2017-05-09 03:37:12 +00:00
|
|
|
DeviceType dType = m_token.getDeviceType();
|
|
|
|
if (dType == DeviceType::USB)
|
2017-09-16 01:55:41 +00:00
|
|
|
m_thread = std::thread(_threadProcUSBLL, std::static_pointer_cast<HIDDeviceUdev>(shared_from_this()));
|
2017-05-09 03:37:12 +00:00
|
|
|
else if (dType == DeviceType::Bluetooth)
|
2017-09-16 01:55:41 +00:00
|
|
|
m_thread = std::thread(_threadProcBTLL, std::static_pointer_cast<HIDDeviceUdev>(shared_from_this()));
|
2017-05-09 03:37:12 +00:00
|
|
|
else if (dType == DeviceType::HID)
|
2017-09-16 01:55:41 +00:00
|
|
|
m_thread = std::thread(_threadProcHID, std::static_pointer_cast<HIDDeviceUdev>(shared_from_this()));
|
2015-04-29 07:56:16 +00:00
|
|
|
else
|
2015-08-18 18:00:24 +00:00
|
|
|
{
|
|
|
|
fprintf(stderr, "invalid token supplied to device constructor");
|
|
|
|
abort();
|
|
|
|
}
|
2015-04-29 07:56:16 +00:00
|
|
|
m_initCond.wait(lk);
|
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-08-18 19:40:26 +00:00
|
|
|
~HIDDeviceUdev()
|
2015-04-29 07:56:16 +00:00
|
|
|
{
|
|
|
|
m_runningTransferLoop = false;
|
2016-10-11 01:20:39 +00:00
|
|
|
if (m_thread.joinable())
|
2017-09-16 01:55:41 +00:00
|
|
|
m_thread.detach();
|
2015-04-29 07:56:16 +00:00
|
|
|
}
|
2016-10-11 01:20:39 +00:00
|
|
|
|
2015-04-29 07:56:16 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2017-09-15 17:20:52 +00:00
|
|
|
std::shared_ptr<IHIDDevice> IHIDDeviceNew(DeviceToken& token, const std::shared_ptr<DeviceBase>& devImp)
|
2015-04-29 07:56:16 +00:00
|
|
|
{
|
2017-09-15 17:20:52 +00:00
|
|
|
return std::make_shared<HIDDeviceUdev>(token, devImp);
|
2015-04-29 07:56:16 +00:00
|
|
|
}
|
2015-04-29 10:24:39 +00:00
|
|
|
|
|
|
|
}
|