#include "piuio.h" #include #include 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(); }