boo/lib/inputdev/HIDDeviceWinUSB.cpp

328 lines
11 KiB
C++

#define _CRT_SECURE_NO_WARNINGS 1 /* STFU MSVC */
#include "lib/inputdev/IHIDDevice.hpp"
#include "boo/inputdev/DeviceToken.hpp"
#include "boo/inputdev/DeviceBase.hpp"
#include <algorithm>
#include <condition_variable>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <thread>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <windows.h>
#include <winusb.h>
#include <usb100.h>
#include <Winusbio.h>
#include <hidsdi.h>
#undef min
#undef max
namespace boo {
class HIDDeviceWinUSB final : public IHIDDevice {
DeviceToken& m_token;
std::shared_ptr<DeviceBase> m_devImp;
HANDLE m_devHandle = nullptr;
HANDLE m_hidHandle = nullptr;
WINUSB_INTERFACE_HANDLE m_usbHandle = nullptr;
unsigned m_usbIntfInPipe = 0;
unsigned m_usbIntfOutPipe = 0;
bool m_runningTransferLoop = false;
std::string_view m_devPath;
std::mutex m_initMutex;
std::condition_variable m_initCond;
std::thread m_thread;
bool _sendUSBInterruptTransfer(const uint8_t* data, size_t length) override {
if (m_usbHandle) {
ULONG lengthTransferred = 0;
if (!WinUsb_WritePipe(m_usbHandle, m_usbIntfOutPipe, (PUCHAR)data, (ULONG)length, &lengthTransferred, nullptr) ||
lengthTransferred != length) {
return false;
}
return true;
}
return false;
}
size_t _receiveUSBInterruptTransfer(uint8_t* data, size_t length) override {
if (m_usbHandle) {
ULONG lengthTransferred = 0;
if (!WinUsb_ReadPipe(m_usbHandle, m_usbIntfInPipe, (PUCHAR)data, (ULONG)length, &lengthTransferred, nullptr))
return 0;
return lengthTransferred;
}
return 0;
}
static void _threadProcUSBLL(std::shared_ptr<HIDDeviceWinUSB> device) {
unsigned i;
std::unique_lock<std::mutex> lk(device->m_initMutex);
/* POSIX.. who needs it?? -MS */
device->m_devHandle =
CreateFileA(device->m_devPath.data(), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr);
if (INVALID_HANDLE_VALUE == device->m_devHandle) {
device->m_devImp->deviceError(fmt("Unable to open {}@{}: {}\n"),
device->m_token.getProductName(), device->m_devPath, GetLastError());
lk.unlock();
device->m_initCond.notify_one();
return;
}
if (!WinUsb_Initialize(device->m_devHandle, &device->m_usbHandle)) {
device->m_devImp->deviceError(fmt("Unable to open {}@{}: {}\n"),
device->m_token.getProductName(), device->m_devPath, GetLastError());
lk.unlock();
device->m_initCond.notify_one();
CloseHandle(device->m_devHandle);
return;
}
/* Enumerate device pipes */
USB_INTERFACE_DESCRIPTOR ifDesc = {0};
if (!WinUsb_QueryInterfaceSettings(device->m_usbHandle, 0, &ifDesc)) {
device->m_devImp->deviceError(fmt("Unable to open {}@{}: {}\n"),
device->m_token.getProductName(), device->m_devPath, GetLastError());
lk.unlock();
device->m_initCond.notify_one();
CloseHandle(device->m_devHandle);
return;
}
for (i = 0; i < ifDesc.bNumEndpoints; ++i) {
WINUSB_PIPE_INFORMATION pipeDesc;
WinUsb_QueryPipe(device->m_usbHandle, 0, i, &pipeDesc);
if (pipeDesc.PipeType == UsbdPipeTypeInterrupt) {
if (USB_ENDPOINT_DIRECTION_IN(pipeDesc.PipeId))
device->m_usbIntfInPipe = pipeDesc.PipeId;
else
device->m_usbIntfOutPipe = pipeDesc.PipeId;
}
}
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
/* Start transfer loop */
device->m_devImp->initialCycle();
while (device->m_runningTransferLoop)
device->m_devImp->transferCycle();
device->m_devImp->finalCycle();
/* Cleanup */
WinUsb_Free(device->m_usbHandle);
CloseHandle(device->m_devHandle);
device->m_devHandle = 0;
}
static void _threadProcBTLL(std::shared_ptr<HIDDeviceWinUSB> device) {
std::unique_lock<std::mutex> lk(device->m_initMutex);
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
/* Start transfer loop */
device->m_devImp->initialCycle();
while (device->m_runningTransferLoop)
device->m_devImp->transferCycle();
device->m_devImp->finalCycle();
}
size_t m_minFeatureSz = 0;
size_t m_minInputSz = 0;
size_t m_minOutputSz = 0;
PHIDP_PREPARSED_DATA m_preparsedData = nullptr;
static void _threadProcHID(std::shared_ptr<HIDDeviceWinUSB> device) {
std::unique_lock<std::mutex> lk(device->m_initMutex);
/* POSIX.. who needs it?? -MS */
device->m_overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
device->m_hidHandle =
CreateFileA(device->m_devPath.data(), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr);
if (INVALID_HANDLE_VALUE == device->m_hidHandle) {
device->m_devImp->deviceError(fmt("Unable to open {}@{}: {}\n"),
device->m_token.getProductName(), device->m_devPath, GetLastError());
lk.unlock();
device->m_initCond.notify_one();
return;
}
if (!HidD_GetPreparsedData(device->m_hidHandle, &device->m_preparsedData)) {
device->m_devImp->deviceError(fmt("Unable get preparsed data of {}@{}: {}\n"),
device->m_token.getProductName(), device->m_devPath, GetLastError());
lk.unlock();
device->m_initCond.notify_one();
return;
}
HIDP_CAPS caps;
HidP_GetCaps(device->m_preparsedData, &caps);
device->m_minFeatureSz = caps.FeatureReportByteLength;
device->m_minInputSz = caps.InputReportByteLength;
device->m_minOutputSz = caps.OutputReportByteLength;
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
/* Allocate read buffer */
size_t inBufferSz = device->m_minInputSz;
std::unique_ptr<uint8_t[]> readBuf(new uint8_t[inBufferSz]);
/* Start transfer loop */
device->m_devImp->initialCycle();
while (device->m_runningTransferLoop) {
device->ReadCycle(readBuf.get(), inBufferSz);
if (device->m_runningTransferLoop)
device->m_devImp->transferCycle();
}
device->m_devImp->finalCycle();
/* Cleanup */
CloseHandle(device->m_overlapped.hEvent);
CloseHandle(device->m_hidHandle);
HidD_FreePreparsedData(device->m_preparsedData);
device->m_hidHandle = nullptr;
}
void _deviceDisconnected() override { m_runningTransferLoop = false; }
std::vector<uint8_t> m_sendBuf;
std::vector<uint8_t> m_recvBuf;
const PHIDP_PREPARSED_DATA _getReportDescriptor() override { return m_preparsedData; }
bool _sendHIDReport(const uint8_t* data, size_t length, HIDReportType tp, uint32_t message) override {
size_t maxOut = std::max(m_minFeatureSz, std::max(m_minOutputSz, length));
if (m_sendBuf.size() < maxOut)
m_sendBuf.resize(maxOut);
if (maxOut > length)
memset(m_sendBuf.data() + length, 0, maxOut - length);
memmove(m_sendBuf.data(), data, length);
if (tp == HIDReportType::Output) {
DWORD useLength = DWORD(std::max(length, m_minOutputSz));
DWORD BytesWritten;
OVERLAPPED Overlapped;
ZeroMemory(&Overlapped, sizeof(Overlapped));
BOOL Result = WriteFile(m_hidHandle, m_sendBuf.data(), useLength, &BytesWritten, &Overlapped);
if (!Result) {
DWORD Error = GetLastError();
if (Error == ERROR_INVALID_USER_BUFFER) {
// std::cout << "Falling back to SetOutputReport" << std::endl;
if (!HidD_SetOutputReport(m_hidHandle, (PVOID)m_sendBuf.data(), useLength))
return false;
}
if (Error != ERROR_IO_PENDING) {
fmt::print(stderr, fmt("Write Failed {:08X}\n"), int(Error));
return false;
}
}
if (!GetOverlappedResult(m_hidHandle, &Overlapped, &BytesWritten, TRUE)) {
DWORD Error = GetLastError();
fmt::print(stderr, fmt("Write Failed {:08X}\n"), int(Error));
return false;
}
} else if (tp == HIDReportType::Feature) {
DWORD useLength = DWORD(std::max(length, m_minFeatureSz));
if (!HidD_SetFeature(m_hidHandle, (PVOID)m_sendBuf.data(), useLength)) {
// int error = GetLastError();
return false;
}
}
return true;
}
size_t _receiveHIDReport(uint8_t* data, size_t length, HIDReportType tp, uint32_t message) override {
size_t maxIn = std::max(m_minFeatureSz, std::max(m_minInputSz, length));
if (m_recvBuf.size() < maxIn)
m_recvBuf.resize(maxIn);
memset(m_recvBuf.data(), 0, length);
m_recvBuf[0] = message;
if (tp == HIDReportType::Input) {
if (!HidD_GetInputReport(m_hidHandle, m_recvBuf.data(), ULONG(std::max(m_minInputSz, length))))
return 0;
} else if (tp == HIDReportType::Feature) {
if (!HidD_GetFeature(m_hidHandle, m_recvBuf.data(), ULONG(std::max(m_minFeatureSz, length))))
return 0;
}
memmove(data, m_recvBuf.data(), length);
return length;
}
public:
HIDDeviceWinUSB(DeviceToken& token, const std::shared_ptr<DeviceBase>& devImp)
: m_token(token), m_devImp(devImp), m_devPath(token.getDevicePath()) {}
void _startThread() override {
std::unique_lock<std::mutex> lk(m_initMutex);
DeviceType dType = m_token.getDeviceType();
if (dType == DeviceType::USB)
m_thread = std::thread(_threadProcUSBLL, std::static_pointer_cast<HIDDeviceWinUSB>(shared_from_this()));
else if (dType == DeviceType::Bluetooth)
m_thread = std::thread(_threadProcBTLL, std::static_pointer_cast<HIDDeviceWinUSB>(shared_from_this()));
else if (dType == DeviceType::HID)
m_thread = std::thread(_threadProcHID, std::static_pointer_cast<HIDDeviceWinUSB>(shared_from_this()));
else
throw std::runtime_error("invalid token supplied to device constructor");
m_initCond.wait(lk);
}
~HIDDeviceWinUSB() override {
m_runningTransferLoop = false;
if (m_thread.joinable())
m_thread.detach();
}
OVERLAPPED m_overlapped = {};
void ReadCycle(uint8_t* inBuffer, size_t inBufferSz) {
ResetEvent(m_overlapped.hEvent);
ZeroMemory(inBuffer, inBufferSz);
DWORD BytesRead = 0;
BOOL Result = ReadFile(m_hidHandle, inBuffer, DWORD(inBufferSz), &BytesRead, &m_overlapped);
if (!Result) {
DWORD Error = GetLastError();
if (Error == ERROR_DEVICE_NOT_CONNECTED) {
m_runningTransferLoop = false;
return;
} else if (Error != ERROR_IO_PENDING) {
fmt::print(stderr, fmt("Read Failed: {:08X}\n"), int(Error));
return;
} else if (!GetOverlappedResultEx(m_hidHandle, &m_overlapped, &BytesRead, 10, TRUE)) {
return;
}
}
m_devImp->receivedHIDReport(inBuffer, BytesRead, HIDReportType::Input, inBuffer[0]);
}
};
std::shared_ptr<IHIDDevice> IHIDDeviceNew(DeviceToken& token, const std::shared_ptr<DeviceBase>& devImp) {
return std::make_shared<HIDDeviceWinUSB>(token, devImp);
}
} // namespace boo