spicetools/rawinput/piuio.cpp

395 lines
11 KiB
C++

#include "piuio.h"
#include <iostream>
#include <thread>
using namespace std;
// it's 2019 and I'm writing code to avoid issues with Windows XP.
static HINSTANCE WINUSB_INSTANCE = nullptr;
static std::string WINUSB_NAME = "winusb.dll";
static std::string WinUsb_InitializeStr = "WinUsb_Initialize";
typedef BOOL(__stdcall * WinUsb_Initialize_t)(
_In_ HANDLE DeviceHandle,
_Out_ PWINUSB_INTERFACE_HANDLE InterfaceHandle
);
static std::string WinUsb_ControlTransferStr = "WinUsb_ControlTransfer";
typedef BOOL(__stdcall * WinUsb_ControlTransfer_t)(
_In_ WINUSB_INTERFACE_HANDLE InterfaceHandle,
_In_ WINUSB_SETUP_PACKET SetupPacket,
_Out_writes_bytes_to_opt_(BufferLength, *LengthTransferred) PUCHAR Buffer,
_In_ ULONG BufferLength,
_Out_opt_ PULONG LengthTransferred,
_In_opt_ LPOVERLAPPED Overlapped
);
static WinUsb_ControlTransfer_t pWinUsb_ControlTransfer = nullptr;
static WinUsb_Initialize_t pWinUsb_Initialize = nullptr;
const string rawinput::PIUIO::LIGHT_NAMES[rawinput::PIUIO::PIUIO_MAX_NUM_OF_LIGHTS] = {
"Unknown",
"Unknown",
"P2 Up",
"P2 Down",
"P2 Left",
"P2 Right",
"P2 Center",
"Unknown",
"Unknown",
"Unknown",
"Neon",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"P1 Up",
"P1 Down",
"P1 Left",
"P1 Right",
"P1 Center",
"Marquee Upper Left",
"Marquee Lower Right",
"Marquee Lower Left",
"Marquee Upper Right",
"USB Enable",
"Unknown",
"Unknown",
"Unknown",
"Unknown"
};
rawinput::PIUIO::PIUIO(Device *device) {
this->device = device;
button_state = 0;
light_data = 0;
current_sensor = 0;
prev_timeout = 0;
}
bool rawinput::PIUIO::LoadWinUSBFunctions() {
static bool functions_loaded = false;
if (functions_loaded)
return true;
// see if we even have winusb available.
WINUSB_INSTANCE = libutils::try_library(WINUSB_NAME);
// if windows didn't load it just now, then it's not on the system.
if (WINUSB_INSTANCE != nullptr) {
// load winusb functions
pWinUsb_Initialize = (WinUsb_Initialize_t) libutils::get_proc(
WINUSB_INSTANCE, WinUsb_InitializeStr.c_str());
pWinUsb_ControlTransfer = (WinUsb_ControlTransfer_t) libutils::get_proc(
WINUSB_INSTANCE, WinUsb_ControlTransferStr.c_str());
// make sure they actually loaded.
if(pWinUsb_Initialize != nullptr && pWinUsb_ControlTransfer!= nullptr)
functions_loaded = true;
} else {
log_warning("piuio", "Could not load \'{}\', skipping...", WINUSB_NAME);
}
return functions_loaded;
}
PSP_DEVICE_INTERFACE_DETAIL_DATA rawinput::PIUIO::GetDevicePath() {
// modified from https://forum.vellemanprojects.eu/t/k8090-how-to-identify-which-com-port-it-is-connected-to/3810
// https://www.velleman.eu/images/tmp/usbfind.c
HDEVINFO hDevInfo;
SP_DEVICE_INTERFACE_DATA DevIntfData;
PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
SP_DEVINFO_DATA DevData;
DWORD dwSize, dwMemberIdx;
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (hDevInfo != INVALID_HANDLE_VALUE) {
DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
dwMemberIdx = 0;
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE,
dwMemberIdx, &DevIntfData);
while (GetLastError() != ERROR_NO_MORE_ITEMS) {
DevData.cbSize = sizeof(DevData);
SetupDiGetDeviceInterfaceDetail(
hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData,
DevIntfDetailData, dwSize, &dwSize, &DevData)) {
if (strstr(DevIntfDetailData->DevicePath, VENDOR_PROD_ID) != 0) {
return DevIntfDetailData;
}
}
HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
// continue looping
SetupDiEnumDeviceInterfaces(
hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, ++dwMemberIdx, &DevIntfData);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
}
return 0;
}
bool rawinput::PIUIO::IsPressed(int iKey) {
if (iKey < PIUIO_MAX_NUM_OF_INPUTS) {
// logic high is pressed, logic low is released.
return button_state & (1 << iKey);
}
return false;
}
bool rawinput::PIUIO::WasPressed(int iKey) {
if (iKey < PIUIO_MAX_NUM_OF_INPUTS)
return prev_button_state & (1 << iKey);
return false;
}
void rawinput::PIUIO::SetUSBPortState(bool bState) {
SetLight(USB_ENABLE_BIT, bState);
}
void rawinput::PIUIO::SetLight(int iLight, bool bState) {
if (iLight < PIUIO_MAX_NUM_OF_LIGHTS) {
// turn on a light by setting the bit, turn it off by clearing it.
if (bState)
light_data |= 1 << iLight;
else
light_data &= ~(1 << iLight);
}
}
bool rawinput::PIUIO::Open() {
// ensure the libs are loaded
// if we can't load them, then we shouldn't exist.
if (!LoadWinUSBFunctions())
return false;
bool bResult = false;
PSP_DEVICE_INTERFACE_DETAIL_DATA dev_detail;
dev_detail = GetDevicePath();
if (dev_detail == 0) {
HeapFree(GetProcessHeap(), 0, dev_detail);
return false;
}
device_handle = CreateFile(dev_detail->DevicePath,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
// no longer need dev_detail
HeapFree(GetProcessHeap(), 0, dev_detail);
if (device_handle == INVALID_HANDLE_VALUE)
return false;
if (pWinUsb_Initialize != nullptr)
bResult = pWinUsb_Initialize(device_handle, &win_usb_handle);
if (!bResult) {
CloseHandle(device_handle);
return false;
}
return bResult;
}
uint32_t rawinput::PIUIO::Read() {
WINUSB_SETUP_PACKET SetupPacket;
ZeroMemory(&SetupPacket, sizeof(WINUSB_SETUP_PACKET));
ULONG cbSent = 0;
bool bResult = false;
// create the setup packet
SetupPacket.RequestType = 0x40 | 0x80; // EndPoint In, RequestType Vendor
SetupPacket.Request = PIUIO_CTL_REQ;
SetupPacket.Value = 0;
SetupPacket.Index = 0;
SetupPacket.Length = PACKET_SIZE_BYTES;
// ensure we loaded the pointer
unsigned char buffer[PACKET_SIZE_BYTES] {};
if (pWinUsb_ControlTransfer != nullptr)
bResult = pWinUsb_ControlTransfer(win_usb_handle, SetupPacket, buffer, PACKET_SIZE_BYTES, &cbSent, 0);
if (!bResult)
log_warning("piuio", "read error");
return Deserialize(buffer);
}
bool rawinput::PIUIO::Write(uint32_t p_Data) {
WINUSB_SETUP_PACKET SetupPacket;
ZeroMemory(&SetupPacket, sizeof(WINUSB_SETUP_PACKET));
ULONG cbSent = 0;
bool bResult = false;
// create the setup packet
SetupPacket.RequestType = 0x40; //EndPoint Out, RequestType Vendor
SetupPacket.Request = PIUIO_CTL_REQ;
SetupPacket.Value = 0;
SetupPacket.Index = 0;
SetupPacket.Length = PACKET_SIZE_BYTES;
// ensure we loaded the pointer
if (pWinUsb_ControlTransfer != nullptr) {
bResult = pWinUsb_ControlTransfer(
win_usb_handle,
SetupPacket,
Serialize(light_data),
PACKET_SIZE_BYTES,
&cbSent,
0
);
}
if (!bResult)
log_warning("piuio", "write error");
return bResult;
}
unsigned char * rawinput::PIUIO::Serialize(uint32_t p_Data) {
static unsigned char buffer[PACKET_SIZE_BYTES];
buffer[0] = p_Data;
buffer[1] = p_Data >> 8;
buffer[2] = p_Data >> 16;
buffer[3] = p_Data >> 24;
// these bits don't matter for the piuio
buffer[4] = 0xFF;
buffer[5] = 0xFF;
buffer[6] = 0xFF;
buffer[7] = 0xFF;
return buffer;
}
uint32_t rawinput::PIUIO::Deserialize(unsigned char * p_Data) {
uint32_t result;
result = p_Data[0];
result |= p_Data[1] << 8;
result |= p_Data[2] << 16;
result |= p_Data[3] << 24;
return result;
}
void rawinput::PIUIO::IOThread() {
while (is_connected)
UpdateSingleButtonState();
}
void rawinput::PIUIO::UpdateSingleButtonState() {
// write a set of sensors to request
light_data &= 0xFFFCFFFC;
light_data |= (current_sensor | (current_sensor << 16));
// request this set of sensors
// this also updates the light status
this->Write(light_data);
// read from this set of sensors
input_data[current_sensor] = this->Read();
// piuio opens high; invert the input
input_data[current_sensor] = ~input_data[current_sensor];
// update high values ASAP, that's when the button is pressed.
// see: https://github.com/djpohly/piuio#usb-protocol-and-multiplexer
auto button_state_new = button_state | input_data[current_sensor];
if (button_state != button_state_new) {
this->device->mutex->lock();
button_state |= button_state_new;
this->device->updated = true;
this->device->mutex->unlock();
}
if (current_sensor >= SENSOR_NUM - 1) {
uint32_t temp_btn_state = 0;
// we have done a full sensor arrangement, let's see if the panel has been pulled away from.
// combine all the input.
for (int i = 0; i < 4; ++i)
temp_btn_state |= input_data[i];
// only store every 255th previous value since we are polling very quickly.
// only used by spicecfg to see if a user pressed a button, so not very important.
// unsigned ints rollover
prev_timeout++;
if (prev_timeout == 0)
prev_button_state = button_state;
if (button_state != temp_btn_state) {
this->device->mutex->lock();
button_state = temp_btn_state;
this->device->updated = true;
this->device->mutex->unlock();
}
current_sensor = 0;
} else {
current_sensor++;
}
}
bool rawinput::PIUIO::Init() {
is_connected = Open();
if (is_connected) {
// force the USB lines to be always on.
SetUSBPortState(true);
IOThreadObject = new std::thread(&rawinput::PIUIO::IOThread, this);
}
return is_connected;
}
bool rawinput::PIUIO::Stop() {
is_connected = false;
if (IOThreadObject != NULL) {
IOThreadObject->join();
}
return true;
}
rawinput::PIUIO::~PIUIO() {
Stop();
}