Initial re-upload of spice2x-24-08-24
This commit is contained in:
177
rawinput/device.h
Normal file
177
rawinput/device.h
Normal file
@@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
extern "C" {
|
||||
#include <hidusage.h>
|
||||
#include <hidsdi.h>
|
||||
}
|
||||
|
||||
#include "util/unique_plain_ptr.h"
|
||||
|
||||
#include "sextet.h"
|
||||
#include "smxstage.h"
|
||||
|
||||
namespace rawinput {
|
||||
|
||||
enum DeviceType {
|
||||
DESTROYED,
|
||||
UNKNOWN,
|
||||
MOUSE,
|
||||
KEYBOARD,
|
||||
HID,
|
||||
MIDI,
|
||||
SEXTET_OUTPUT,
|
||||
PIUIO_DEVICE,
|
||||
SMX_STAGE,
|
||||
};
|
||||
|
||||
enum MouseKeys {
|
||||
MOUSEBTN_LEFT = 0,
|
||||
MOUSEBTN_RIGHT = 1,
|
||||
MOUSEBTN_MIDDLE = 2,
|
||||
MOUSEBTN_1 = 3,
|
||||
MOUSEBTN_2 = 4,
|
||||
MOUSEBTN_3 = 5,
|
||||
MOUSEBTN_4 = 6,
|
||||
MOUSEBTN_5 = 7,
|
||||
};
|
||||
|
||||
enum MousePos {
|
||||
MOUSEPOS_X = 0,
|
||||
MOUSEPOS_Y = 1,
|
||||
MOUSEPOS_WHEEL = 2,
|
||||
};
|
||||
|
||||
struct DeviceInfo {
|
||||
std::string devclass;
|
||||
std::string subclass;
|
||||
std::string protocol;
|
||||
std::string guid_str;
|
||||
GUID guid;
|
||||
};
|
||||
|
||||
struct DeviceMouseInfo {
|
||||
bool key_states[16];
|
||||
bool key_states_bind[16];
|
||||
double key_up[16];
|
||||
double key_down[16];
|
||||
long pos_x, pos_y;
|
||||
long pos_wheel;
|
||||
};
|
||||
|
||||
struct DeviceKeyboardInfo {
|
||||
bool key_states[1024];
|
||||
double key_up[1024];
|
||||
double key_down[1024];
|
||||
};
|
||||
|
||||
enum class HIDDriver {
|
||||
Default,
|
||||
PacDrive,
|
||||
};
|
||||
|
||||
struct HIDTouchPoint {
|
||||
DWORD id;
|
||||
bool down;
|
||||
float x, y;
|
||||
size_t ttl;
|
||||
uint64_t last_report; // unix time in ms
|
||||
};
|
||||
|
||||
struct HIDTouchScreenData {
|
||||
bool valid = false;
|
||||
bool parsed_elements = false;
|
||||
size_t remaining_contact_count = 0;
|
||||
std::vector<int> elements_contact_count;
|
||||
std::vector<int> elements_contact_identifier;
|
||||
std::vector<int> elements_x;
|
||||
std::vector<int> elements_y;
|
||||
std::vector<int> elements_width;
|
||||
std::vector<int> elements_height;
|
||||
std::vector<int> elements_pressed;
|
||||
std::vector<int> elements_pressure;
|
||||
std::vector<HIDTouchPoint> touch_points;
|
||||
};
|
||||
|
||||
struct DeviceHIDInfo {
|
||||
HANDLE handle;
|
||||
_HIDP_CAPS caps;
|
||||
HIDD_ATTRIBUTES attributes;
|
||||
HIDDriver driver = HIDDriver::Default;
|
||||
HIDTouchScreenData touch {};
|
||||
|
||||
std::string usage_name;
|
||||
util::unique_plain_ptr<_HIDP_PREPARSED_DATA> preparsed_data;
|
||||
UINT preparsed_size;
|
||||
|
||||
std::vector<HIDP_BUTTON_CAPS> button_caps_list;
|
||||
std::vector<std::string> button_caps_names;
|
||||
std::vector<HIDP_BUTTON_CAPS> button_output_caps_list;
|
||||
std::vector<std::string> button_output_caps_names;
|
||||
std::vector<HIDP_VALUE_CAPS> value_caps_list;
|
||||
std::vector<std::string> value_caps_names;
|
||||
std::vector<HIDP_VALUE_CAPS> value_output_caps_list;
|
||||
std::vector<std::string> value_output_caps_names;
|
||||
std::vector<std::vector<bool>> button_states;
|
||||
std::vector<std::vector<double>> button_up, button_down;
|
||||
std::vector<std::vector<bool>> button_output_states;
|
||||
std::vector<float> value_states;
|
||||
std::vector<LONG> value_states_raw;
|
||||
std::vector<float> value_output_states;
|
||||
|
||||
// for config binding function
|
||||
std::vector<float> bind_value_states;
|
||||
};
|
||||
|
||||
struct DeviceMIDIInfo {
|
||||
std::vector<bool> states;
|
||||
std::vector<uint8_t> states_events;
|
||||
std::vector<bool> bind_states;
|
||||
std::vector<uint8_t> velocity; // 16*128 x 7 bit resolution
|
||||
bool freeze;
|
||||
std::vector<uint16_t> controls_precision; // 16*32 14 bit resolution
|
||||
std::vector<uint16_t> controls_precision_bind;
|
||||
std::vector<bool> controls_precision_msb;
|
||||
std::vector<bool> controls_precision_lsb;
|
||||
std::vector<bool> controls_precision_set;
|
||||
std::vector<uint8_t> controls_single; // 16*44 7 bit resolution
|
||||
std::vector<uint8_t> controls_single_bind;
|
||||
std::vector<bool> controls_single_set;
|
||||
std::vector<bool> controls_onoff; // 16*6 1 bit resolution :)
|
||||
std::vector<bool> controls_onoff_bind;
|
||||
std::vector<bool> controls_onoff_set;
|
||||
uint16_t pitch_bend; // 14 bit resolution
|
||||
};
|
||||
|
||||
class PIUIO;
|
||||
|
||||
struct Device {
|
||||
size_t id;
|
||||
std::string name;
|
||||
std::string desc;
|
||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||
DeviceType type = UNKNOWN;
|
||||
DeviceInfo info;
|
||||
std::mutex *mutex;
|
||||
std::mutex *mutex_out;
|
||||
bool updated = true;
|
||||
bool output_pending = true;
|
||||
bool output_enabled = false;
|
||||
DeviceMouseInfo *mouseInfo = nullptr;
|
||||
DeviceKeyboardInfo *keyboardInfo = nullptr;
|
||||
DeviceHIDInfo *hidInfo = nullptr;
|
||||
DeviceMIDIInfo *midiInfo = nullptr;
|
||||
SextetDevice *sextetInfo = nullptr;
|
||||
PIUIO* piuioDev = nullptr;
|
||||
SmxStageDevice *smxstageInfo = nullptr;
|
||||
double input_time = 0.0;
|
||||
double input_hz = 0.f;
|
||||
double input_hz_max = 0.f;
|
||||
};
|
||||
}
|
||||
132
rawinput/hotplug.cpp
Normal file
132
rawinput/hotplug.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#include <initguid.h>
|
||||
|
||||
#include "hotplug.h"
|
||||
|
||||
#include <dbt.h>
|
||||
#include <devguid.h>
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace rawinput {
|
||||
|
||||
DEFINE_GUID(GUID_HID, 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
|
||||
|
||||
HotplugManager::HotplugManager(RawInputManager *ri_mgr, HWND hWnd) : ri_mgr(ri_mgr) {
|
||||
|
||||
// init settings
|
||||
DEV_BROADCAST_DEVICEINTERFACE settings_hid {
|
||||
.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE),
|
||||
.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE,
|
||||
.dbcc_reserved = 0,
|
||||
.dbcc_classguid = GUID_HID,
|
||||
.dbcc_name = {0},
|
||||
};
|
||||
|
||||
// register notifications
|
||||
this->hotplug_hid = RegisterDeviceNotification(hWnd, &settings_hid, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
if (this->hotplug_hid == nullptr) {
|
||||
log_warning("hotplug", "failed to register HID notifications: {}", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
HotplugManager::~HotplugManager() {
|
||||
|
||||
// unregister notifications
|
||||
if (this->hotplug_hid != nullptr) {
|
||||
UnregisterDeviceNotification(this->hotplug_hid);
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK HotplugManager::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
|
||||
// check for device change
|
||||
if (msg == WM_DEVICECHANGE) {
|
||||
|
||||
// check type
|
||||
switch (wParam) {
|
||||
case DBT_DEVICEARRIVAL: {
|
||||
|
||||
// check arrived device type
|
||||
auto hdr = (DEV_BROADCAST_HDR*) lParam;
|
||||
switch (hdr->dbch_devicetype) {
|
||||
case DBT_DEVTYP_DEVICEINTERFACE: {
|
||||
auto dev = (const DEV_BROADCAST_DEVICEINTERFACE *) hdr;
|
||||
this->ri_mgr->devices_scan_midi();
|
||||
|
||||
// check if class is not HID
|
||||
if (memcmp(&dev->dbcc_classguid, &GUID_HID, sizeof(GUID_HID)) != 0)
|
||||
break;
|
||||
|
||||
// hotplug
|
||||
std::string name(dev->dbcc_name);
|
||||
this->ri_mgr->devices_scan_rawinput(name);
|
||||
this->ri_mgr->devices_register();
|
||||
|
||||
break;
|
||||
}
|
||||
case DBT_DEVTYP_VOLUME: {
|
||||
auto dev = (const DEV_BROADCAST_VOLUME *) hdr;
|
||||
auto unitmask = dev->dbcv_unitmask;
|
||||
|
||||
// check drive
|
||||
unsigned long bit;
|
||||
while (_BitScanForward(&bit, unitmask)) {
|
||||
|
||||
// remove drive from unitmask so it is not scanned again
|
||||
unitmask &= ~(1 << bit);
|
||||
|
||||
// convert bit to drive letter char
|
||||
char drive = 'A' + bit;
|
||||
log_info("hotplug", "detected volume arrival: {}", drive);
|
||||
|
||||
#ifndef SPICETOOLS_SPICECFG_STANDALONE
|
||||
// auto insert cards
|
||||
for (int player = 0; player <= 1; player++) {
|
||||
std::string path = to_string(drive) + ":\\card" + to_string(player) + ".txt";
|
||||
if (fileutils::file_exists(path)) {
|
||||
uint8_t card_data[8];
|
||||
if (eamuse_get_card_from_file(path, card_data, player)) {
|
||||
eamuse_card_insert(player, card_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// success
|
||||
return TRUE;
|
||||
}
|
||||
case DBT_DEVICEREMOVECOMPLETE: {
|
||||
|
||||
// check arrived device type
|
||||
auto hdr = (DEV_BROADCAST_HDR*) lParam;
|
||||
switch (hdr->dbch_devicetype) {
|
||||
case DBT_DEVTYP_DEVICEINTERFACE: {
|
||||
auto dev = (DEV_BROADCAST_DEVICEINTERFACE*) hdr;
|
||||
std::string name(dev->dbcc_name);
|
||||
|
||||
// destruct device
|
||||
this->ri_mgr->devices_remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
// success
|
||||
return TRUE;
|
||||
}
|
||||
case 7: { // windows 10 reports this for MIDI?
|
||||
this->ri_mgr->devices_scan_midi();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
20
rawinput/hotplug.h
Normal file
20
rawinput/hotplug.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace rawinput {
|
||||
|
||||
class RawInputManager;
|
||||
|
||||
class HotplugManager {
|
||||
private:
|
||||
RawInputManager *ri_mgr;
|
||||
HANDLE hotplug_hid;
|
||||
|
||||
public:
|
||||
HotplugManager(RawInputManager *ri_mgr, HWND hwnd);
|
||||
~HotplugManager();
|
||||
|
||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
};
|
||||
}
|
||||
394
rawinput/piuio.cpp
Normal file
394
rawinput/piuio.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
#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();
|
||||
}
|
||||
78
rawinput/piuio.h
Normal file
78
rawinput/piuio.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
#include <winusb.h>
|
||||
#include <setupapi.h>
|
||||
#include <string>
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "device.h"
|
||||
|
||||
// This is the GUID for the USB device class
|
||||
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);
|
||||
|
||||
#pragma comment (lib, "setupapi.lib")
|
||||
#pragma comment (lib , "winusb.lib" )
|
||||
|
||||
namespace rawinput {
|
||||
|
||||
class PIUIO
|
||||
{
|
||||
public:
|
||||
|
||||
static const size_t PACKET_SIZE_BYTES = 8;
|
||||
static const size_t SENSOR_NUM = 4;
|
||||
static const size_t USB_ENABLE_BIT = 27;
|
||||
static const UCHAR PIUIO_CTL_REQ = 0xAE;
|
||||
const char *VENDOR_PROD_ID = "vid_0547&pid_1002";
|
||||
|
||||
const static std::string LIGHT_NAMES[];
|
||||
static const int PIUIO_MAX_NUM_OF_LIGHTS = 32;
|
||||
static const int PIUIO_MAX_NUM_OF_INPUTS = 32;
|
||||
|
||||
bool is_connected;
|
||||
Device *device;
|
||||
|
||||
bool IsPressed(int iKey);
|
||||
bool WasPressed(int iKey);
|
||||
void SetLight(int iLight, bool bState);
|
||||
|
||||
PIUIO(Device* device);
|
||||
virtual ~PIUIO();
|
||||
|
||||
bool Init();
|
||||
bool Stop();
|
||||
|
||||
void UpdateSingleButtonState();
|
||||
|
||||
void SetUSBPortState(bool bState);
|
||||
|
||||
private:
|
||||
bool LoadWinUSBFunctions();
|
||||
|
||||
uint32_t light_data;
|
||||
uint32_t input_data[SENSOR_NUM];
|
||||
uint8_t current_sensor;
|
||||
uint32_t button_state, prev_button_state;
|
||||
|
||||
uint8_t prev_timeout;
|
||||
|
||||
LPCWSTR device_path;
|
||||
WINUSB_INTERFACE_HANDLE win_usb_handle = INVALID_HANDLE_VALUE;
|
||||
HANDLE device_handle = INVALID_HANDLE_VALUE;
|
||||
|
||||
bool Open();
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA GetDevicePath();
|
||||
|
||||
void IOThread();
|
||||
|
||||
uint32_t Read();
|
||||
bool Write(uint32_t p_Data);
|
||||
|
||||
unsigned char * Serialize(uint32_t p_Data);
|
||||
uint32_t Deserialize(unsigned char * p_Data);
|
||||
|
||||
std::thread* IOThreadObject;
|
||||
};
|
||||
}
|
||||
2680
rawinput/rawinput.cpp
Normal file
2680
rawinput/rawinput.cpp
Normal file
File diff suppressed because it is too large
Load Diff
140
rawinput/rawinput.h
Normal file
140
rawinput/rawinput.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include "device.h"
|
||||
#include "hotplug.h"
|
||||
|
||||
namespace rawinput {
|
||||
|
||||
// settings
|
||||
extern bool NOLEGACY;
|
||||
|
||||
extern uint8_t HID_LIGHT_BRIGHTNESS;
|
||||
|
||||
extern bool ENABLE_SMX_STAGE;
|
||||
|
||||
extern int TOUCHSCREEN_RANGE_X;
|
||||
extern int TOUCHSCREEN_RANGE_Y;
|
||||
|
||||
struct DeviceCallback {
|
||||
void *data;
|
||||
std::function<void(void*, Device*)> f;
|
||||
};
|
||||
struct MidiCallback {
|
||||
void *data;
|
||||
std::function<void(void*, Device*, uint8_t cmd, uint8_t ch, uint8_t b1, uint8_t b2)> f;
|
||||
};
|
||||
|
||||
class RawInputManager {
|
||||
private:
|
||||
|
||||
HotplugManager *hotplug;
|
||||
std::vector<Device> devices;
|
||||
HWND input_hwnd = nullptr;
|
||||
WNDCLASSEX input_hwnd_class {};
|
||||
std::thread *input_thread = nullptr;
|
||||
std::thread *flush_thread = nullptr;
|
||||
bool flush_thread_running = false;
|
||||
std::thread *output_thread = nullptr;
|
||||
std::mutex output_thread_m;
|
||||
bool output_thread_ready = false;
|
||||
bool output_thread_running = false;
|
||||
std::condition_variable output_thread_cv;
|
||||
std::vector<DeviceCallback> callback_add;
|
||||
std::vector<DeviceCallback> callback_change;
|
||||
std::vector<MidiCallback> callback_midi;
|
||||
|
||||
void input_hwnd_create();
|
||||
void input_hwnd_destroy();
|
||||
void devices_reload();
|
||||
void devices_scan_rawinput(RAWINPUTDEVICELIST *device, bool log = true);
|
||||
void devices_scan_piuio();
|
||||
void devices_scan_smxstage();
|
||||
void devices_destruct();
|
||||
void devices_destruct(Device *device, bool log = true);
|
||||
void flush_start();
|
||||
void flush_stop();
|
||||
void output_start();
|
||||
void output_stop();
|
||||
|
||||
static std::string rawinput_get_device_name(HANDLE hDevice);
|
||||
static std::string rawinput_get_device_description(const DeviceInfo& info, const std::string &device_name);
|
||||
|
||||
static LRESULT CALLBACK input_wnd_proc(HWND, UINT, WPARAM, LPARAM);
|
||||
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
||||
static DeviceInfo get_device_info(const std::string &device_name);
|
||||
|
||||
public:
|
||||
|
||||
RawInputManager();
|
||||
~RawInputManager();
|
||||
|
||||
void stop();
|
||||
|
||||
void sextet_register(
|
||||
const std::string &port_name,
|
||||
const std::string &alias = "Sextet",
|
||||
bool warn = true);
|
||||
|
||||
void devices_scan_rawinput(const std::string &device_name = "");
|
||||
void devices_scan_midi();
|
||||
void devices_remove(const std::string &name);
|
||||
|
||||
void devices_register();
|
||||
void devices_unregister();
|
||||
|
||||
static void device_write_output(Device *device, bool only_updated = true);
|
||||
void devices_flush_output(bool optimized = true);
|
||||
|
||||
void __stdcall devices_print();
|
||||
Device *devices_get(const std::string &name, bool updated = false);
|
||||
|
||||
inline std::vector<Device> &devices_get() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
inline std::vector<Device *> devices_get_updated() {
|
||||
std::vector<Device *> updated;
|
||||
for (auto &device : devices_get()) {
|
||||
device.mutex->lock();
|
||||
if (device.updated) {
|
||||
device.updated = false;
|
||||
device.mutex->unlock();
|
||||
updated.emplace_back(&device);
|
||||
} else {
|
||||
device.mutex->unlock();
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
inline void devices_midi_freeze(bool freeze) {
|
||||
for (auto &device : devices_get()) {
|
||||
if (device.type == MIDI) {
|
||||
device.midiInfo->freeze = freeze;
|
||||
if (!freeze) {
|
||||
for (unsigned short index = 0; index < 16 * 128; index++) {
|
||||
device.midiInfo->states[index] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_callback_add(void *data, std::function<void(void *, Device *)> callback);
|
||||
void remove_callback_add(void *data, const std::function<void(void *, Device *)> &callback);
|
||||
void add_callback_change(void *data, std::function<void(void *, Device *)> callback);
|
||||
void remove_callback_change(void *data, const std::function<void(void *, Device *)>& callback);
|
||||
void add_callback_midi(void *data, std::function<void(void *, Device *,
|
||||
uint8_t, uint8_t, uint8_t, uint8_t)> callback);
|
||||
void remove_callback_midi(void *data, const std::function<void(void *, Device *,
|
||||
uint8_t, uint8_t, uint8_t, uint8_t)>& callback);
|
||||
};
|
||||
}
|
||||
178
rawinput/sextet.cpp
Normal file
178
rawinput/sextet.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "sextet.h"
|
||||
#include <cstring>
|
||||
#include <stdint.h>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "util/logging.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
const string rawinput::SextetDevice::LIGHT_NAMES[LIGHT_COUNT] = {
|
||||
"Neon",
|
||||
|
||||
"P1 Halogen Upper",
|
||||
"P1 Halogen Lower",
|
||||
"P2 Halogen Upper",
|
||||
"P2 Halogen Lower",
|
||||
|
||||
"P1 Button",
|
||||
"P2 Button",
|
||||
|
||||
"P1 Foot Up",
|
||||
"P1 Foot Down",
|
||||
"P1 Foot Left",
|
||||
"P1 Foot Right",
|
||||
|
||||
"P2 Foot Up",
|
||||
"P2 Foot Down",
|
||||
"P2 Foot Left",
|
||||
"P2 Foot Right"
|
||||
};
|
||||
|
||||
inline void rawinput::SextetDevice::fill_packet(uint8_t *buffer, bool *light_state) {
|
||||
|
||||
/*
|
||||
* Refer to this doucment for more info
|
||||
* https://github.com/stepmania/stepmania/blob/master/src/arch/Lights/LightsDriver_SextetStream.md#bit-meanings
|
||||
*/
|
||||
|
||||
buffer[0] = 0xC0 | (uint8_t) (
|
||||
(light_state[SextetLights::BASS_NEON] << 5) |
|
||||
(light_state[SextetLights::BASS_NEON] << 4) |
|
||||
(light_state[SextetLights::MARQUEE_P2_LOWER] << 3) |
|
||||
(light_state[SextetLights::MARQUEE_P1_LOWER] << 2) |
|
||||
(light_state[SextetLights::MARQUEE_P2_UPPER] << 1) |
|
||||
(light_state[SextetLights::MARQUEE_P1_UPPER] << 0)
|
||||
);
|
||||
|
||||
buffer[1] = 0xC0 | (uint8_t) (
|
||||
(light_state[SextetLights::P1_MENU] << 5) |
|
||||
(light_state[SextetLights::P1_MENU] << 4)
|
||||
);
|
||||
|
||||
buffer[2] = 0xC0;
|
||||
|
||||
buffer[3] = 0xC0 | (uint8_t) (
|
||||
(light_state[SextetLights::P1_DOWN] << 3) |
|
||||
(light_state[SextetLights::P1_UP] << 2) |
|
||||
(light_state[SextetLights::P1_RIGHT] << 1) |
|
||||
(light_state[SextetLights::P1_LEFT] << 0)
|
||||
);
|
||||
|
||||
buffer[4] = 0xC0;
|
||||
buffer[5] = 0xC0;
|
||||
buffer[6] = 0xC0;
|
||||
|
||||
buffer[7] = 0xC0 | (uint8_t) (
|
||||
(light_state[SextetLights::P2_MENU] << 5) |
|
||||
(light_state[SextetLights::P2_MENU] << 4)
|
||||
);
|
||||
|
||||
buffer[8] = 0xC0;
|
||||
|
||||
buffer[9] = 0xC0 | (uint8_t) (
|
||||
(light_state[SextetLights::P2_DOWN] << 3) |
|
||||
(light_state[SextetLights::P2_UP] << 2) |
|
||||
(light_state[SextetLights::P2_RIGHT] << 1) |
|
||||
(light_state[SextetLights::P2_LEFT] << 0)
|
||||
);
|
||||
|
||||
buffer[10] = 0xC0;
|
||||
buffer[11] = 0xC0;
|
||||
buffer[12] = 0xC0;
|
||||
|
||||
// terminate with LF
|
||||
buffer[13] = '\n';
|
||||
}
|
||||
|
||||
rawinput::SextetDevice::SextetDevice(std::string device_name) {
|
||||
this->device_name = device_name;
|
||||
is_connected = false;
|
||||
all_off();
|
||||
}
|
||||
|
||||
rawinput::SextetDevice::~SextetDevice() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void rawinput::SextetDevice::set_all(bool state) {
|
||||
for (int i = 0; i < LIGHT_COUNT; i++)
|
||||
light_state[i] = state;
|
||||
}
|
||||
|
||||
void rawinput::SextetDevice::all_on() {
|
||||
set_all(true);
|
||||
}
|
||||
|
||||
void rawinput::SextetDevice::all_off() {
|
||||
set_all(false);
|
||||
}
|
||||
|
||||
bool rawinput::SextetDevice::connect() {
|
||||
|
||||
// open device
|
||||
device = CreateFileA(
|
||||
this->device_name.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
0,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
0
|
||||
);
|
||||
|
||||
// check device
|
||||
if (device == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
// get comm state
|
||||
DCB dcb;
|
||||
SecureZeroMemory(&dcb, sizeof(DCB));
|
||||
dcb.DCBlength = sizeof(DCB);
|
||||
if (!GetCommState(device, &dcb)) {
|
||||
log_warning("sextet", "GetCommState failed: 0x{:08x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// set comm state
|
||||
dcb.BaudRate = 115200;
|
||||
dcb.ByteSize = 8;
|
||||
dcb.Parity = NOPARITY;
|
||||
dcb.StopBits = ONESTOPBIT;
|
||||
if (!SetCommState(device, &dcb)) {
|
||||
log_warning("sextet", "SetCommState failed: 0x{:08x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// success
|
||||
is_connected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rawinput::SextetDevice::disconnect() {
|
||||
return CloseHandle(device);
|
||||
}
|
||||
|
||||
bool rawinput::SextetDevice::push_light_state() {
|
||||
|
||||
// build packet
|
||||
uint8_t buffer[FULL_SEXTET_COUNT];
|
||||
this->fill_packet(&(buffer[0]), &(light_state[0]));
|
||||
|
||||
// write data to device
|
||||
DWORD bytes_written;
|
||||
WriteFile(
|
||||
device,
|
||||
buffer,
|
||||
FULL_SEXTET_COUNT,
|
||||
&bytes_written,
|
||||
NULL
|
||||
);
|
||||
|
||||
// verify written bytes count
|
||||
return bytes_written == FULL_SEXTET_COUNT;
|
||||
}
|
||||
67
rawinput/sextet.h
Normal file
67
rawinput/sextet.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
namespace rawinput {
|
||||
|
||||
namespace SextetLights {
|
||||
enum {
|
||||
BASS_NEON = 0,
|
||||
|
||||
MARQUEE_P1_UPPER,
|
||||
MARQUEE_P1_LOWER,
|
||||
MARQUEE_P2_UPPER,
|
||||
MARQUEE_P2_LOWER,
|
||||
|
||||
P1_MENU,
|
||||
P2_MENU,
|
||||
|
||||
P1_UP,
|
||||
P1_DOWN,
|
||||
P1_LEFT,
|
||||
P1_RIGHT,
|
||||
|
||||
P2_UP,
|
||||
P2_DOWN,
|
||||
P2_LEFT,
|
||||
P2_RIGHT
|
||||
};
|
||||
}
|
||||
|
||||
class SextetDevice {
|
||||
private:
|
||||
|
||||
// COM port handle
|
||||
std::string device_name;
|
||||
HANDLE device;
|
||||
|
||||
// number of bytes to contain the full sextet pack and a trailing LF
|
||||
static const size_t FULL_SEXTET_COUNT = 14;
|
||||
|
||||
void set_all(bool state);
|
||||
void fill_packet(uint8_t *buffer, bool *light_state);
|
||||
|
||||
public:
|
||||
|
||||
// only 15 lights are used on SD DDR Cabinets.
|
||||
static const int LIGHT_COUNT = 15;
|
||||
static const std::string LIGHT_NAMES[];
|
||||
|
||||
// state
|
||||
bool is_connected;
|
||||
bool light_state[LIGHT_COUNT];
|
||||
|
||||
SextetDevice(std::string device_name);
|
||||
~SextetDevice();
|
||||
|
||||
bool connect();
|
||||
bool disconnect();
|
||||
|
||||
bool push_light_state();
|
||||
|
||||
void all_on();
|
||||
void all_off();
|
||||
};
|
||||
}
|
||||
63
rawinput/smxstage.cpp
Normal file
63
rawinput/smxstage.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "smxstage.h"
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
#include <SMX.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace {
|
||||
static const std::string PANEL_NAMES[] = {
|
||||
"Top-Left", "Top-Center", "Top-Right",
|
||||
"Middle-Left", "Middle-Center", "Middle-Right",
|
||||
"Bottom-Left", "Bottom-Center", "Bottom-Right"
|
||||
};
|
||||
|
||||
void NullSmxCallback(int pad, SMXUpdateCallbackReason reason, void *pUser) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace rawinput {
|
||||
std::string SmxStageDevice::GetLightNameByIndex(size_t index) {
|
||||
const size_t subpixel = index%3;
|
||||
const size_t panel = (index/3)%PANEL_COUNT;
|
||||
const size_t pad = (index/3)/PANEL_COUNT;
|
||||
|
||||
switch (subpixel) {
|
||||
case 0: return fmt::format("Pad {} Panel {} Red", pad+1, PANEL_NAMES[panel]);
|
||||
case 1: return fmt::format("Pad {} Panel {} Green", pad+1, PANEL_NAMES[panel]);
|
||||
case 2: return fmt::format("Pad {} Panel {} Blue", pad+1, PANEL_NAMES[panel]);
|
||||
}
|
||||
|
||||
return "StepmaniaX invalid index";
|
||||
}
|
||||
|
||||
SmxStageDevice::SmxStageDevice() :
|
||||
m_lightData(TOTAL_LIGHT_COUNT*LEDS_PER_PAD),
|
||||
m_lightDataMutex()
|
||||
{
|
||||
}
|
||||
|
||||
bool SmxStageDevice::Initialize() {
|
||||
SMX_Start(&NullSmxCallback, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SmxStageDevice::SetLightByIndex(size_t index, uint8_t value) {
|
||||
const size_t subpixel = index%3;
|
||||
const size_t panel = (index/3)%PANEL_COUNT;
|
||||
const size_t pad = (index/3)/PANEL_COUNT;
|
||||
|
||||
m_lightDataMutex.lock();
|
||||
for(size_t i = 0; i < LEDS_PER_PAD; ++i) {
|
||||
m_lightData[(pad*PANEL_COUNT*LEDS_PER_PAD*3)+(panel*LEDS_PER_PAD*3)+(i*3)+subpixel] = value;
|
||||
}
|
||||
m_lightDataMutex.unlock();
|
||||
}
|
||||
|
||||
void SmxStageDevice::Update() {
|
||||
m_lightDataMutex.lock();
|
||||
SMX_SetLights2(reinterpret_cast<const char*>(&m_lightData[0]), m_lightData.size());
|
||||
m_lightDataMutex.unlock();
|
||||
}
|
||||
}
|
||||
29
rawinput/smxstage.h
Normal file
29
rawinput/smxstage.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
namespace rawinput {
|
||||
class SmxStageDevice {
|
||||
public:
|
||||
static constexpr int PAD_COUNT = 2;
|
||||
static constexpr int PANEL_COUNT = 9;
|
||||
static constexpr int LEDS_PER_PAD = 25;
|
||||
static constexpr int TOTAL_LIGHT_COUNT = PAD_COUNT*PANEL_COUNT*3;
|
||||
|
||||
static inline size_t GetPanelIndexR(size_t pad, size_t panel) {return ((pad*PANEL_COUNT)+panel)*3 + 0;};
|
||||
static inline size_t GetPanelIndexG(size_t pad, size_t panel) {return ((pad*PANEL_COUNT)+panel)*3 + 1;};
|
||||
static inline size_t GetPanelIndexB(size_t pad, size_t panel) {return ((pad*PANEL_COUNT)+panel)*3 + 2;};
|
||||
|
||||
static std::string GetLightNameByIndex(size_t index);
|
||||
|
||||
SmxStageDevice();
|
||||
bool Initialize();
|
||||
void SetLightByIndex(size_t index, uint8_t value);
|
||||
void Update();
|
||||
private:
|
||||
std::vector<uint8_t> m_lightData;
|
||||
std::mutex m_lightDataMutex;
|
||||
};
|
||||
}
|
||||
567
rawinput/touch.cpp
Normal file
567
rawinput/touch.cpp
Normal file
@@ -0,0 +1,567 @@
|
||||
#include "touch.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <windows.h>
|
||||
#include <versionhelpers.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/time.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
// std::min
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
// std::max
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace rawinput::touch {
|
||||
|
||||
// settings
|
||||
bool DISABLED = false;
|
||||
bool INVERTED = false;
|
||||
|
||||
// state
|
||||
static bool DISPLAY_INITIALIZED = false;
|
||||
static DWORD DISPLAY_ORIENTATION = DMDO_DEFAULT;
|
||||
static long DISPLAY_SIZE_X = 1920L;
|
||||
static long DISPLAY_SIZE_Y = 1080L;
|
||||
|
||||
bool is_touchscreen(Device *device) {
|
||||
|
||||
// check if disabled
|
||||
if (DISABLED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check device type
|
||||
if (device->type != HID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *hid = device->hidInfo;
|
||||
auto &attributes = hid->attributes;
|
||||
|
||||
// filter by VID/PID
|
||||
if (attributes.VendorID > 0) {
|
||||
|
||||
// P2418HT
|
||||
// touch points apparently aren't released and will stay
|
||||
if (attributes.VendorID == 0x1FD2 && attributes.ProductID == 0x6103) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore the "Touch Pad" (0x05) usage under the "Digitizers" (0x0d) usage page
|
||||
if (hid->caps.UsagePage == 0x0d && hid->caps.Usage == 0x05) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check description
|
||||
if (device->desc == "HID-compliant touch screen") {
|
||||
// TODO: this only works on english OS (?)
|
||||
return true;
|
||||
}
|
||||
|
||||
// we can also check if there's touch point values inside
|
||||
for (const auto &value_name : hid->value_caps_names) {
|
||||
if (value_name == "Contact identifier") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// nope this one probably not
|
||||
return false;
|
||||
}
|
||||
|
||||
void enable(Device *device) {
|
||||
if (device->type == HID) {
|
||||
device->hidInfo->touch.valid = true;
|
||||
log_info("rawinput", "enabled touchscreen device: {} ({})", device->desc, device->name);
|
||||
} else {
|
||||
log_fatal("rawinput", "tried to enable touch functionality on non HID device");
|
||||
}
|
||||
}
|
||||
|
||||
void disable(Device *device) {
|
||||
if (device->type == HID) {
|
||||
device->hidInfo->touch.valid = false;
|
||||
log_info("rawinput", "disabled touchscreen device: {} ({})", device->desc, device->name);
|
||||
} else
|
||||
log_fatal("rawinput", "tried to disable touch functionality on non HID device");
|
||||
}
|
||||
|
||||
void update_input(Device *device) {
|
||||
|
||||
// check type
|
||||
if (device->type != HID) {
|
||||
log_fatal("rawinput", "touch update called on non HID device");
|
||||
return;
|
||||
}
|
||||
|
||||
// get touch info
|
||||
auto hid = device->hidInfo;
|
||||
auto &touch = hid->touch;
|
||||
if (!touch.valid) {
|
||||
|
||||
// not a registered touchscreen
|
||||
return;
|
||||
}
|
||||
|
||||
// parse elements
|
||||
if (!touch.parsed_elements) {
|
||||
log_info("rawinput", "parsing touch elements");
|
||||
|
||||
// clear lists first
|
||||
touch.elements_contact_count.clear();
|
||||
touch.elements_contact_identifier.clear();
|
||||
touch.elements_x.clear();
|
||||
touch.elements_y.clear();
|
||||
touch.elements_width.clear();
|
||||
touch.elements_height.clear();
|
||||
touch.elements_pressed.clear();
|
||||
touch.elements_pressure.clear();
|
||||
|
||||
// get value indices
|
||||
for (int i = 0; i < (int) hid->value_caps_names.size(); i++) {
|
||||
auto &name = hid->value_caps_names[i];
|
||||
if (name == "Contact count") {
|
||||
touch.elements_contact_count.push_back(i);
|
||||
} else if (name == "X") {
|
||||
touch.elements_x.push_back(i);
|
||||
} else if (name == "Y") {
|
||||
touch.elements_y.push_back(i);
|
||||
} else if (name == "Width") {
|
||||
touch.elements_width.push_back(i);
|
||||
} else if (name == "Height") {
|
||||
touch.elements_height.push_back(i);
|
||||
} else if (name == "Contact identifier") {
|
||||
touch.elements_contact_identifier.push_back(i);
|
||||
} else if (name == "Pressure") {
|
||||
touch.elements_pressure.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// get button indices
|
||||
for (int i = 0; i < (int) hid->button_caps_names.size(); i++) {
|
||||
auto &name = hid->button_caps_names[i];
|
||||
if (name == "Tip Switch") {
|
||||
touch.elements_pressed.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// check sizes
|
||||
auto touch_size = touch.elements_contact_identifier.size();
|
||||
if (touch_size != touch.elements_x.size() ||
|
||||
touch_size != touch.elements_y.size() ||
|
||||
touch_size != touch.elements_pressed.size())
|
||||
{
|
||||
log_info("rawinput", "touch element size mismatch: {}:contacts, {}:x, {}:y, {}:pressed",
|
||||
touch.elements_contact_identifier.size(),
|
||||
touch.elements_x.size(),
|
||||
touch.elements_y.size(),
|
||||
touch.elements_pressed.size());
|
||||
disable(device);
|
||||
return;
|
||||
}
|
||||
|
||||
// mark as parsed
|
||||
touch.parsed_elements = true;
|
||||
log_info("rawinput", "touch elements parsed: {} status fields", touch.elements_x.size());
|
||||
}
|
||||
|
||||
// check if display is initialized
|
||||
if (!DISPLAY_INITIALIZED) {
|
||||
display_update();
|
||||
}
|
||||
|
||||
// update timeouts here as well so events are in the right order
|
||||
update_timeouts(device);
|
||||
|
||||
// determine the number of touches contained in this report, defaulting to the size of `elements_x`
|
||||
size_t touch_report_count = touch.elements_x.size();
|
||||
if (!touch.elements_contact_count.empty()) {
|
||||
|
||||
// support devices that have multiple "Contact count" fields, for some reason
|
||||
size_t contact_count = 0;
|
||||
for (auto &index : touch.elements_contact_count) {
|
||||
contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]);
|
||||
}
|
||||
|
||||
// hybrid mode devices will report a contact count of 0 for subsequent reports that are
|
||||
// part of the same initial frame
|
||||
if (contact_count > 0) {
|
||||
if (contact_count > touch_report_count) {
|
||||
touch.remaining_contact_count = contact_count - touch_report_count;
|
||||
} else {
|
||||
touch_report_count = contact_count;
|
||||
touch.remaining_contact_count = 0;
|
||||
}
|
||||
} else if (touch.remaining_contact_count > 0) {
|
||||
if (touch.remaining_contact_count > touch_report_count) {
|
||||
touch.remaining_contact_count -= touch_report_count;
|
||||
} else {
|
||||
touch_report_count = touch.remaining_contact_count;
|
||||
touch.remaining_contact_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iterate all input data and get touch points
|
||||
std::vector<HIDTouchPoint> touch_points;
|
||||
touch_points.reserve(touch.elements_x.size());
|
||||
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
||||
|
||||
// build touch point
|
||||
HIDTouchPoint hid_tp{};
|
||||
auto pos_x = hid->value_states[touch.elements_x[i]];
|
||||
auto pos_y = hid->value_states[touch.elements_y[i]];
|
||||
switch (DISPLAY_ORIENTATION) {
|
||||
case DMDO_DEFAULT:
|
||||
hid_tp.x = pos_x;
|
||||
hid_tp.y = pos_y;
|
||||
break;
|
||||
case DMDO_90:
|
||||
hid_tp.x = 1.f - pos_y;
|
||||
hid_tp.y = pos_x;
|
||||
break;
|
||||
case DMDO_180:
|
||||
hid_tp.x = 1.f - pos_x;
|
||||
hid_tp.y = 1.f - pos_y;
|
||||
break;
|
||||
case DMDO_270:
|
||||
hid_tp.x = pos_y;
|
||||
hid_tp.y = 1.f - pos_x;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// optionally invert
|
||||
if (INVERTED) {
|
||||
hid_tp.x = 1.f - hid_tp.x;
|
||||
hid_tp.y = 1.f - hid_tp.y;
|
||||
}
|
||||
|
||||
// check if this touch point should be considered valid
|
||||
//
|
||||
// If "Contact count" reports there are no touch reports remaining and the X and Y
|
||||
// coordinates of this touch point are zero, then this report element should be
|
||||
// skipped.
|
||||
if (touch_report_count == 0 && hid_tp.x == 0.f && hid_tp.y == 0.f) {
|
||||
continue;
|
||||
} else if (touch_report_count > 0) {
|
||||
touch_report_count--;
|
||||
}
|
||||
|
||||
// generate ID (hopefully unique)
|
||||
hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]];
|
||||
hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512);
|
||||
|
||||
//std::string src = "(none)";
|
||||
|
||||
// check if tip switch is down
|
||||
size_t index_pressed = touch.elements_pressed[i];
|
||||
for (auto &button_states : hid->button_states) {
|
||||
if (index_pressed < button_states.size()) {
|
||||
hid_tp.down = button_states[index_pressed];
|
||||
|
||||
/*
|
||||
if (hid_tp.down)
|
||||
src = "pressed (index: " + to_string(touch.elements_pressed[i]) + ", state: " + to_string(button_states[index_pressed]) +")";
|
||||
*/
|
||||
|
||||
break;
|
||||
} else
|
||||
index_pressed -= button_states.size();
|
||||
}
|
||||
|
||||
// check width/height of touch point to see if pressed
|
||||
float width = 0.f, height = 0.f;
|
||||
if (!hid_tp.down && i < touch.elements_width.size() && i < touch.elements_height.size()) {
|
||||
width = hid->value_states[touch.elements_width[i]];
|
||||
height = hid->value_states[touch.elements_height[i]];
|
||||
hid_tp.down = width > 0.f && height > 0.f;
|
||||
|
||||
/*
|
||||
if (hid_tp.down)
|
||||
src = "width_height (width index: " + to_string(touch.elements_width[i]) + ", height index: " + to_string(touch.elements_height[i]) + ")";
|
||||
*/
|
||||
}
|
||||
|
||||
// so last thing we can check is the pressure
|
||||
if (!hid_tp.down && i < touch.elements_pressure.size()) {
|
||||
auto pressure = hid->value_states[touch.elements_pressure[i]];
|
||||
hid_tp.down = pressure > 0.f;
|
||||
|
||||
/*
|
||||
if (hid_tp.down)
|
||||
src = "pressure (index: " + to_string(touch.elements_pressure[i]) + ")";
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
log_info("rawinput",
|
||||
"touch i: " + to_string(i) +
|
||||
" (id: " + to_string(hid_tp.id) +
|
||||
"), ci: " + to_string(hid->value_states_raw[touch.elements_contact_identifier[i]]) +
|
||||
", x: " + to_string(pos_x) +
|
||||
", y: " + to_string(pos_y) +
|
||||
", width: " + to_string(width) +
|
||||
", height: " + to_string(height) +
|
||||
", down: " + to_string(hid_tp.down) +
|
||||
", src: " + src);
|
||||
*/
|
||||
|
||||
// add to touch points
|
||||
touch_points.emplace_back(hid_tp);
|
||||
}
|
||||
|
||||
// process touch points
|
||||
std::vector<DWORD> touch_removes;
|
||||
std::vector<TouchPoint> touch_writes;
|
||||
std::vector<DWORD> touch_modifications;
|
||||
for (auto &hid_tp : touch_points) {
|
||||
|
||||
// check if existing
|
||||
auto existing = std::find_if(
|
||||
touch.touch_points.begin(),
|
||||
touch.touch_points.end(),
|
||||
[hid_tp] (const HIDTouchPoint &x) {
|
||||
return x.id == hid_tp.id;
|
||||
});
|
||||
|
||||
/*
|
||||
ssize_t ttl = -1;
|
||||
int64_t last_report = -1;
|
||||
if (existing != touch.touch_points.end()) {
|
||||
ttl = existing->ttl;
|
||||
last_report = existing->last_report;
|
||||
}
|
||||
|
||||
log_info("rawinput",
|
||||
"id: " + to_string(hid_tp.id) +
|
||||
", existing: " + to_string(existing != touch.touch_points.end()) +
|
||||
", ttl: " + to_string(ttl) +
|
||||
", last_report: " + to_string(last_report) +
|
||||
", down: " + to_string(hid_tp.down));
|
||||
*/
|
||||
|
||||
// check if pressed
|
||||
if (!hid_tp.down) {
|
||||
|
||||
// only remove if it exists
|
||||
if (existing != touch.touch_points.end()) {
|
||||
|
||||
// remove all touch points with this ID
|
||||
touch_removes.push_back(hid_tp.id);
|
||||
touch.touch_points.erase(std::remove_if(
|
||||
touch.touch_points.begin(),
|
||||
touch.touch_points.end(),
|
||||
[hid_tp] (const HIDTouchPoint &x) {
|
||||
return x.id == hid_tp.id;
|
||||
}), touch.touch_points.end());
|
||||
}
|
||||
} else {
|
||||
|
||||
// write touch point
|
||||
TouchPoint tp {
|
||||
.id = hid_tp.id,
|
||||
.x = (long) (hid_tp.x * DISPLAY_SIZE_X),
|
||||
.y = (long) (hid_tp.y * DISPLAY_SIZE_Y),
|
||||
.mouse = false,
|
||||
};
|
||||
touch_writes.push_back(tp);
|
||||
touch_modifications.push_back(hid_tp.id);
|
||||
|
||||
// check if existing
|
||||
if (existing == touch.touch_points.end()) {
|
||||
|
||||
// add new touch point
|
||||
touch.touch_points.push_back(hid_tp);
|
||||
|
||||
} else {
|
||||
|
||||
// update existing touch point
|
||||
*existing = hid_tp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set TTL and last report time
|
||||
if (!touch.elements_x.empty() && !touch_modifications.empty()) {
|
||||
auto ttl = touch.touch_points.size() / touch.elements_x.size() + 1;
|
||||
auto system_time_ms = get_system_milliseconds();
|
||||
for (auto &hid_tp_id : touch_modifications) {
|
||||
for (auto &hid_tp : touch.touch_points) {
|
||||
if (hid_tp.id == hid_tp_id) {
|
||||
hid_tp.ttl = ttl;
|
||||
hid_tp.last_report = system_time_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove dead touch points
|
||||
auto ttl_it = touch.touch_points.begin();
|
||||
while (ttl_it != touch.touch_points.end()) {
|
||||
auto &tp = *ttl_it;
|
||||
if (tp.ttl == 0) {
|
||||
touch_removes.push_back(tp.id);
|
||||
ttl_it = touch.touch_points.erase(ttl_it);
|
||||
} else {
|
||||
tp.ttl--;
|
||||
ttl_it++;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SPICETOOLS_SPICECFG_STANDALONE
|
||||
|
||||
// update touch module
|
||||
touch_remove_points(&touch_removes);
|
||||
touch_write_points(&touch_writes);
|
||||
#endif
|
||||
}
|
||||
|
||||
void update_timeouts(Device *device) {
|
||||
|
||||
// check type
|
||||
if (device->type != HID) {
|
||||
log_fatal("rawinput", "touch timeout update called on non HID device");
|
||||
return;
|
||||
}
|
||||
|
||||
// get touch info
|
||||
auto hid = device->hidInfo;
|
||||
auto &touch = hid->touch;
|
||||
if (!touch.valid) {
|
||||
return; // not a registered touchscreen
|
||||
}
|
||||
|
||||
// calculate deadline - we allow the devices 50ms of not sending shit
|
||||
auto deadline = get_system_milliseconds() - 50;
|
||||
|
||||
// check touch points
|
||||
std::vector<DWORD> touch_removes;
|
||||
auto touch_it = touch.touch_points.begin();
|
||||
while (touch_it != touch.touch_points.end()) {
|
||||
auto &hid_tp = *touch_it;
|
||||
|
||||
// see if it's behind the deadline
|
||||
if (hid_tp.last_report < deadline) {
|
||||
|
||||
// oops it's gone
|
||||
touch_removes.push_back(hid_tp.id);
|
||||
touch_it = touch.touch_points.erase(touch_it);
|
||||
|
||||
} else {
|
||||
|
||||
// check the next device
|
||||
touch_it++;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SPICETOOLS_SPICECFG_STANDALONE
|
||||
|
||||
// remove from touch module
|
||||
touch_remove_points(&touch_removes);
|
||||
#endif
|
||||
}
|
||||
|
||||
void update_timeouts(RawInputManager *manager) {
|
||||
|
||||
// iterate all devices
|
||||
for (auto &device : manager->devices_get()) {
|
||||
|
||||
// check if it's a valid touchscreen
|
||||
if (device.type == HID && device.hidInfo->touch.valid) {
|
||||
|
||||
// lock n' load
|
||||
device.mutex->lock();
|
||||
update_timeouts(&device);
|
||||
device.mutex->unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_enabled(RawInputManager *manager) {
|
||||
|
||||
// check if disabled or manager is null
|
||||
if (DISABLED || manager == nullptr)
|
||||
return false;
|
||||
|
||||
// check if at least one device is marked as valid touchscreen
|
||||
for (auto &device : manager->devices_get()) {
|
||||
if (device.type == HID && device.hidInfo->touch.valid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// no valid touch screen found
|
||||
return false;
|
||||
}
|
||||
|
||||
void display_update() {
|
||||
|
||||
// check if disabled
|
||||
if (DISABLED)
|
||||
return;
|
||||
|
||||
// determine monitor size
|
||||
static RECT display_rect;
|
||||
GetWindowRect(GetDesktopWindow(), &display_rect);
|
||||
DISPLAY_SIZE_X = display_rect.right - display_rect.left;
|
||||
DISPLAY_SIZE_Y = display_rect.bottom - display_rect.top;
|
||||
log_info("rawinput", "display size: {}x{}", (int) DISPLAY_SIZE_X, (int) DISPLAY_SIZE_Y);
|
||||
|
||||
// determine monitor orientation
|
||||
DEVMODE display_mode{};
|
||||
display_mode.dmSize = sizeof(DEVMODE);
|
||||
if (!EnumDisplaySettingsEx(nullptr, ENUM_CURRENT_SETTINGS, &display_mode, EDS_RAWMODE)) {
|
||||
log_info("rawinput", "failed to determine monitor mode");
|
||||
} else if (display_mode.dmFields & DM_DISPLAYORIENTATION) {
|
||||
DISPLAY_ORIENTATION = display_mode.dmDisplayOrientation;
|
||||
switch (DISPLAY_ORIENTATION) {
|
||||
case DMDO_DEFAULT:
|
||||
log_info("rawinput", "display rotation: 0");
|
||||
break;
|
||||
case DMDO_90:
|
||||
log_info("rawinput", "display rotation: 90");
|
||||
break;
|
||||
case DMDO_180:
|
||||
log_info("rawinput", "display rotation: 180");
|
||||
break;
|
||||
case DMDO_270:
|
||||
log_info("rawinput", "display rotation: 270");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// another XP fix
|
||||
if (!IsWindowsVistaOrGreater()) {
|
||||
switch (DISPLAY_ORIENTATION) {
|
||||
case DMDO_90:
|
||||
DISPLAY_ORIENTATION = DMDO_180;
|
||||
log_info("rawinput", "flipping to 180");
|
||||
break;
|
||||
case DMDO_270:
|
||||
DISPLAY_ORIENTATION = DMDO_90;
|
||||
log_info("rawinput", "flipping to 90");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_info("rawinput", "failed to determine monitor orientation");
|
||||
}
|
||||
|
||||
// mark as initialized
|
||||
DISPLAY_INITIALIZED = true;
|
||||
}
|
||||
}
|
||||
20
rawinput/touch.h
Normal file
20
rawinput/touch.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "device.h"
|
||||
#include "rawinput.h"
|
||||
|
||||
namespace rawinput::touch {
|
||||
|
||||
// settings
|
||||
extern bool DISABLED;
|
||||
extern bool INVERTED;
|
||||
|
||||
bool is_touchscreen(Device *device);
|
||||
void enable(Device *device);
|
||||
void disable(Device *device);
|
||||
void update_input(Device *device);
|
||||
void update_timeouts(Device *device);
|
||||
void update_timeouts(RawInputManager *manager);
|
||||
bool is_enabled(RawInputManager *manager);
|
||||
void display_update();
|
||||
}
|
||||
Reference in New Issue
Block a user