2681 lines
108 KiB
C++
2681 lines
108 KiB
C++
|
#include "rawinput.h"
|
||
|
|
||
|
#include <cstdarg>
|
||
|
#include <utility>
|
||
|
#include <vector>
|
||
|
|
||
|
#include <objbase.h>
|
||
|
#include <setupapi.h>
|
||
|
|
||
|
#include "util/logging.h"
|
||
|
#include "util/time.h"
|
||
|
#include "util/utils.h"
|
||
|
|
||
|
#include "piuio.h"
|
||
|
#include "touch.h"
|
||
|
|
||
|
extern "C" {
|
||
|
#include "external/usbhidusage/usb-hid-usage.h"
|
||
|
}
|
||
|
|
||
|
namespace rawinput {
|
||
|
|
||
|
// settings
|
||
|
bool NOLEGACY = false;
|
||
|
uint8_t HID_LIGHT_BRIGHTNESS = 100; // 100%
|
||
|
bool ENABLE_SMX_STAGE = false;
|
||
|
int TOUCHSCREEN_RANGE_X = 0;
|
||
|
int TOUCHSCREEN_RANGE_Y = 0;
|
||
|
}
|
||
|
|
||
|
rawinput::RawInputManager::RawInputManager() {
|
||
|
|
||
|
// create input window and load in devices
|
||
|
this->input_hwnd_create();
|
||
|
this->devices_reload();
|
||
|
|
||
|
// start flushing thread
|
||
|
this->output_start();
|
||
|
this->flush_start();
|
||
|
|
||
|
// now create the hotplug manager on that window
|
||
|
this->hotplug = new HotplugManager(this, this->input_hwnd);
|
||
|
}
|
||
|
|
||
|
rawinput::RawInputManager::~RawInputManager() {
|
||
|
this->stop();
|
||
|
|
||
|
log_info("rawinput", "destructor done");
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::stop() {
|
||
|
if (this->hotplug) {
|
||
|
|
||
|
// remove hotplug
|
||
|
delete this->hotplug;
|
||
|
this->hotplug = nullptr;
|
||
|
}
|
||
|
|
||
|
// unregister device messages
|
||
|
this->devices_unregister();
|
||
|
|
||
|
// stop threads
|
||
|
this->flush_stop();
|
||
|
this->output_stop();
|
||
|
|
||
|
// destruct all devices and input window
|
||
|
this->devices_destruct();
|
||
|
this->input_hwnd_destroy();
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::input_hwnd_create() {
|
||
|
|
||
|
// register window class
|
||
|
this->input_hwnd_class.cbSize = sizeof(WNDCLASSEX);
|
||
|
this->input_hwnd_class.hInstance = GetModuleHandle(nullptr);
|
||
|
this->input_hwnd_class.lpfnWndProc = rawinput::RawInputManager::input_wnd_proc;
|
||
|
this->input_hwnd_class.lpszClassName = "SpiceTools Input";
|
||
|
if (!RegisterClassEx(&this->input_hwnd_class)) {
|
||
|
log_warning("rawinput", "could not register input class");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// create input thread
|
||
|
this->input_thread = new std::thread([this]() {
|
||
|
|
||
|
// increase priority
|
||
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||
|
|
||
|
// create window
|
||
|
this->input_hwnd = CreateWindowExA(
|
||
|
0,
|
||
|
this->input_hwnd_class.lpszClassName,
|
||
|
"SpiceTools Input",
|
||
|
0, 0, 0, 0, 0,
|
||
|
nullptr,
|
||
|
nullptr,
|
||
|
this->input_hwnd_class.hInstance,
|
||
|
this
|
||
|
);
|
||
|
|
||
|
// window loop
|
||
|
MSG msg;
|
||
|
while (GetMessage(&msg, this->input_hwnd, 0, 0) > 0) {
|
||
|
TranslateMessage(&msg);
|
||
|
DispatchMessage(&msg);
|
||
|
}
|
||
|
|
||
|
DestroyWindow(this->input_hwnd);
|
||
|
this->input_hwnd = nullptr;
|
||
|
});
|
||
|
|
||
|
// wait for window creation being done
|
||
|
while (!this->input_hwnd) {
|
||
|
Sleep(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::input_hwnd_destroy() {
|
||
|
if (this->input_hwnd) {
|
||
|
|
||
|
// post close and join
|
||
|
PostMessage(this->input_hwnd, WM_CLOSE, 0, 0);
|
||
|
}
|
||
|
|
||
|
if (this->input_thread) {
|
||
|
this->input_thread->join();
|
||
|
|
||
|
// delete thread
|
||
|
delete this->input_thread;
|
||
|
this->input_thread = nullptr;
|
||
|
}
|
||
|
|
||
|
// unregister the window class
|
||
|
UnregisterClass(this->input_hwnd_class.lpszClassName, this->input_hwnd_class.hInstance);
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_reload() {
|
||
|
this->devices_destruct();
|
||
|
log_info("rawinput", "reloading devices");
|
||
|
|
||
|
// scan for devices
|
||
|
this->devices_scan_rawinput();
|
||
|
this->devices_scan_midi();
|
||
|
this->devices_scan_piuio();
|
||
|
|
||
|
if(ENABLE_SMX_STAGE) {
|
||
|
this->devices_scan_smxstage();
|
||
|
}
|
||
|
|
||
|
// check for LIT Board
|
||
|
sextet_register("COM54", "LIT Board", false);
|
||
|
|
||
|
// register devices
|
||
|
this->devices_register();
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_scan_rawinput(const std::string &device_name) {
|
||
|
|
||
|
// get number of devices
|
||
|
UINT device_no = 0;
|
||
|
if (GetRawInputDeviceList(nullptr, &device_no, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
|
||
|
return;
|
||
|
}
|
||
|
if (!device_no) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// get device list
|
||
|
std::shared_ptr<RAWINPUTDEVICELIST> device_list(new RAWINPUTDEVICELIST[device_no]);
|
||
|
GetRawInputDeviceList(device_list.get(), &device_no, sizeof(RAWINPUTDEVICELIST));
|
||
|
if (!device_no) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// iterate devices
|
||
|
for (UINT device_cur_index = 0; device_cur_index < device_no; device_cur_index++) {
|
||
|
auto device = &device_list.get()[device_cur_index];
|
||
|
if (device_name.length() == 0) {
|
||
|
devices_scan_rawinput(device, false);
|
||
|
} else if (device_name == rawinput::RawInputManager::rawinput_get_device_name(device->hDevice)) {
|
||
|
log_info("rawinput", "scanning device: {}", device_name);
|
||
|
devices_scan_rawinput(device, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device, bool log) {
|
||
|
|
||
|
// get device name
|
||
|
std::string device_name = rawinput_get_device_name(device->hDevice);
|
||
|
|
||
|
// extract information out of name
|
||
|
auto device_info = rawinput::RawInputManager::get_device_info(device_name);
|
||
|
auto device_description = rawinput::RawInputManager::rawinput_get_device_description(device_info, device_name);
|
||
|
|
||
|
// get device information
|
||
|
RID_DEVICE_INFO rawinput_device_info {};
|
||
|
rawinput_device_info.cbSize = sizeof(RID_DEVICE_INFO);
|
||
|
UINT device_info_size = rawinput_device_info.cbSize;
|
||
|
if (GetRawInputDeviceInfo(device->hDevice, RIDI_DEVICEINFO, &rawinput_device_info, &device_info_size) == (UINT) -1) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// check for duplicate handle
|
||
|
size_t unique_id = devices.size() + 1;
|
||
|
for (size_t i = 0; i < this->devices.size(); i++) {
|
||
|
if (this->devices[i].name == device_name) {
|
||
|
unique_id = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// build device
|
||
|
Device new_device {};
|
||
|
new_device.id = unique_id;
|
||
|
new_device.handle = device->hDevice;
|
||
|
new_device.name = device_name;
|
||
|
new_device.desc = device_description;
|
||
|
new_device.info = device_info;
|
||
|
new_device.mutex = new std::mutex();
|
||
|
new_device.mutex_out = new std::mutex();
|
||
|
new_device.input_time = get_performance_seconds();
|
||
|
switch (device->dwType) {
|
||
|
case RIM_TYPEMOUSE:
|
||
|
new_device.type = MOUSE;
|
||
|
new_device.mouseInfo = new DeviceMouseInfo();
|
||
|
break;
|
||
|
case RIM_TYPEKEYBOARD:
|
||
|
new_device.type = KEYBOARD;
|
||
|
new_device.keyboardInfo = new DeviceKeyboardInfo();
|
||
|
break;
|
||
|
case RIM_TYPEHID: {
|
||
|
new_device.type = HID;
|
||
|
HIDDriver hid_driver = HIDDriver::Default;
|
||
|
HIDD_ATTRIBUTES hid_attributes {};
|
||
|
|
||
|
// get preparsed information
|
||
|
UINT preparsed_size = 0;
|
||
|
if (GetRawInputDeviceInfo(
|
||
|
device->hDevice,
|
||
|
RIDI_PREPARSEDDATA,
|
||
|
nullptr,
|
||
|
&preparsed_size) == (UINT) -1)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (!preparsed_size) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// allocate buffer
|
||
|
auto preparsed_data = util::make_unique_plain<_HIDP_PREPARSED_DATA>(preparsed_size);
|
||
|
|
||
|
if (GetRawInputDeviceInfo(
|
||
|
device->hDevice,
|
||
|
RIDI_PREPARSEDDATA,
|
||
|
preparsed_data.get(),
|
||
|
&preparsed_size) == (UINT) -1)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// get caps
|
||
|
_HIDP_CAPS caps {};
|
||
|
if (HidP_GetCaps(preparsed_data.get(), &caps) != HIDP_STATUS_SUCCESS) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// skip vendor-specific devices
|
||
|
//
|
||
|
// In the case of the Corsair Vengeance K70 RGB, the get device manufacturer and product
|
||
|
// string functions take 5 seconds each, which really delays the boot when it has three
|
||
|
// vendor-specific devices. Luckily, those three vendor-specific devices have the usage page
|
||
|
// appropriately set. This took Felix an hour to narrow down.
|
||
|
auto hid_vid = rawinput_device_info.hid.dwVendorId;
|
||
|
auto hid_pid = rawinput_device_info.hid.dwProductId;
|
||
|
if ((caps.UsagePage >> 8) == 0xFF
|
||
|
&& !(hid_vid == 0xBEEF && hid_pid == 0x5730)) // allow Minimaid
|
||
|
{
|
||
|
log_misc("rawinput", "skipping vendor-specific device");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// get usage description
|
||
|
std::string usage_name;
|
||
|
{
|
||
|
auto *usage_name_str = usb_hid_get_usage_text(caps.UsagePage, caps.Usage);
|
||
|
if (usage_name_str) {
|
||
|
usage_name = usage_name_str;
|
||
|
free(usage_name_str);
|
||
|
} else {
|
||
|
usage_name = fmt::format("Unknown (0x{:04x}:0x{:04x})", caps.UsagePage, caps.Usage);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get better device description
|
||
|
HANDLE hid_handle = CreateFile(
|
||
|
device_name.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||
|
nullptr,
|
||
|
OPEN_EXISTING,
|
||
|
0, nullptr);
|
||
|
if (hid_handle != INVALID_HANDLE_VALUE) {
|
||
|
|
||
|
// check manufacturer string
|
||
|
std::wstring man_ws;
|
||
|
wchar_t man_str_buffer[256] {};
|
||
|
if (HidD_GetManufacturerString(hid_handle, man_str_buffer, sizeof(man_str_buffer))) {
|
||
|
man_ws = std::wstring(man_str_buffer);
|
||
|
}
|
||
|
|
||
|
// get product string
|
||
|
std::wstring prod_ws;
|
||
|
wchar_t prod_str_buffer[256] {};
|
||
|
if (HidD_GetProductString(hid_handle, prod_str_buffer, sizeof(prod_str_buffer))) {
|
||
|
prod_ws = std::wstring(prod_str_buffer);
|
||
|
}
|
||
|
|
||
|
// For Bluetooth LE HID devices (e.g., newer Xbox controllers) HidD_GetProductString
|
||
|
// and HidD_GetManufacturerString will return blank strings.
|
||
|
// https://docs.microsoft.com/en-us/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html
|
||
|
if (man_ws.empty() && prod_ws.empty()) {
|
||
|
prod_ws = s2ws(device_description);
|
||
|
}
|
||
|
|
||
|
// build desc string
|
||
|
std::wstring desc_ws;
|
||
|
if (string_begins_with(prod_ws, man_ws)) {
|
||
|
desc_ws = prod_ws;
|
||
|
} else {
|
||
|
desc_ws = man_ws;
|
||
|
if (!man_ws.empty() && !prod_ws.empty()) {
|
||
|
desc_ws += L" ";
|
||
|
}
|
||
|
desc_ws += prod_ws;
|
||
|
}
|
||
|
|
||
|
// convert to normal string
|
||
|
new_device.desc = ws2s(desc_ws);
|
||
|
|
||
|
// get attributes
|
||
|
if (!HidD_GetAttributes(hid_handle, &hid_attributes)) {
|
||
|
log_warning("rawinput", "failed to get HID device attributes for {}", device_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get button caps
|
||
|
USHORT button_cap_length = caps.NumberInputButtonCaps;
|
||
|
std::vector<HIDP_BUTTON_CAPS> button_cap_data(static_cast<size_t>(button_cap_length));
|
||
|
if (button_cap_length > 0) {
|
||
|
if (HidP_GetButtonCaps(HidP_Input, button_cap_data.data(), &button_cap_length,
|
||
|
preparsed_data.get()) != HIDP_STATUS_SUCCESS)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
std::vector<HIDP_BUTTON_CAPS> button_caps_list;
|
||
|
std::vector<std::string> button_caps_names;
|
||
|
std::vector<std::vector<bool>> button_states;
|
||
|
std::vector<std::vector<double>> button_up, button_down;
|
||
|
for (int button_cap_num = 0; button_cap_num < button_cap_length; button_cap_num++) {
|
||
|
auto &button_caps = button_cap_data[button_cap_num];
|
||
|
|
||
|
// fill out range fields so we don't have to care later on
|
||
|
if (!button_caps.IsRange) {
|
||
|
button_caps.Range.UsageMin = button_caps.NotRange.Usage;
|
||
|
button_caps.Range.UsageMax = button_caps.NotRange.Usage;
|
||
|
button_caps.Range.DataIndexMin = button_caps.NotRange.DataIndex;
|
||
|
button_caps.Range.DataIndexMax = button_caps.NotRange.DataIndex;
|
||
|
button_caps.Range.DesignatorMin = button_caps.NotRange.DesignatorIndex;
|
||
|
button_caps.Range.DesignatorMax = button_caps.NotRange.DesignatorIndex;
|
||
|
button_caps.Range.StringMin = button_caps.NotRange.StringIndex;
|
||
|
button_caps.Range.StringMax = button_caps.NotRange.StringIndex;
|
||
|
}
|
||
|
|
||
|
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||
|
|
||
|
// ignore bad ranges reported by bad devices
|
||
|
if (button_count >= 0xffff) {
|
||
|
log_warning("rawinput", "skipping bad button cap range for device {}, range [{}, {}]",
|
||
|
device_name,
|
||
|
button_caps.Range.UsageMin,
|
||
|
button_caps.Range.UsageMax);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// fill vectors
|
||
|
button_caps_list.emplace_back(button_caps);
|
||
|
button_states.emplace_back(std::vector<bool>(static_cast<unsigned int>(button_count), false));
|
||
|
button_up.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
||
|
button_down.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
||
|
|
||
|
// names
|
||
|
for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) {
|
||
|
const char *name = usb_hid_get_usage_text(button_caps.UsagePage, usg);
|
||
|
button_caps_names.emplace_back(name ? name : "Button Control");
|
||
|
free((void *) name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get button output caps
|
||
|
USHORT button_output_cap_length = caps.NumberOutputButtonCaps;
|
||
|
std::vector<HIDP_BUTTON_CAPS> button_output_cap_data(static_cast<size_t>(button_output_cap_length));
|
||
|
if (button_output_cap_length > 0) {
|
||
|
if (HidP_GetButtonCaps(HidP_Output, button_output_cap_data.data(), &button_output_cap_length,
|
||
|
preparsed_data.get()) != HIDP_STATUS_SUCCESS)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
std::vector<HIDP_BUTTON_CAPS> button_output_caps_list;
|
||
|
std::vector<std::string> button_output_caps_names;
|
||
|
std::vector<std::vector<bool>> button_output_states;
|
||
|
for (int button_cap_num = 0; button_cap_num < button_output_cap_length; button_cap_num++) {
|
||
|
auto &button_caps = button_output_cap_data[button_cap_num];
|
||
|
|
||
|
// fill out range fields so we don't have to care later on
|
||
|
if (!button_caps.IsRange) {
|
||
|
button_caps.Range.UsageMin = button_caps.NotRange.Usage;
|
||
|
button_caps.Range.UsageMax = button_caps.NotRange.Usage;
|
||
|
button_caps.Range.DataIndexMin = button_caps.NotRange.DataIndex;
|
||
|
button_caps.Range.DataIndexMax = button_caps.NotRange.DataIndex;
|
||
|
button_caps.Range.DesignatorMin = button_caps.NotRange.DesignatorIndex;
|
||
|
button_caps.Range.DesignatorMax = button_caps.NotRange.DesignatorIndex;
|
||
|
button_caps.Range.StringMin = button_caps.NotRange.StringIndex;
|
||
|
button_caps.Range.StringMax = button_caps.NotRange.StringIndex;
|
||
|
}
|
||
|
|
||
|
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||
|
|
||
|
// ignore bad ranges reported by bad devices
|
||
|
if (button_count >= 0xffff) {
|
||
|
log_warning("rawinput", "skipping bad button output cap range for device {}, range [{}, {}]",
|
||
|
device_name,
|
||
|
button_caps.Range.UsageMin,
|
||
|
button_caps.Range.UsageMax);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// fill vectors
|
||
|
button_output_caps_list.emplace_back(button_caps);
|
||
|
button_output_states.emplace_back(std::vector<bool>(button_count, false));
|
||
|
|
||
|
// names
|
||
|
for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) {
|
||
|
|
||
|
// check for custom name
|
||
|
wchar_t custom_name[256]{};
|
||
|
bool custom_name_set = false;
|
||
|
if (hid_handle != INVALID_HANDLE_VALUE) {
|
||
|
|
||
|
// get string index
|
||
|
ULONG string_index = 0;
|
||
|
if (button_caps.IsStringRange && button_caps.Range.StringMin != 0) {
|
||
|
string_index = button_caps.Range.StringMin + static_cast<ULONG>(button_output_caps_list.size()) - 1;
|
||
|
}
|
||
|
else if (!button_caps.IsStringRange && button_caps.NotRange.StringIndex != 0) {
|
||
|
string_index = button_caps.NotRange.StringIndex;
|
||
|
}
|
||
|
|
||
|
// lookup string
|
||
|
if (string_index > 0 && HidD_GetIndexedString(
|
||
|
hid_handle,
|
||
|
string_index,
|
||
|
reinterpret_cast<void*>(custom_name),
|
||
|
sizeof(custom_name)))
|
||
|
{
|
||
|
custom_name_set = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if custom name is set
|
||
|
if (custom_name_set) {
|
||
|
|
||
|
// use custom name
|
||
|
button_output_caps_names.push_back(ws2s(std::wstring(custom_name)));
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// lookup generic name
|
||
|
const char* name = usb_hid_get_usage_text(button_caps.UsagePage, usg);
|
||
|
button_output_caps_names.emplace_back(name ? name : "Button Control");
|
||
|
free((void*)name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* PacDrive LED driver board ("Ultimarc LED Controller")
|
||
|
* It's HID descriptor is trash so we need to fix that
|
||
|
*/
|
||
|
if (hid_attributes.VendorID == 0xD209 && (hid_attributes.ProductID & 0xFFF8) == 0x1500) {
|
||
|
hid_driver = HIDDriver::PacDrive;
|
||
|
|
||
|
// clear
|
||
|
button_output_caps_list.clear();
|
||
|
button_output_caps_names.clear();
|
||
|
button_output_states.clear();
|
||
|
|
||
|
// fake the output LEDs
|
||
|
for (int i = 0; i < 16; i++) {
|
||
|
|
||
|
// create generic indicator caps
|
||
|
HIDP_BUTTON_CAPS fakeCaps {};
|
||
|
fakeCaps.Range.UsageMin = 0x4B;
|
||
|
fakeCaps.Range.UsageMax = 0x4B;
|
||
|
|
||
|
// add content to lists
|
||
|
button_output_caps_list.push_back(fakeCaps);
|
||
|
button_output_caps_names.push_back("LED " + to_string(i + 1));
|
||
|
button_output_states.emplace_back(std::vector<bool>(1, false));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get value caps
|
||
|
USHORT value_cap_length = caps.NumberInputValueCaps;
|
||
|
std::vector<HIDP_VALUE_CAPS> value_cap_data(value_cap_length);
|
||
|
if (value_cap_length > 0) {
|
||
|
if (HidP_GetValueCaps(HidP_Input, value_cap_data.data(), &value_cap_length,
|
||
|
preparsed_data.get()) != HIDP_STATUS_SUCCESS) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
std::vector<HIDP_VALUE_CAPS> value_caps_list;
|
||
|
std::vector<std::string> value_caps_names;
|
||
|
std::vector<float> value_states(value_cap_length, 0.5f);
|
||
|
std::vector<LONG> value_states_raw(value_cap_length, 0);
|
||
|
std::vector<float> bind_value_states(value_cap_length, 0.5f);
|
||
|
|
||
|
// erratum for incorrect min/max reported by DJ DAO IIDX controller in HID-light mode
|
||
|
// (2012 version with updateable firmware)
|
||
|
bool is_dao_iidx =
|
||
|
(hid_attributes.VendorID == 0x1CCF &&
|
||
|
hid_attributes.ProductID == 0x8048 &&
|
||
|
new_device.desc == "MY-POWER CO.,LTD. PS3Controller");
|
||
|
|
||
|
for (int value_cap_num = 0; value_cap_num < value_cap_length; value_cap_num++) {
|
||
|
auto &value_caps = value_cap_data[value_cap_num];
|
||
|
|
||
|
if (is_dao_iidx && value_caps.BitSize == 8) {
|
||
|
log_info("rawinput", "Override analog range for device {}. Replacing [{}, {}] with [{}, {}]",
|
||
|
new_device.name,
|
||
|
value_caps.LogicalMin, value_caps.LogicalMax,
|
||
|
0, 255);
|
||
|
value_caps.LogicalMin = 0;
|
||
|
value_caps.LogicalMax = 255;
|
||
|
}
|
||
|
|
||
|
// fix min and max values
|
||
|
if (value_caps.BitSize > 0 && value_caps.BitSize <= sizeof(value_caps.LogicalMin) * 8) {
|
||
|
auto shift_size = sizeof(value_caps.LogicalMin) * 8 - value_caps.BitSize + 1;
|
||
|
auto mask = ((uint64_t) 1 << value_caps.BitSize) - 1;
|
||
|
value_caps.LogicalMin &= mask;
|
||
|
value_caps.LogicalMin <<= shift_size;
|
||
|
value_caps.LogicalMin >>= shift_size;
|
||
|
value_caps.LogicalMax &= mask;
|
||
|
}
|
||
|
|
||
|
// fix up hat switch to initially report as neutral position
|
||
|
if (value_caps.UsagePage == 0x1 && value_caps.Range.UsageMin == 0x39) {
|
||
|
value_states[value_cap_num] = -1.f;
|
||
|
}
|
||
|
|
||
|
// add to list
|
||
|
value_caps_list.emplace_back(value_caps);
|
||
|
|
||
|
// names
|
||
|
const char *name = usb_hid_get_usage_text(value_caps.UsagePage, value_caps.Range.UsageMin);
|
||
|
value_caps_names.emplace_back(name ? name : "Analog Control");
|
||
|
free((void *) name);
|
||
|
}
|
||
|
|
||
|
// get value output caps
|
||
|
USHORT value_output_cap_length = caps.NumberOutputValueCaps;
|
||
|
std::vector<HIDP_VALUE_CAPS> value_output_cap_data(static_cast<size_t>(value_output_cap_length));
|
||
|
if (value_output_cap_length > 0) {
|
||
|
if (HidP_GetValueCaps(HidP_Output, value_output_cap_data.data(), &value_output_cap_length,
|
||
|
preparsed_data.get()) != HIDP_STATUS_SUCCESS) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
std::vector<HIDP_VALUE_CAPS> value_output_caps_list;
|
||
|
std::vector<std::string> value_output_caps_names;
|
||
|
std::vector<float> value_output_states;
|
||
|
for (size_t value_cap_num = 0; value_cap_num < value_output_cap_length; value_cap_num++) {
|
||
|
auto &value_caps = value_output_cap_data[value_cap_num];
|
||
|
|
||
|
// fix min and max values
|
||
|
if (value_caps.BitSize > 0 && value_caps.BitSize <= sizeof(value_caps.LogicalMin) * 8) {
|
||
|
auto shift_size = sizeof(value_caps.LogicalMin) * 8 - value_caps.BitSize + 1;
|
||
|
auto mask = ((uint64_t) 1 << value_caps.BitSize) - 1;
|
||
|
value_caps.LogicalMin &= mask;
|
||
|
value_caps.LogicalMin <<= shift_size;
|
||
|
value_caps.LogicalMin >>= shift_size;
|
||
|
value_caps.LogicalMax &= mask;
|
||
|
}
|
||
|
|
||
|
// check if this is a range cap
|
||
|
if (value_caps.IsRange) {
|
||
|
|
||
|
// add a cap for each value for range caps
|
||
|
auto usage_min = value_caps.Range.UsageMin;
|
||
|
auto usage_max = value_caps.Range.UsageMax;
|
||
|
for (auto usage = usage_min; usage <= usage_max; usage++) {
|
||
|
|
||
|
// add to list
|
||
|
value_caps.NotRange.Usage = usage;
|
||
|
value_output_caps_list.push_back(value_caps);
|
||
|
value_output_states.push_back(0.f);
|
||
|
|
||
|
// check for custom name
|
||
|
wchar_t custom_name[256] {};
|
||
|
bool custom_name_set = false;
|
||
|
if (hid_handle != INVALID_HANDLE_VALUE) {
|
||
|
|
||
|
// get string index
|
||
|
ULONG string_index = 0;
|
||
|
if (value_caps.IsStringRange && value_caps.Range.StringMin != 0) {
|
||
|
string_index = value_caps.Range.StringMin
|
||
|
+ static_cast<ULONG>(value_output_caps_list.size()) - 1;
|
||
|
} else if (!value_caps.IsStringRange && value_caps.NotRange.StringIndex != 0) {
|
||
|
string_index = value_caps.NotRange.StringIndex;
|
||
|
}
|
||
|
|
||
|
// lookup string
|
||
|
if (string_index > 0 && HidD_GetIndexedString(
|
||
|
hid_handle,
|
||
|
string_index,
|
||
|
reinterpret_cast<void *>(custom_name),
|
||
|
sizeof(custom_name))) {
|
||
|
custom_name_set = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if custom name is set
|
||
|
if (custom_name_set) {
|
||
|
|
||
|
// use custom name
|
||
|
value_output_caps_names.push_back(ws2s(std::wstring(custom_name)));
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// lookup generic name
|
||
|
const char *name = usb_hid_get_usage_text(value_caps.UsagePage, usage);
|
||
|
value_output_caps_names.emplace_back(name ? name : "Value Output");
|
||
|
free((void *) name);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
// add to list
|
||
|
value_output_caps_list.emplace_back(value_caps);
|
||
|
value_output_states.push_back(0.f);
|
||
|
|
||
|
// check for custom name
|
||
|
wchar_t custom_name[256] {};
|
||
|
bool custom_name_set = false;
|
||
|
if (hid_handle != INVALID_HANDLE_VALUE) {
|
||
|
|
||
|
// get string index
|
||
|
ULONG string_index = 0;
|
||
|
if (!value_caps.IsStringRange && value_caps.NotRange.StringIndex != 0) {
|
||
|
string_index = value_caps.NotRange.StringIndex;
|
||
|
}
|
||
|
|
||
|
// lookup string
|
||
|
if (string_index > 0 && HidD_GetIndexedString(
|
||
|
hid_handle,
|
||
|
string_index,
|
||
|
reinterpret_cast<void *>(custom_name),
|
||
|
sizeof(custom_name))) {
|
||
|
custom_name_set = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if custom name is set
|
||
|
if (custom_name_set) {
|
||
|
|
||
|
// use custom name
|
||
|
value_output_caps_names.push_back(ws2s(std::wstring(custom_name)));
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// lookup generic name
|
||
|
const char *name = usb_hid_get_usage_text(value_caps.UsagePage, value_caps.NotRange.Usage);
|
||
|
value_output_caps_names.emplace_back(name ? name : "Value Output");
|
||
|
free((void *) name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// generate HID info
|
||
|
new_device.hidInfo = new DeviceHIDInfo();
|
||
|
new_device.hidInfo->handle = hid_handle;
|
||
|
new_device.hidInfo->caps = caps;
|
||
|
new_device.hidInfo->attributes = hid_attributes;
|
||
|
new_device.hidInfo->driver = hid_driver;
|
||
|
new_device.hidInfo->usage_name = std::move(usage_name);
|
||
|
new_device.hidInfo->preparsed_data = std::move(preparsed_data);
|
||
|
new_device.hidInfo->preparsed_size = preparsed_size;
|
||
|
new_device.hidInfo->button_caps_list = std::move(button_caps_list);
|
||
|
new_device.hidInfo->button_caps_names = std::move(button_caps_names);
|
||
|
new_device.hidInfo->button_output_caps_list = std::move(button_output_caps_list);
|
||
|
new_device.hidInfo->button_output_caps_names = std::move(button_output_caps_names);
|
||
|
new_device.hidInfo->value_caps_list = std::move(value_caps_list);
|
||
|
new_device.hidInfo->value_caps_names = std::move(value_caps_names);
|
||
|
new_device.hidInfo->value_output_caps_list = std::move(value_output_caps_list);
|
||
|
new_device.hidInfo->value_output_caps_names = std::move(value_output_caps_names);
|
||
|
new_device.hidInfo->button_states = std::move(button_states);
|
||
|
new_device.hidInfo->button_up = std::move(button_up);
|
||
|
new_device.hidInfo->button_down = std::move(button_down);
|
||
|
new_device.hidInfo->button_output_states = std::move(button_output_states);
|
||
|
new_device.hidInfo->value_states = std::move(value_states);
|
||
|
new_device.hidInfo->value_states_raw = std::move(value_states_raw);
|
||
|
new_device.hidInfo->value_output_states = std::move(value_output_states);
|
||
|
new_device.hidInfo->bind_value_states = std::move(bind_value_states);
|
||
|
|
||
|
// check for touch screen
|
||
|
if (rawinput::touch::is_touchscreen(&new_device)) {
|
||
|
rawinput::touch::enable(&new_device);
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// overwrite device with the same handle
|
||
|
for (auto &prev_device : this->devices) {
|
||
|
if (prev_device.name == new_device.name) {
|
||
|
log_info("rawinput", "overwriting existing device: {} / {}", new_device.desc, new_device.name);
|
||
|
|
||
|
// carry over old device ID
|
||
|
new_device.id = prev_device.id;
|
||
|
|
||
|
// destruct and replace
|
||
|
this->devices_destruct(&prev_device);
|
||
|
prev_device = new_device;
|
||
|
|
||
|
// notify change
|
||
|
for (auto &cb : this->callback_change) {
|
||
|
cb.f(cb.data, &prev_device);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add device to list
|
||
|
auto &added_device = this->devices.emplace_back(new_device);
|
||
|
if (log) {
|
||
|
log_info("rawinput", "added device: {} / {}", added_device.desc, added_device.name);
|
||
|
}
|
||
|
|
||
|
// notify add
|
||
|
for (auto &cb : this->callback_add) {
|
||
|
cb.f(cb.data, &added_device);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_scan_midi() {
|
||
|
|
||
|
// add midi devices
|
||
|
auto midi_device_count = midiInGetNumDevs();
|
||
|
for (size_t midi_device_id = 0; midi_device_id < midi_device_count; midi_device_id++) {
|
||
|
|
||
|
// get dev caps
|
||
|
MIDIINCAPS midi_device_caps{};
|
||
|
if (midiInGetDevCaps(midi_device_id, &midi_device_caps, sizeof(MIDIINCAPS)) != MMSYSERR_NOERROR) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// open device
|
||
|
HMIDIIN midi_device_handle;
|
||
|
if (midiInOpen(&midi_device_handle,
|
||
|
(UINT) midi_device_id,
|
||
|
(DWORD_PTR) &input_midi_proc,
|
||
|
(DWORD_PTR) this,
|
||
|
CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// start input
|
||
|
if (midiInStart(midi_device_handle) != MMSYSERR_NOERROR) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// device info
|
||
|
DeviceInfo midi_device_info {};
|
||
|
|
||
|
// device midi info
|
||
|
auto midi_device_midi_info = new DeviceMIDIInfo();
|
||
|
midi_device_midi_info->states = std::vector<bool>(16 * 128);
|
||
|
midi_device_midi_info->states_events = std::vector<uint8_t>(16 * 128);
|
||
|
midi_device_midi_info->bind_states = std::vector<bool>(16 * 128);
|
||
|
midi_device_midi_info->velocity = std::vector<uint8_t>(16 * 128);
|
||
|
midi_device_midi_info->freeze = false;
|
||
|
midi_device_midi_info->controls_precision = std::vector<uint16_t>(16 * 32);
|
||
|
midi_device_midi_info->controls_precision_bind = std::vector<uint16_t>(16 * 32);
|
||
|
midi_device_midi_info->controls_precision_msb = std::vector<bool>(16 * 32);
|
||
|
midi_device_midi_info->controls_precision_lsb = std::vector<bool>(16 * 32);
|
||
|
midi_device_midi_info->controls_precision_set = std::vector<bool>(16 * 32);
|
||
|
midi_device_midi_info->controls_single = std::vector<uint8_t>(16 * 44);
|
||
|
midi_device_midi_info->controls_single_bind = std::vector<uint8_t>(16 * 44);
|
||
|
midi_device_midi_info->controls_single_set = std::vector<bool>(16 * 44);
|
||
|
midi_device_midi_info->controls_onoff = std::vector<bool>(16 * 6);
|
||
|
midi_device_midi_info->controls_onoff_bind = std::vector<bool>(16 * 6);
|
||
|
midi_device_midi_info->controls_onoff_set = std::vector<bool>(16 * 6);
|
||
|
midi_device_midi_info->pitch_bend = 0x2000;
|
||
|
|
||
|
// build identifier
|
||
|
std::ostringstream midi_identifier;
|
||
|
midi_identifier << ";" << "MIDI";
|
||
|
midi_identifier << ";" << midi_device_id;
|
||
|
midi_identifier << ";" << midi_device_caps.szPname;
|
||
|
midi_identifier << ";" << midi_device_caps.wMid;
|
||
|
midi_identifier << ";" << midi_device_caps.wPid;
|
||
|
|
||
|
// build device
|
||
|
Device midi_device {};
|
||
|
midi_device.id = devices.size() + 1;
|
||
|
midi_device.type = MIDI;
|
||
|
midi_device.handle = midi_device_handle;
|
||
|
midi_device.name = midi_identifier.str();
|
||
|
midi_device.desc = to_string(midi_device_caps.szPname);
|
||
|
midi_device.info = midi_device_info;
|
||
|
midi_device.mutex = new std::mutex();
|
||
|
midi_device.mutex_out = new std::mutex();
|
||
|
midi_device.midiInfo = midi_device_midi_info;
|
||
|
|
||
|
// check for duplicate handle
|
||
|
for (auto &device : this->devices) {
|
||
|
if (device.name == midi_identifier.str()) {
|
||
|
|
||
|
// carry over ID
|
||
|
midi_device.id = device.id;
|
||
|
|
||
|
// destruct and replace
|
||
|
this->devices_destruct(&device);
|
||
|
device = midi_device;
|
||
|
|
||
|
// notify change
|
||
|
for (auto &cb : this->callback_change) {
|
||
|
cb.f(cb.data, &device);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add device to list
|
||
|
auto &device = this->devices.emplace_back(midi_device);
|
||
|
|
||
|
// notify add
|
||
|
for (auto &cb : this->callback_add) {
|
||
|
cb.f(cb.data, &device);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_scan_piuio() {
|
||
|
|
||
|
// add device to vector first so pointer is valid
|
||
|
auto *new_piuio_device = new Device();
|
||
|
new_piuio_device->type = PIUIO_DEVICE;
|
||
|
new_piuio_device->name = "piuio";
|
||
|
new_piuio_device->desc = "PIUIO";
|
||
|
new_piuio_device->piuioDev = nullptr;
|
||
|
new_piuio_device->mutex = new std::mutex();
|
||
|
new_piuio_device->mutex_out = new std::mutex();
|
||
|
|
||
|
// try to initialize
|
||
|
auto &device = this->devices.emplace_back(*new_piuio_device);
|
||
|
auto piuioDev = new PIUIO(&device);
|
||
|
if (piuioDev->Init()) {
|
||
|
|
||
|
// successful initialization
|
||
|
device.piuioDev = piuioDev;
|
||
|
|
||
|
// notify add
|
||
|
for (auto &cb : this->callback_add) {
|
||
|
cb.f(cb.data, &device);
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
// remove device since connection failed
|
||
|
this->devices.pop_back();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_scan_smxstage() {
|
||
|
auto *new_smxstage_device = new Device();
|
||
|
new_smxstage_device->type = SMX_STAGE;
|
||
|
new_smxstage_device->name = "smxstage";
|
||
|
new_smxstage_device->desc = "SMX Stage";
|
||
|
new_smxstage_device->smxstageInfo = nullptr;
|
||
|
new_smxstage_device->mutex = new std::mutex();
|
||
|
new_smxstage_device->mutex_out = new std::mutex();
|
||
|
|
||
|
auto &device = this->devices.emplace_back(*new_smxstage_device);
|
||
|
auto smxstageInfo = new SmxStageDevice();
|
||
|
if (smxstageInfo->Initialize()) {
|
||
|
device.smxstageInfo = smxstageInfo;
|
||
|
|
||
|
// notify add
|
||
|
for (auto &cb : this->callback_add) {
|
||
|
cb.f(cb.data, &device);
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
// remove device since connection failed
|
||
|
this->devices.pop_back();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::flush_start() {
|
||
|
|
||
|
// start flush thread
|
||
|
if (this->flush_thread == nullptr) {
|
||
|
this->flush_thread_running = true;
|
||
|
this->flush_thread = new std::thread([this] {
|
||
|
while (this->flush_thread_running) {
|
||
|
|
||
|
/*
|
||
|
* Write output report all ~500ms so DAO IIDX boards (and probably more) don't go back
|
||
|
* to button based lighting.
|
||
|
*/
|
||
|
this->devices_flush_output(false);
|
||
|
Sleep(495);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::flush_stop() {
|
||
|
|
||
|
// set stop flag
|
||
|
this->flush_thread_running = false;
|
||
|
|
||
|
// check if thread is set
|
||
|
if (this->flush_thread) {
|
||
|
|
||
|
// join and kill
|
||
|
this->flush_thread->join();
|
||
|
delete this->flush_thread;
|
||
|
|
||
|
// unset thread
|
||
|
this->flush_thread = nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::output_start() {
|
||
|
|
||
|
// start thread if required
|
||
|
if (!output_thread) {
|
||
|
output_thread_running = true;
|
||
|
output_thread = new std::thread([this] {
|
||
|
std::unique_lock<std::mutex> lock(output_thread_m);
|
||
|
while (output_thread_running) {
|
||
|
|
||
|
// wait for CV
|
||
|
output_thread_cv.wait(lock, [this] {
|
||
|
return output_thread_ready;
|
||
|
});
|
||
|
|
||
|
// check for exit
|
||
|
if (!output_thread_running) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// iterate all devices
|
||
|
do {
|
||
|
output_thread_ready = false;
|
||
|
for (auto &device : this->devices) {
|
||
|
|
||
|
// write output
|
||
|
device_write_output(&device, true);
|
||
|
}
|
||
|
} while (output_thread_ready);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::output_stop() {
|
||
|
|
||
|
// stop output thread
|
||
|
this->output_thread_running = false;
|
||
|
if (this->output_thread) {
|
||
|
this->output_thread_m.lock();
|
||
|
this->output_thread_ready = true;
|
||
|
this->output_thread_m.unlock();
|
||
|
this->output_thread_cv.notify_all();
|
||
|
this->output_thread->join();
|
||
|
delete this->output_thread;
|
||
|
this->output_thread = nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::string rawinput::RawInputManager::rawinput_get_device_name(HANDLE hDevice) {
|
||
|
|
||
|
// get device name length
|
||
|
UINT device_name_len = 0;
|
||
|
if (GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, nullptr, &device_name_len) == (UINT) -1) {
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
// allocate buffer
|
||
|
auto device_name = std::make_unique<char[]>(device_name_len);
|
||
|
|
||
|
// get device name (but it is actually the path)
|
||
|
if (GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, device_name.get(), &device_name_len) == (UINT) -1) {
|
||
|
return "";
|
||
|
}
|
||
|
if (device_name_len < 4) {
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
// the infamous XP fix
|
||
|
// see http://stackoverflow.com/questions/10798798
|
||
|
device_name[1] = '\\'; //
|
||
|
|
||
|
// build string
|
||
|
return std::string(device_name.get());
|
||
|
}
|
||
|
|
||
|
std::string rawinput::RawInputManager::rawinput_get_device_description(const rawinput::DeviceInfo &info,
|
||
|
const std::string &device_name) {
|
||
|
|
||
|
// yes this whole motherfucker is just for the device name - gotta <3 microsoft
|
||
|
std::string device_description;
|
||
|
HDEVINFO devinfo = SetupDiGetClassDevs(&info.guid, nullptr, nullptr, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
|
||
|
SP_DEVINFO_DATA devinfo_data{};
|
||
|
devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
||
|
for (DWORD i1 = 0; SetupDiEnumDeviceInfo(devinfo, i1, &devinfo_data); i1++) {
|
||
|
SP_DEVICE_INTERFACE_DATA i_data{};
|
||
|
i_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||
|
for (DWORD i2 = 0; SetupDiEnumDeviceInterfaces(devinfo, &devinfo_data, &info.guid, i2, &i_data); i2++) {
|
||
|
|
||
|
// get device path
|
||
|
DWORD detail_data_size = 0;
|
||
|
if (SetupDiGetDeviceInterfaceDetail(devinfo, &i_data, nullptr, 0, &detail_data_size, nullptr)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// allocate buffer
|
||
|
std::unique_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA> detail_data(
|
||
|
reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA *>(new uint8_t[detail_data_size])
|
||
|
);
|
||
|
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||
|
|
||
|
if (!SetupDiGetDeviceInterfaceDetail(devinfo, &i_data, detail_data.get(), detail_data_size,
|
||
|
nullptr, nullptr))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
std::string device_path(detail_data->DevicePath);
|
||
|
|
||
|
// the XP fix again
|
||
|
if (device_path.length() > 1) {
|
||
|
device_path[1] = '\\';
|
||
|
}
|
||
|
|
||
|
// check if this is our device (must be case insensitive)
|
||
|
if (_stricmp(device_path.c_str(), device_name.c_str()) == 0) {
|
||
|
|
||
|
// get property
|
||
|
DWORD desc_size = 0;
|
||
|
if (SetupDiGetDeviceRegistryProperty(
|
||
|
devinfo, &devinfo_data, SPDRP_DEVICEDESC, nullptr, nullptr, 0, &desc_size))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// allocate buffer
|
||
|
auto desc_data = std::make_unique<BYTE[]>(desc_size);
|
||
|
|
||
|
if (!SetupDiGetDeviceRegistryProperty(
|
||
|
devinfo, &devinfo_data, SPDRP_DEVICEDESC, nullptr, desc_data.get(), desc_size, nullptr))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// kthxbye
|
||
|
device_description = std::string(reinterpret_cast<char *>(desc_data.get()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// kill it with fire
|
||
|
SetupDiDestroyDeviceInfoList(devinfo);
|
||
|
|
||
|
// some descriptions are empty - especially using WINE
|
||
|
if (device_description.empty()) {
|
||
|
device_description = device_name;
|
||
|
}
|
||
|
|
||
|
// alias
|
||
|
if (device_description == R"(\\?\WINE_MOUSE)") {
|
||
|
device_description = "WINE Mouse";
|
||
|
} else if (device_description == R"(\\?\WINE_KEYBOARD)") {
|
||
|
device_description = "WINE Keyboard";
|
||
|
}
|
||
|
|
||
|
// return result
|
||
|
return device_description;
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::sextet_register(const std::string &port_name, const std::string &alias,
|
||
|
bool warn) {
|
||
|
|
||
|
// check for any sextet-stream devices
|
||
|
Device device {};
|
||
|
device.type = SEXTET_OUTPUT;
|
||
|
device.name = "sextet_" + port_name;
|
||
|
device.desc = alias + " (" + port_name + ")";
|
||
|
device.sextetInfo = new rawinput::SextetDevice(R"(\\.\)" + port_name);
|
||
|
device.mutex = new std::mutex();
|
||
|
device.mutex_out = new std::mutex();
|
||
|
|
||
|
// try to connect
|
||
|
if (device.sextetInfo->connect()) {
|
||
|
|
||
|
// successful connection
|
||
|
this->devices.emplace_back(device);
|
||
|
|
||
|
// notify add
|
||
|
for (auto &cb : this->callback_add) {
|
||
|
cb.f(cb.data, &this->devices.back());
|
||
|
}
|
||
|
} else if (warn) {
|
||
|
log_warning("rawinput", "unable to connect to {} on {}", alias, port_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_remove(const std::string &name) {
|
||
|
|
||
|
// iterate devices
|
||
|
for (auto &device : this->devices) {
|
||
|
|
||
|
// check if name matches
|
||
|
if (device.name == name) {
|
||
|
|
||
|
// remove device
|
||
|
this->devices_destruct(&device);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_register() {
|
||
|
|
||
|
// check input window
|
||
|
if (!this->input_hwnd) {
|
||
|
log_warning("rawinput", "trying to register devices without input window");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// register keyboard
|
||
|
RAWINPUTDEVICE keyboard_device{};
|
||
|
if (rawinput::NOLEGACY) {
|
||
|
|
||
|
// this prevents win/media/special key events to get sent to the game window
|
||
|
keyboard_device.dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK;
|
||
|
|
||
|
} else {
|
||
|
keyboard_device.dwFlags = RIDEV_INPUTSINK;
|
||
|
}
|
||
|
keyboard_device.usUsagePage = 1;
|
||
|
keyboard_device.usUsage = 0x06;
|
||
|
keyboard_device.hwndTarget = this->input_hwnd;
|
||
|
if (!RegisterRawInputDevices(&keyboard_device, 1, sizeof(keyboard_device))) {
|
||
|
log_warning("rawinput", "failed to register keyboard events: {}", GetLastError());
|
||
|
}
|
||
|
|
||
|
// register keypad
|
||
|
RAWINPUTDEVICE keypad_device{};
|
||
|
keypad_device.dwFlags = RIDEV_INPUTSINK;
|
||
|
keypad_device.usUsagePage = 1;
|
||
|
keypad_device.usUsage = 0x07;
|
||
|
keypad_device.hwndTarget = this->input_hwnd;
|
||
|
if (!RegisterRawInputDevices(&keypad_device, 1, sizeof(keypad_device))) {
|
||
|
log_warning("rawinput", "failed to register keypad events: {}", GetLastError());
|
||
|
}
|
||
|
|
||
|
// register mouse
|
||
|
RAWINPUTDEVICE mouse_device{};
|
||
|
mouse_device.dwFlags = RIDEV_INPUTSINK;
|
||
|
mouse_device.usUsagePage = 1;
|
||
|
mouse_device.usUsage = 0x02;
|
||
|
mouse_device.hwndTarget = this->input_hwnd;
|
||
|
if (!RegisterRawInputDevices(&mouse_device, 1, sizeof(mouse_device))) {
|
||
|
log_warning("rawinput", "failed to register mouse events: {}", GetLastError());
|
||
|
}
|
||
|
|
||
|
// register joystick
|
||
|
RAWINPUTDEVICE joystick_device{};
|
||
|
joystick_device.dwFlags = RIDEV_INPUTSINK;
|
||
|
joystick_device.usUsagePage = 1;
|
||
|
joystick_device.usUsage = 0x04;
|
||
|
joystick_device.hwndTarget = this->input_hwnd;
|
||
|
if (!RegisterRawInputDevices(&joystick_device, 1, sizeof(joystick_device))) {
|
||
|
log_warning("rawinput", "failed to register joystick events: {}", GetLastError());
|
||
|
}
|
||
|
|
||
|
// register gamepad
|
||
|
RAWINPUTDEVICE gamepad_device{};
|
||
|
gamepad_device.dwFlags = RIDEV_INPUTSINK;
|
||
|
gamepad_device.usUsagePage = 1;
|
||
|
gamepad_device.usUsage = 0x05;
|
||
|
gamepad_device.hwndTarget = this->input_hwnd;
|
||
|
if (!RegisterRawInputDevices(&gamepad_device, 1, sizeof(gamepad_device))) {
|
||
|
log_warning("rawinput", "failed to register gamepad events: {}", GetLastError());
|
||
|
}
|
||
|
|
||
|
// register digitizer
|
||
|
RAWINPUTDEVICE digitizer_device{};
|
||
|
digitizer_device.dwFlags = RIDEV_PAGEONLY | RIDEV_INPUTSINK;
|
||
|
digitizer_device.usUsagePage = 0x0D;
|
||
|
digitizer_device.usUsage = 0x00;
|
||
|
digitizer_device.hwndTarget = this->input_hwnd;
|
||
|
if (!RegisterRawInputDevices(&digitizer_device, 1, sizeof(digitizer_device))) {
|
||
|
log_warning("rawinput", "failed to register digitizer events: {}", GetLastError());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_unregister() {
|
||
|
|
||
|
// unregister keyboard
|
||
|
RAWINPUTDEVICE keyboard_device {};
|
||
|
if (rawinput::NOLEGACY) {
|
||
|
keyboard_device.dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK | RIDEV_REMOVE;
|
||
|
} else {
|
||
|
keyboard_device.dwFlags = RIDEV_INPUTSINK | RIDEV_REMOVE;
|
||
|
}
|
||
|
keyboard_device.usUsagePage = 1;
|
||
|
keyboard_device.usUsage = 0x06;
|
||
|
keyboard_device.hwndTarget = this->input_hwnd;
|
||
|
RegisterRawInputDevices(&keyboard_device, 1, sizeof(keyboard_device));
|
||
|
|
||
|
// unregister keypad
|
||
|
RAWINPUTDEVICE keypad_device {};
|
||
|
keypad_device.dwFlags = RIDEV_INPUTSINK | RIDEV_REMOVE;
|
||
|
keypad_device.usUsagePage = 1;
|
||
|
keypad_device.usUsage = 0x07;
|
||
|
keypad_device.hwndTarget = this->input_hwnd;
|
||
|
RegisterRawInputDevices(&keypad_device, 1, sizeof(keypad_device));
|
||
|
|
||
|
// unregister mouse
|
||
|
RAWINPUTDEVICE mouse_device {};
|
||
|
mouse_device.dwFlags = RIDEV_INPUTSINK | RIDEV_REMOVE;
|
||
|
mouse_device.usUsagePage = 1;
|
||
|
mouse_device.usUsage = 0x02;
|
||
|
mouse_device.hwndTarget = this->input_hwnd;
|
||
|
RegisterRawInputDevices(&mouse_device, 1, sizeof(mouse_device));
|
||
|
|
||
|
// unregister joystick
|
||
|
RAWINPUTDEVICE joystick_device {};
|
||
|
joystick_device.dwFlags = RIDEV_INPUTSINK | RIDEV_REMOVE;
|
||
|
joystick_device.usUsagePage = 1;
|
||
|
joystick_device.usUsage = 0x04;
|
||
|
joystick_device.hwndTarget = this->input_hwnd;
|
||
|
RegisterRawInputDevices(&joystick_device, 1, sizeof(joystick_device));
|
||
|
|
||
|
// unregister gamepad
|
||
|
RAWINPUTDEVICE gamepad_device {};
|
||
|
gamepad_device.dwFlags = RIDEV_INPUTSINK | RIDEV_REMOVE;
|
||
|
gamepad_device.usUsagePage = 1;
|
||
|
gamepad_device.usUsage = 0x06;
|
||
|
gamepad_device.hwndTarget = this->input_hwnd;
|
||
|
RegisterRawInputDevices(&gamepad_device, 1, sizeof(gamepad_device));
|
||
|
|
||
|
// unregister digitizer
|
||
|
RAWINPUTDEVICE digitizer_device {};
|
||
|
digitizer_device.dwFlags = RIDEV_PAGEONLY | RIDEV_INPUTSINK | RIDEV_REMOVE;
|
||
|
digitizer_device.usUsagePage = 0x0D;
|
||
|
digitizer_device.usUsage = 0x00;
|
||
|
digitizer_device.hwndTarget = this->input_hwnd;
|
||
|
RegisterRawInputDevices(&digitizer_device, 1, sizeof(digitizer_device));
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_destruct() {
|
||
|
|
||
|
// check if there's something to destruct
|
||
|
if (this->devices.empty()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// dispose devices
|
||
|
log_info("rawinput", "disposing devices");
|
||
|
for (auto &device : this->devices) {
|
||
|
this->devices_destruct(&device, false);
|
||
|
delete device.mutex;
|
||
|
}
|
||
|
|
||
|
// empty array
|
||
|
this->devices.clear();
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_destruct(Device *device, bool log) {
|
||
|
|
||
|
// check if destroyed
|
||
|
if (device->type == DESTROYED) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// optionally log
|
||
|
if (log) {
|
||
|
log_info("rawinput", "destroying device: {} / {}", device->desc, device->name);
|
||
|
}
|
||
|
|
||
|
// mark as destroyed
|
||
|
auto device_type = device->type;
|
||
|
device->type = DESTROYED;
|
||
|
|
||
|
// notify change
|
||
|
for (auto &cb : this->callback_change) {
|
||
|
cb.f(cb.data, device);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* lock device
|
||
|
* note: this is an exception to only locking devices when we acquire them from the list
|
||
|
* callbacks could want to lock the mutex as well and it isn't recursive
|
||
|
* this also means the device must be unlocked before calling this function
|
||
|
*/
|
||
|
std::lock_guard<std::mutex> lock(*device->mutex);
|
||
|
|
||
|
// close device handles
|
||
|
switch (device_type) {
|
||
|
case HID:
|
||
|
if (device->hidInfo->handle != INVALID_HANDLE_VALUE) {
|
||
|
CloseHandle(device->hidInfo->handle);
|
||
|
device->hidInfo->handle = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
break;
|
||
|
case MIDI:
|
||
|
midiInReset((HMIDIIN) device->handle);
|
||
|
midiInClose((HMIDIIN) device->handle);
|
||
|
break;
|
||
|
case SEXTET_OUTPUT:
|
||
|
device->sextetInfo->disconnect();
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// clean up generic stuff
|
||
|
delete device->mouseInfo;
|
||
|
device->mouseInfo = nullptr;
|
||
|
delete device->keyboardInfo;
|
||
|
device->keyboardInfo = nullptr;
|
||
|
delete device->hidInfo;
|
||
|
device->hidInfo = nullptr;
|
||
|
delete device->midiInfo;
|
||
|
device->midiInfo = nullptr;
|
||
|
delete device->sextetInfo;
|
||
|
device->sextetInfo = nullptr;
|
||
|
delete device->smxstageInfo;
|
||
|
device->smxstageInfo = nullptr;
|
||
|
// TODO: check if mutex can be deleted
|
||
|
}
|
||
|
|
||
|
LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||
|
HWND hWnd, UINT msg, WPARAM wparam, LPARAM lParam) {
|
||
|
|
||
|
// message switch
|
||
|
switch (msg) {
|
||
|
case WM_CREATE: {
|
||
|
|
||
|
// save reference
|
||
|
auto create_params = reinterpret_cast<LPCREATESTRUCT>(lParam);
|
||
|
SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(create_params->lpCreateParams));
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case WM_INPUT: {
|
||
|
|
||
|
// get reference
|
||
|
auto ref = reinterpret_cast<RawInputManager *>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
|
||
|
|
||
|
// get raw input data
|
||
|
UINT data_size = 0;
|
||
|
if (GetRawInputData(
|
||
|
(HRAWINPUT) lParam,
|
||
|
RID_INPUT,
|
||
|
nullptr,
|
||
|
&data_size,
|
||
|
sizeof(RAWINPUTHEADER)) == (UINT) -1) {
|
||
|
break;
|
||
|
}
|
||
|
if (!data_size) {
|
||
|
break;
|
||
|
}
|
||
|
std::shared_ptr<RAWINPUT> data((RAWINPUT *) new char[data_size]{});
|
||
|
if (GetRawInputData(
|
||
|
(HRAWINPUT) lParam,
|
||
|
RID_INPUT,
|
||
|
data.get(),
|
||
|
&data_size,
|
||
|
sizeof(RAWINPUTHEADER)) != data_size) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// find device
|
||
|
HANDLE device_handle = data->header.hDevice;
|
||
|
for (auto &device : ref->devices_get()) {
|
||
|
|
||
|
// skip if this is the wrong device
|
||
|
if (device.handle != device_handle) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// get input time
|
||
|
double input_time = get_performance_seconds();
|
||
|
|
||
|
// lock device
|
||
|
device.mutex->lock();
|
||
|
|
||
|
// update hz
|
||
|
double diff_time = input_time - device.input_time;
|
||
|
if (diff_time > 0.0001) {
|
||
|
device.input_hz = 1.f / diff_time;
|
||
|
device.input_hz_max = MAX(device.input_hz_max, device.input_hz);
|
||
|
device.input_time = input_time;
|
||
|
}
|
||
|
|
||
|
// check type
|
||
|
switch (device.type) {
|
||
|
case DESTROYED:
|
||
|
log_warning("rawinput", "received input msg for destroyed device");
|
||
|
break;
|
||
|
case MOUSE: {
|
||
|
|
||
|
// get mouse data
|
||
|
auto data_mouse = data->data.mouse;
|
||
|
|
||
|
// save position
|
||
|
if (data_mouse.usFlags & MOUSE_MOVE_ABSOLUTE) {
|
||
|
if (device.mouseInfo->pos_x != data_mouse.lLastX) {
|
||
|
device.updated = true;
|
||
|
}
|
||
|
device.mouseInfo->pos_x = data_mouse.lLastX;
|
||
|
if (device.mouseInfo->pos_y != data_mouse.lLastY) {
|
||
|
device.updated = true;
|
||
|
}
|
||
|
device.mouseInfo->pos_y = data_mouse.lLastY;
|
||
|
} else {
|
||
|
if (data_mouse.lLastX != 0 || data_mouse.lLastY != 0) {
|
||
|
device.updated = true;
|
||
|
}
|
||
|
device.mouseInfo->pos_x += data_mouse.lLastX;
|
||
|
device.mouseInfo->pos_y += data_mouse.lLastY;
|
||
|
}
|
||
|
|
||
|
// check buttons
|
||
|
if (data_mouse.usButtonFlags) {
|
||
|
auto &key_states = device.mouseInfo->key_states;
|
||
|
auto &key_up = device.mouseInfo->key_up;
|
||
|
auto &key_down = device.mouseInfo->key_down;
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_LEFT] = true;
|
||
|
key_down[MOUSEBTN_LEFT] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_LEFT] = false;
|
||
|
key_up[MOUSEBTN_LEFT] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_RIGHT] = true;
|
||
|
key_down[MOUSEBTN_RIGHT] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_RIGHT] = false;
|
||
|
key_up[MOUSEBTN_RIGHT] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_MIDDLE] = true;
|
||
|
key_down[MOUSEBTN_MIDDLE] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_MIDDLE] = false;
|
||
|
key_up[MOUSEBTN_MIDDLE] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_1] = true;
|
||
|
key_down[MOUSEBTN_1] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_1] = false;
|
||
|
key_up[MOUSEBTN_1] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_2] = true;
|
||
|
key_down[MOUSEBTN_2] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_2] = false;
|
||
|
key_up[MOUSEBTN_2] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_3] = true;
|
||
|
key_down[MOUSEBTN_3] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_3] = false;
|
||
|
key_up[MOUSEBTN_3] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_4] = true;
|
||
|
key_down[MOUSEBTN_4] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_4] = false;
|
||
|
key_up[MOUSEBTN_4] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_5] = true;
|
||
|
key_down[MOUSEBTN_5] = input_time;
|
||
|
}
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) {
|
||
|
device.updated = true;
|
||
|
key_states[MOUSEBTN_5] = false;
|
||
|
key_up[MOUSEBTN_5] = input_time;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check wheel
|
||
|
if (data_mouse.usButtonFlags & RI_MOUSE_WHEEL) {
|
||
|
if ((short) data_mouse.usButtonData != 0) {
|
||
|
device.updated = true;
|
||
|
}
|
||
|
device.mouseInfo->pos_wheel += ((short) data_mouse.usButtonData) / WHEEL_DELTA;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case KEYBOARD: {
|
||
|
|
||
|
// get keyboard data
|
||
|
auto &data_keyboard = data->data.keyboard;
|
||
|
|
||
|
// set index based on flags
|
||
|
int index = 0;
|
||
|
if (data_keyboard.Flags & RI_KEY_E0) {
|
||
|
index += 256;
|
||
|
}
|
||
|
if (data_keyboard.Flags & RI_KEY_E1) {
|
||
|
index += 512;
|
||
|
}
|
||
|
|
||
|
// check the funny exceptions
|
||
|
USHORT vkey = data_keyboard.VKey;
|
||
|
switch (index + vkey) {
|
||
|
case 17:
|
||
|
vkey = VK_LCONTROL;
|
||
|
break;
|
||
|
case 273:
|
||
|
vkey = VK_RCONTROL;
|
||
|
break;
|
||
|
}
|
||
|
switch (data_keyboard.MakeCode) {
|
||
|
case 42:
|
||
|
vkey = VK_LSHIFT;
|
||
|
break;
|
||
|
case 54:
|
||
|
vkey = VK_RSHIFT;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// update key state
|
||
|
if (vkey < 255) {
|
||
|
bool state = (data_keyboard.Flags & RI_KEY_BREAK) == 0;
|
||
|
auto &cur_state = device.keyboardInfo->key_states[index + vkey];
|
||
|
if (!cur_state && state) {
|
||
|
cur_state = state;
|
||
|
device.updated = true;
|
||
|
device.keyboardInfo->key_down[index + vkey] = input_time;
|
||
|
} else if (cur_state && !state) {
|
||
|
cur_state = state;
|
||
|
device.updated = true;
|
||
|
device.keyboardInfo->key_up[index + vkey] = input_time;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case HID: {
|
||
|
|
||
|
// get HID data
|
||
|
auto &data_hid = data->data.hid;
|
||
|
|
||
|
// buttons
|
||
|
for (size_t cap_num = 0; cap_num < device.hidInfo->button_caps_list.size(); cap_num++) {
|
||
|
auto &button_caps = device.hidInfo->button_caps_list[cap_num];
|
||
|
auto &button_states = device.hidInfo->button_states[cap_num];
|
||
|
auto &button_down = device.hidInfo->button_down[cap_num];
|
||
|
auto &button_up = device.hidInfo->button_up[cap_num];
|
||
|
|
||
|
// get button count
|
||
|
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||
|
if (button_count <= 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// get usages
|
||
|
auto usages_length = static_cast<ULONG>(button_count);
|
||
|
std::vector<USAGE> usages(static_cast<size_t>(usages_length));
|
||
|
if (HidP_GetUsages(
|
||
|
HidP_Input,
|
||
|
button_caps.UsagePage,
|
||
|
button_caps.LinkCollection,
|
||
|
usages.data(),
|
||
|
&usages_length,
|
||
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
|
||
|
reinterpret_cast<PCHAR>(data_hid.bRawData),
|
||
|
data_hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// update buttons
|
||
|
std::vector<bool> new_states(button_count);
|
||
|
for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) {
|
||
|
USAGE usage = usages[usage_num] - button_caps.Range.UsageMin;
|
||
|
|
||
|
// guard against some buggy device sending an event for a usage below `UsageMin`
|
||
|
if (usage < button_count) {
|
||
|
new_states[usage] = true;
|
||
|
}
|
||
|
}
|
||
|
for (int button_num = 0; button_num < button_count; button_num++) {
|
||
|
if (!new_states[button_num] && button_states[button_num]) {
|
||
|
device.updated = true;
|
||
|
button_states[button_num] = new_states[button_num];
|
||
|
button_down[button_num] = input_time;
|
||
|
} else if (new_states[button_num] && !button_states[button_num]) {
|
||
|
device.updated = true;
|
||
|
button_states[button_num] = new_states[button_num];
|
||
|
button_up[button_num] = input_time;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// analogs
|
||
|
for (auto cap_num = 0; cap_num < device.hidInfo->caps.NumberInputValueCaps; cap_num++) {
|
||
|
auto &value_caps = device.hidInfo->value_caps_list[cap_num];
|
||
|
|
||
|
// get value
|
||
|
LONG value_raw = 0;
|
||
|
if (HidP_GetUsageValue(
|
||
|
HidP_Input,
|
||
|
value_caps.UsagePage,
|
||
|
value_caps.LinkCollection,
|
||
|
value_caps.Range.UsageMin,
|
||
|
reinterpret_cast<ULONG *>(&value_raw),
|
||
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
|
||
|
reinterpret_cast<CHAR *>(data_hid.bRawData),
|
||
|
data_hid.dwSizeHid) != HIDP_STATUS_SUCCESS)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// get min and max
|
||
|
LONG value_min = value_caps.LogicalMin;
|
||
|
LONG value_max = value_caps.LogicalMax;
|
||
|
|
||
|
// fix sign bits for signed values
|
||
|
if (value_caps.LogicalMin < 0 &&
|
||
|
value_caps.BitSize > 0 &&
|
||
|
value_caps.BitSize <= sizeof(value_caps.LogicalMin) * 8) {
|
||
|
auto shift_size = sizeof(value_caps.LogicalMin) * 8 - value_caps.BitSize + 1;
|
||
|
value_raw <<= shift_size;
|
||
|
value_raw >>= shift_size;
|
||
|
}
|
||
|
|
||
|
float value;
|
||
|
// 0x1 == generic desktop, 0x39 == hat switch
|
||
|
if (value_caps.UsagePage == 0x1 && value_caps.Range.UsageMin == 0x39) {
|
||
|
if (value_min <= value_raw && value_raw <= value_max) {
|
||
|
// scale to float; minimum valid value is UP, and increases in clockwise order
|
||
|
value = (float) (value_raw - value_min) / (float) (value_max - value_min);
|
||
|
} else {
|
||
|
// hat switches report an out-of-bounds value to indicate a neutral position, so it
|
||
|
// needs special handling; here, we will use a negative value to indicate neutral
|
||
|
value = -1.f;
|
||
|
}
|
||
|
} else {
|
||
|
// automatic calibration
|
||
|
if (value_raw < value_min) {
|
||
|
value_caps.LogicalMin = value_raw;
|
||
|
value_min = value_raw;
|
||
|
}
|
||
|
if (value_raw > value_max) {
|
||
|
value_caps.LogicalMax = value_raw;
|
||
|
value_max = value_raw;
|
||
|
}
|
||
|
|
||
|
// scale to float
|
||
|
value = (float) (value_raw - value_min) / (float) (value_max - value_min);
|
||
|
}
|
||
|
|
||
|
// store value
|
||
|
auto &cur_state = device.hidInfo->value_states[cap_num];
|
||
|
if (cur_state != value) {
|
||
|
device.updated = true;
|
||
|
cur_state = value;
|
||
|
}
|
||
|
|
||
|
// store raw value
|
||
|
auto &cur_raw_state = device.hidInfo->value_states_raw[cap_num];
|
||
|
if (cur_raw_state != value_raw) {
|
||
|
device.updated = true;
|
||
|
cur_raw_state = value_raw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// touch screen
|
||
|
rawinput::touch::update_input(&device);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// free device
|
||
|
device.mutex->unlock();
|
||
|
}
|
||
|
|
||
|
// call the default window handler for cleanup
|
||
|
DefWindowProc(hWnd, msg, wparam, lParam);
|
||
|
|
||
|
// return zero to indicate the event was processed
|
||
|
return 0;
|
||
|
}
|
||
|
case WM_DEVICECHANGE: {
|
||
|
|
||
|
// call hotplug manager
|
||
|
auto ref = reinterpret_cast<RawInputManager *>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
|
||
|
if (ref != nullptr && ref->hotplug != nullptr) {
|
||
|
return ref->hotplug->WndProc(hWnd, msg, wparam, lParam);
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// default
|
||
|
return DefWindowProc(hWnd, msg, wparam, lParam);
|
||
|
}
|
||
|
|
||
|
void CALLBACK rawinput::RawInputManager::input_midi_proc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance,
|
||
|
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||
|
// get instance
|
||
|
auto ri_mgr = reinterpret_cast<RawInputManager *>(dwInstance);
|
||
|
|
||
|
// handle message
|
||
|
switch (wMsg) {
|
||
|
case MIM_OPEN:
|
||
|
case MIM_CLOSE:
|
||
|
break;
|
||
|
case MIM_MOREDATA:
|
||
|
case MIM_DATA: {
|
||
|
|
||
|
// param mapping
|
||
|
auto dwMidiMessage = dwParam1;
|
||
|
//auto dwTimestamp = dwParam2;
|
||
|
|
||
|
// message unpacking
|
||
|
auto midi_status = LOBYTE(LOWORD(dwMidiMessage));
|
||
|
auto midi_status_command = (midi_status & 0xF0u) >> 4u;
|
||
|
auto midi_status_channel = (midi_status & 0x0Fu);
|
||
|
auto midi_byte1 = HIBYTE(LOWORD(dwMidiMessage));
|
||
|
auto midi_byte2 = LOBYTE(HIWORD(dwMidiMessage));
|
||
|
|
||
|
// callbacks
|
||
|
for (auto &callback : ri_mgr->callback_midi) {
|
||
|
|
||
|
// find device
|
||
|
for (auto &device : ri_mgr->devices_get()) {
|
||
|
if (device.type == MIDI && device.handle == hMidiIn) {
|
||
|
|
||
|
// call function
|
||
|
callback.f(callback.data, &device,
|
||
|
midi_status_command, midi_status_channel,
|
||
|
midi_byte1, midi_byte2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// skip unused messages types early for performance
|
||
|
bool skip = false;
|
||
|
switch (midi_status_command) {
|
||
|
case 0xA: // POLYPHONIC PRESSURE
|
||
|
case 0xC: // PROGRAM CHANGE
|
||
|
case 0xD: // CHANNEL PRESSURE
|
||
|
case 0xF: // SYSTEM EXCLUSIVE
|
||
|
skip = true;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
if (skip) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// find device
|
||
|
for (auto &device : ri_mgr->devices_get()) {
|
||
|
|
||
|
// filter non MIDI devices
|
||
|
if (device.type != MIDI) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// filter wrong handles
|
||
|
if (device.handle != hMidiIn) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// get input time
|
||
|
auto input_time = get_performance_seconds();
|
||
|
|
||
|
// lock device
|
||
|
std::lock_guard<std::mutex> lock(*device.mutex);
|
||
|
|
||
|
// update hz
|
||
|
auto diff_time = input_time - device.input_time;
|
||
|
if (diff_time > 0.0001) {
|
||
|
device.input_hz = 1.f / diff_time;
|
||
|
device.input_hz_max = MAX(device.input_hz_max, device.input_hz);
|
||
|
device.input_time = input_time;
|
||
|
}
|
||
|
|
||
|
// command logic
|
||
|
switch (midi_status_command) {
|
||
|
case 0x8: { // NOTE OFF
|
||
|
|
||
|
// param mapping
|
||
|
auto midi_note = midi_byte1 & 127u;
|
||
|
|
||
|
// get index
|
||
|
auto midi_index = midi_status_channel * 128 + midi_note;
|
||
|
if (midi_index < 16 * 128) {
|
||
|
|
||
|
// update velocity
|
||
|
device.midiInfo->velocity[midi_index] = 0;
|
||
|
|
||
|
// disable note
|
||
|
if (device.midiInfo->states_events[midi_index])
|
||
|
device.midiInfo->states[midi_index] = false;
|
||
|
|
||
|
// mark device as updated
|
||
|
device.updated = true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case 0x9: { // NOTE ON
|
||
|
|
||
|
// param mapping
|
||
|
auto midi_note = midi_byte1 & 127u;
|
||
|
auto midi_velocity = midi_byte2 & 127u;
|
||
|
|
||
|
// get index
|
||
|
auto midi_index = midi_status_channel * 128 + midi_note;
|
||
|
if (midi_index < 16 * 128) {
|
||
|
|
||
|
// update velocity
|
||
|
device.midiInfo->velocity[midi_index] = (uint8_t) midi_velocity;
|
||
|
|
||
|
// update events
|
||
|
if (midi_velocity) {
|
||
|
|
||
|
// so currently it's meant to be turned on
|
||
|
device.midiInfo->states[midi_index] = true;
|
||
|
|
||
|
// if its already on just increase it by one to turn it off
|
||
|
if (device.midiInfo->states_events[midi_index] % 2)
|
||
|
device.midiInfo->states_events[midi_index]++;
|
||
|
else
|
||
|
device.midiInfo->states_events[midi_index] += 2;
|
||
|
|
||
|
} else if (!device.midiInfo->freeze) {
|
||
|
|
||
|
// velocity 0 means turn it off
|
||
|
device.midiInfo->states[midi_index] = false;
|
||
|
}
|
||
|
|
||
|
// mark device as updated
|
||
|
device.updated = true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case 0xA: // POLYPHONIC PRESSURE
|
||
|
break; // skipped above (!)
|
||
|
case 0xB: { // CONTROL CHANGE
|
||
|
|
||
|
// param mapping
|
||
|
auto midi_control = midi_byte1 & 127;
|
||
|
auto midi_value = midi_byte2 & 127u;
|
||
|
|
||
|
// get index
|
||
|
auto channel_offset = midi_status_channel * 128;
|
||
|
auto midi_index = channel_offset + midi_control;
|
||
|
if (midi_index < 16 * 128) {
|
||
|
|
||
|
// continuous controller MSB
|
||
|
if (midi_control >= 0x00 && midi_control <= 0x1F) {
|
||
|
|
||
|
// update index
|
||
|
midi_index = midi_status_channel * 32 + midi_control;
|
||
|
device.midiInfo->controls_precision_set[midi_index] = true;
|
||
|
|
||
|
// check if MSB wasn't sent yet
|
||
|
if (!device.midiInfo->controls_precision_msb[midi_index]) {
|
||
|
device.midiInfo->controls_precision_msb[midi_index] = true;
|
||
|
|
||
|
// move LSB value to actual position
|
||
|
device.midiInfo->controls_precision[midi_index] >>= 7u;
|
||
|
}
|
||
|
|
||
|
// update MSB
|
||
|
auto tmp = device.midiInfo->controls_precision[midi_index];
|
||
|
tmp = (tmp & 127u) | midi_value << 7u;
|
||
|
if (!device.midiInfo->controls_precision_lsb[midi_index])
|
||
|
tmp = (tmp & (127u << 7u)) | midi_value;
|
||
|
if (device.midiInfo->controls_precision[midi_index] != tmp) {
|
||
|
device.midiInfo->controls_precision[midi_index] = tmp;
|
||
|
device.updated = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// continuous controller LSB
|
||
|
else if (midi_control >= 0x20 && midi_control <= 0x3F) {
|
||
|
|
||
|
// update index
|
||
|
midi_index = midi_status_channel * 32 + midi_control - 0x20;
|
||
|
device.midiInfo->controls_precision_set[midi_index] = true;
|
||
|
device.midiInfo->controls_precision_lsb[midi_index] = true;
|
||
|
|
||
|
// check for MSB flag
|
||
|
if (device.midiInfo->controls_precision_msb[midi_index]) {
|
||
|
|
||
|
// update LSB only
|
||
|
auto tmp = device.midiInfo->controls_precision[midi_index];
|
||
|
tmp &= 127u << 7u;
|
||
|
tmp |= midi_value;
|
||
|
if (device.midiInfo->controls_precision[midi_index] != tmp) {
|
||
|
device.midiInfo->controls_precision[midi_index] = tmp;
|
||
|
device.updated = true;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// cast to MSB
|
||
|
if (device.midiInfo->controls_precision[midi_index] != midi_value << 7u) {
|
||
|
device.midiInfo->controls_precision[midi_index] = midi_value << 7u | midi_value;
|
||
|
device.updated = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// on/off controls
|
||
|
else if (midi_control >= 0x40 && midi_control <= 0x45) {
|
||
|
|
||
|
// update index
|
||
|
midi_index = midi_status_channel * 6 + midi_control - 0x40;
|
||
|
device.midiInfo->controls_precision_set[midi_index] = true;
|
||
|
|
||
|
// get on/off state
|
||
|
auto onoff_state = midi_value >= 64;
|
||
|
|
||
|
// update device
|
||
|
if (device.midiInfo->controls_onoff[midi_index] != onoff_state) {
|
||
|
device.midiInfo->controls_onoff[midi_index] = onoff_state;
|
||
|
device.updated = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// single byte controllers
|
||
|
else if (midi_control >= 0x46 && midi_control <= 0x5F) {
|
||
|
|
||
|
// update index
|
||
|
midi_index = midi_status_channel * 32 + midi_control - 0x46;
|
||
|
device.midiInfo->controls_precision_set[midi_index] = true;
|
||
|
|
||
|
// update device
|
||
|
if (device.midiInfo->controls_single[midi_index] != midi_value) {
|
||
|
device.midiInfo->controls_single[midi_index] = midi_value;
|
||
|
device.updated = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// increment/decrement and parameter numbers
|
||
|
else if (midi_control >= 0x60 && midi_control <= 0x65) {
|
||
|
// skip
|
||
|
}
|
||
|
|
||
|
// undefined single-byte controllers
|
||
|
else if (midi_control >= 0x66 && midi_control <= 0x77) {
|
||
|
|
||
|
// update index
|
||
|
auto sbc_count = 0x5F - 0x46 + 1;
|
||
|
midi_index = midi_status_channel * 32 + midi_control - 0x66 + sbc_count;
|
||
|
device.midiInfo->controls_precision_set[midi_index] = true;
|
||
|
|
||
|
// update device
|
||
|
if (device.midiInfo->controls_single[midi_index] != midi_value) {
|
||
|
device.midiInfo->controls_single[midi_index] = midi_value;
|
||
|
device.updated = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// channel mode messages
|
||
|
else if (midi_control >= 0x78 && midi_control <= 0x7F) {
|
||
|
switch (midi_control) {
|
||
|
case 0x78: // all sound off
|
||
|
break;
|
||
|
case 0x79: { // reset all controllers
|
||
|
for (int i = 0; i < 32; i++)
|
||
|
device.midiInfo->controls_precision[midi_status_channel * 32 + i] = 0;
|
||
|
for (int i = 0; i < 44; i++)
|
||
|
device.midiInfo->controls_single[midi_status_channel * 44 + i] = 0;
|
||
|
for (int i = 0; i < 6; i++)
|
||
|
device.midiInfo->controls_onoff[midi_status_channel * 6 + i] = false;
|
||
|
device.updated = true;
|
||
|
break;
|
||
|
}
|
||
|
case 0x7A: // local control on/off
|
||
|
break;
|
||
|
case 0x7B: { // all notes off
|
||
|
for (int i = 0; i < 128; i++) {
|
||
|
device.midiInfo->states[channel_offset + i] = false;
|
||
|
device.midiInfo->states_events[channel_offset + i] = 0;
|
||
|
device.midiInfo->bind_states[channel_offset + i] = false;
|
||
|
device.midiInfo->velocity[channel_offset + i] = 0;
|
||
|
}
|
||
|
device.updated = true;
|
||
|
break;
|
||
|
}
|
||
|
case 0x7C: // omni mode off + all notes off
|
||
|
for (int i = 0; i < 128; i++) {
|
||
|
device.midiInfo->states[channel_offset + i] = false;
|
||
|
device.midiInfo->states_events[channel_offset + i] = 0;
|
||
|
device.midiInfo->bind_states[channel_offset + i] = false;
|
||
|
device.midiInfo->velocity[channel_offset + i] = 0;
|
||
|
}
|
||
|
device.updated = true;
|
||
|
break;
|
||
|
case 0x7D: // omni mode on + all notes off
|
||
|
for (int i = 0; i < 128; i++) {
|
||
|
device.midiInfo->states[channel_offset + i] = false;
|
||
|
device.midiInfo->states_events[channel_offset + i] = 0;
|
||
|
device.midiInfo->bind_states[channel_offset + i] = false;
|
||
|
device.midiInfo->velocity[channel_offset + i] = 0;
|
||
|
}
|
||
|
device.updated = true;
|
||
|
break;
|
||
|
case 0x7E: // mono mode on + poly off + all notes off
|
||
|
for (int i = 0; i < 128; i++) {
|
||
|
device.midiInfo->states[channel_offset + i] = false;
|
||
|
device.midiInfo->states_events[channel_offset + i] = 0;
|
||
|
device.midiInfo->bind_states[channel_offset + i] = false;
|
||
|
device.midiInfo->velocity[channel_offset + i] = 0;
|
||
|
}
|
||
|
device.updated = true;
|
||
|
break;
|
||
|
case 0x7F: // poly mode on + mono off + all notes off
|
||
|
for (int i = 0; i < 128; i++) {
|
||
|
device.midiInfo->states[channel_offset + i] = false;
|
||
|
device.midiInfo->states_events[channel_offset + i] = 0;
|
||
|
device.midiInfo->bind_states[channel_offset + i] = false;
|
||
|
device.midiInfo->velocity[channel_offset + i] = 0;
|
||
|
}
|
||
|
device.updated = true;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case 0xC: // PROGRAM CHANGE
|
||
|
break; // skipped above (!)
|
||
|
case 0xD: // CHANNEL PRESSURE
|
||
|
break; // skipped above (!)
|
||
|
case 0xE: { // PITCH BENDING
|
||
|
|
||
|
// build value
|
||
|
uint16_t value = midi_byte1 | midi_byte2 << 7u;
|
||
|
|
||
|
// update device
|
||
|
if (device.midiInfo->pitch_bend != value) {
|
||
|
device.midiInfo->pitch_bend = value;
|
||
|
device.updated = true;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case 0xF: // SYSTEM EXCLUSIVE
|
||
|
break; // skipped above (!)
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// don't iterate through the other devices
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case MIM_LONGDATA:
|
||
|
case MIM_ERROR:
|
||
|
case MIM_LONGERROR:
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::device_write_output(Device *device, bool only_updated) {
|
||
|
|
||
|
// check if output is enabled
|
||
|
if (!device->output_enabled) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// check if output is pending
|
||
|
if (only_updated && !device->output_pending) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// lock device
|
||
|
device->mutex_out->lock();
|
||
|
|
||
|
// mark device as updated
|
||
|
device->output_pending = false;
|
||
|
|
||
|
// check device type
|
||
|
switch (device->type) {
|
||
|
case HID: {
|
||
|
|
||
|
// get HID info
|
||
|
auto hid = device->hidInfo;
|
||
|
|
||
|
// check handle
|
||
|
if (hid->handle == INVALID_HANDLE_VALUE) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// check driver
|
||
|
switch (hid->driver) {
|
||
|
case HIDDriver::Default: {
|
||
|
|
||
|
// allocate report
|
||
|
CHAR *report_data = new CHAR[hid->caps.OutputReportByteLength] {};
|
||
|
|
||
|
// set buttons
|
||
|
for (size_t cap_no = 0; cap_no < hid->button_output_caps_list.size(); cap_no++) {
|
||
|
auto &button_cap = hid->button_output_caps_list[cap_no];
|
||
|
auto &button_state_list = hid->button_output_states[cap_no];
|
||
|
|
||
|
// determine which buttons to turn on
|
||
|
std::vector<USAGE> usage_list;
|
||
|
std::vector<USAGE> usage_off_list;
|
||
|
usage_list.reserve(button_state_list.size());
|
||
|
usage_off_list.reserve(button_state_list.size());
|
||
|
for (size_t state_no = 0; state_no < button_state_list.size(); state_no++) {
|
||
|
if (button_state_list[state_no]) {
|
||
|
usage_list.push_back(button_cap.Range.UsageMin + (USAGE) state_no);
|
||
|
} else {
|
||
|
usage_off_list.push_back(button_cap.Range.UsageMin + (USAGE) state_no);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set the buttons
|
||
|
auto usage_list_length = (ULONG) usage_list.size();
|
||
|
while (HidP_SetButtons(
|
||
|
HidP_Output,
|
||
|
button_cap.UsagePage,
|
||
|
button_cap.LinkCollection,
|
||
|
&usage_list[0],
|
||
|
&usage_list_length,
|
||
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||
|
report_data,
|
||
|
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||
|
|
||
|
// flush report
|
||
|
HidD_SetOutputReport(hid->handle, report_data, hid->caps.OutputReportByteLength);
|
||
|
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
||
|
}
|
||
|
|
||
|
// clear the buttons
|
||
|
auto usage_off_list_length = (ULONG) usage_off_list.size();
|
||
|
while (HidP_UnsetButtons(
|
||
|
HidP_Output,
|
||
|
button_cap.UsagePage,
|
||
|
button_cap.LinkCollection,
|
||
|
&usage_off_list[0],
|
||
|
&usage_off_list_length,
|
||
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||
|
report_data,
|
||
|
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||
|
|
||
|
// flush report
|
||
|
DWORD written_bytes = 0;
|
||
|
WriteFile(
|
||
|
hid->handle,
|
||
|
reinterpret_cast<void *>(report_data),
|
||
|
hid->caps.OutputReportByteLength,
|
||
|
&written_bytes,
|
||
|
nullptr
|
||
|
);
|
||
|
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set values
|
||
|
for (size_t cap_no = 0; cap_no < hid->value_output_caps_list.size(); cap_no++) {
|
||
|
auto &value_cap = hid->value_output_caps_list[cap_no];
|
||
|
auto &value_state = hid->value_output_states[cap_no];
|
||
|
|
||
|
// adjust output value per "brightness" setting
|
||
|
auto adjusted_value_state = value_state * HID_LIGHT_BRIGHTNESS / 100;
|
||
|
|
||
|
// build value
|
||
|
LONG usage_value = value_cap.LogicalMin +
|
||
|
lroundf((value_cap.LogicalMax - value_cap.LogicalMin) * adjusted_value_state);
|
||
|
if (usage_value > value_cap.LogicalMax) {
|
||
|
usage_value = value_cap.LogicalMax;
|
||
|
} else if (usage_value < value_cap.LogicalMin) {
|
||
|
usage_value = value_cap.LogicalMin;
|
||
|
}
|
||
|
|
||
|
// set the state
|
||
|
while (HidP_SetUsageValue(
|
||
|
HidP_Output,
|
||
|
value_cap.UsagePage,
|
||
|
value_cap.LinkCollection,
|
||
|
value_cap.NotRange.Usage,
|
||
|
static_cast<ULONG>(usage_value),
|
||
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||
|
report_data,
|
||
|
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||
|
|
||
|
// flush report
|
||
|
DWORD written_bytes = 0;
|
||
|
WriteFile(
|
||
|
hid->handle,
|
||
|
reinterpret_cast<void *>(report_data),
|
||
|
hid->caps.OutputReportByteLength,
|
||
|
&written_bytes,
|
||
|
nullptr
|
||
|
);
|
||
|
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MiniMaid Madness
|
||
|
if (hid->attributes.VendorID == 0xBEEF && hid->attributes.ProductID == 0x5730) {
|
||
|
if (hid->caps.OutputReportByteLength >= 8) {
|
||
|
/*
|
||
|
* MiniMaid HID Index Positions:
|
||
|
* 0: HID Report ID
|
||
|
* 1: EXT Values
|
||
|
* 2: Cabinet
|
||
|
* 3: Player 1
|
||
|
* 4: Player 2
|
||
|
* 5: Bass
|
||
|
* 6: Onboard LED Brightness
|
||
|
* 7: Keyboard Enable
|
||
|
* 8: Unused 'hax' variable
|
||
|
*/
|
||
|
|
||
|
// put pads in proper mode
|
||
|
// bit 4 high is pad enable.
|
||
|
report_data[3] |= 0x10u; // P1
|
||
|
report_data[4] |= 0x10u; // P2
|
||
|
|
||
|
// enable keyboard flag
|
||
|
report_data[7] |= 0x01u;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// write final report
|
||
|
DWORD written_bytes = 0;
|
||
|
WriteFile(
|
||
|
hid->handle,
|
||
|
reinterpret_cast<void *>(report_data),
|
||
|
hid->caps.OutputReportByteLength,
|
||
|
&written_bytes,
|
||
|
nullptr
|
||
|
);
|
||
|
|
||
|
// delete report
|
||
|
delete[] report_data;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case HIDDriver::PacDrive: {
|
||
|
|
||
|
// allocate report
|
||
|
uint8_t report_data[5] {};
|
||
|
|
||
|
// set leds
|
||
|
static const size_t mapping[] = {
|
||
|
8, 9, 10, 11, 12, 13, 14, 15,
|
||
|
0, 1, 2, 3, 4, 5, 6, 7
|
||
|
};
|
||
|
auto led_data = (uint16_t *) &report_data[3];
|
||
|
size_t count = 0;
|
||
|
for (auto &button_output_states : hid->button_output_states) {
|
||
|
for (auto &&button_output_state : button_output_states) {
|
||
|
if (button_output_state) {
|
||
|
*led_data |= 1u << mapping[count];
|
||
|
}
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// write report
|
||
|
DWORD written_bytes = 0;
|
||
|
WriteFile(
|
||
|
hid->handle,
|
||
|
static_cast<void *>(&report_data),
|
||
|
sizeof(report_data),
|
||
|
&written_bytes,
|
||
|
nullptr
|
||
|
);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case SEXTET_OUTPUT: {
|
||
|
device->sextetInfo->push_light_state();
|
||
|
break;
|
||
|
}
|
||
|
case SMX_STAGE: {
|
||
|
device->smxstageInfo->Update();
|
||
|
break;
|
||
|
};
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// unlock device
|
||
|
device->mutex_out->unlock();
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_flush_output(bool optimized) {
|
||
|
|
||
|
// optimized routine
|
||
|
if (optimized) {
|
||
|
|
||
|
// notify thread
|
||
|
output_thread_ready = true;
|
||
|
output_thread_cv.notify_one();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// blocking routine
|
||
|
for (auto &device : this->devices) {
|
||
|
|
||
|
// write output
|
||
|
device_write_output(&device, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::devices_print() {
|
||
|
|
||
|
bool touchscreen_found = false;
|
||
|
|
||
|
// iterate devices
|
||
|
log_info("rawinput", "printing list of detected devices");
|
||
|
log_info("rawinput", "detected device count: {}", devices.size());
|
||
|
for (auto &device : devices) {
|
||
|
bool is_touchscreen = false;
|
||
|
|
||
|
// lock it
|
||
|
device.mutex->lock();
|
||
|
|
||
|
// general information
|
||
|
log_misc("rawinput", "device name: {}", device.name);
|
||
|
log_misc("rawinput", "device desc: {}", device.desc);
|
||
|
log_misc("rawinput", "device handle: {}", device.handle);
|
||
|
|
||
|
// type specific
|
||
|
switch (device.type) {
|
||
|
case MOUSE:
|
||
|
log_misc("rawinput", "device type: MOUSE");
|
||
|
break;
|
||
|
case KEYBOARD:
|
||
|
log_misc("rawinput", "device type: KEYBOARD");
|
||
|
break;
|
||
|
case HID: {
|
||
|
log_misc("rawinput", "device type: HID");
|
||
|
log_misc("rawinput", "device preparsed size: {}", device.hidInfo->preparsed_size);
|
||
|
log_misc("rawinput", "device usage: {}", device.hidInfo->usage_name);
|
||
|
|
||
|
// check touchscreen
|
||
|
if (device.hidInfo->touch.valid) {
|
||
|
log_info("rawinput", "device is marked as touchscreen");
|
||
|
if (!touchscreen_found) {
|
||
|
touchscreen_found = true;
|
||
|
is_touchscreen = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// button caps
|
||
|
log_misc("rawinput", "device button caps count: {}",
|
||
|
device.hidInfo->button_caps_list.size());
|
||
|
int button_name_index = 0;
|
||
|
for (size_t i = 0; i < device.hidInfo->button_caps_list.size(); i++) {
|
||
|
auto &button_caps = device.hidInfo->button_caps_list[i];
|
||
|
USAGE usage_min = button_caps.Range.UsageMin;
|
||
|
USAGE usage_max = button_caps.Range.UsageMax;
|
||
|
int cap_len = usage_max - usage_min;
|
||
|
auto &name1 = device.hidInfo->button_caps_names[button_name_index];
|
||
|
auto &name2 = device.hidInfo->button_caps_names[button_name_index + cap_len];
|
||
|
button_name_index += cap_len + 1;
|
||
|
log_misc("rawinput", "device button caps detected: {} to {} ({}-{})",
|
||
|
name1, name2, usage_min, usage_max);
|
||
|
}
|
||
|
|
||
|
// button output caps
|
||
|
log_misc("rawinput", "device button output caps count: {}",
|
||
|
device.hidInfo->button_output_caps_list.size());
|
||
|
int button_output_name_index = 0;
|
||
|
for (size_t i = 0; i < device.hidInfo->button_output_caps_list.size(); i++) {
|
||
|
auto &button_caps = device.hidInfo->button_output_caps_list[i];
|
||
|
USAGE usage_min = button_caps.Range.UsageMin;
|
||
|
USAGE usage_max = button_caps.Range.UsageMax;
|
||
|
int cap_len = usage_max - usage_min;
|
||
|
auto &name1 = device.hidInfo->button_output_caps_names[button_output_name_index];
|
||
|
auto &name2 = device.hidInfo->button_output_caps_names[button_output_name_index + cap_len];
|
||
|
button_output_name_index += cap_len + 1;
|
||
|
log_misc("rawinput", "device button output caps detected: {} to {} ({}-{})",
|
||
|
name1, name2, usage_min, usage_max);
|
||
|
}
|
||
|
|
||
|
// value caps
|
||
|
if (!device.hidInfo->value_caps_list.empty()) {
|
||
|
log_misc("rawinput", "device value caps count: {}",
|
||
|
device.hidInfo->value_caps_list.size());
|
||
|
for (size_t i = 0; i < device.hidInfo->value_caps_list.size(); i++) {
|
||
|
auto &value_caps = device.hidInfo->value_caps_list[i];
|
||
|
if (device.hidInfo->value_caps_names.size() < i) {
|
||
|
log_fatal("rawinput", "value cap has no name!");
|
||
|
}
|
||
|
auto &name = device.hidInfo->value_caps_names[i];
|
||
|
LONG min = value_caps.LogicalMin;
|
||
|
LONG max = value_caps.LogicalMax;
|
||
|
log_misc("rawinput", "device value caps detected: {} ({} to {}, {}-bit)",
|
||
|
name, min, max, value_caps.BitSize);
|
||
|
|
||
|
if (name.compare("X") == 0 && is_touchscreen) {
|
||
|
TOUCHSCREEN_RANGE_X = max;
|
||
|
} else if (name.compare("Y") == 0 && is_touchscreen) {
|
||
|
TOUCHSCREEN_RANGE_Y = max;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// value output caps
|
||
|
if (!device.hidInfo->value_output_caps_list.empty()) {
|
||
|
log_misc("rawinput", "device value output caps count: {}",
|
||
|
device.hidInfo->value_output_caps_list.size());
|
||
|
for (size_t i = 0; i < device.hidInfo->value_output_caps_list.size(); i++) {
|
||
|
auto &value_caps = device.hidInfo->value_output_caps_list[i];
|
||
|
if (device.hidInfo->value_output_caps_names.size() < i) {
|
||
|
log_fatal("rawinput", "value output cap has no name!");
|
||
|
}
|
||
|
auto &name = device.hidInfo->value_output_caps_names[i];
|
||
|
LONG min = value_caps.LogicalMin;
|
||
|
LONG max = value_caps.LogicalMax;
|
||
|
log_misc("rawinput", "device value output caps detected: {} ({} to {}, {}-bit)",
|
||
|
name, min, max, value_caps.BitSize);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
case MIDI: {
|
||
|
log_misc("rawinput", "device type: MIDI");
|
||
|
break;
|
||
|
}
|
||
|
case SEXTET_OUTPUT: {
|
||
|
log_misc("rawinput", "device type: SEXTET_OUTPUT");
|
||
|
break;
|
||
|
}
|
||
|
case PIUIO_DEVICE: {
|
||
|
log_misc("rawinput", "device type: PIUIO");
|
||
|
break;
|
||
|
}
|
||
|
case SMX_STAGE: {
|
||
|
log_misc("rawinput", "device type: SMX_STAGE");
|
||
|
break;
|
||
|
};
|
||
|
case UNKNOWN:
|
||
|
default:
|
||
|
log_warning("rawinput", "device type: UNKNOWN");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// unlock device
|
||
|
device.mutex->unlock();
|
||
|
}
|
||
|
|
||
|
// mark as done
|
||
|
log_misc("rawinput", "done printing devices");
|
||
|
}
|
||
|
|
||
|
rawinput::DeviceInfo rawinput::RawInputManager::get_device_info(const std::string &device_name) {
|
||
|
DeviceInfo info {};
|
||
|
|
||
|
// check device name
|
||
|
if (device_name.size() < 16) {
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
// remove header
|
||
|
auto name = device_name.substr(4);
|
||
|
|
||
|
// split
|
||
|
std::vector<std::string> elements;
|
||
|
strsplit(name, elements, '#');
|
||
|
|
||
|
// check split
|
||
|
if (elements.size() < 4) {
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
// fill out fields
|
||
|
info.devclass = elements[0];
|
||
|
info.subclass = elements[1];
|
||
|
info.protocol = elements[2];
|
||
|
info.guid_str = elements[3];
|
||
|
|
||
|
// generate GUID
|
||
|
std::wstring guid_wstr = s2ws(info.guid_str);
|
||
|
if (IIDFromString(guid_wstr.c_str(), &info.guid) != S_OK) {
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
rawinput::Device *rawinput::RawInputManager::devices_get(const std::string &name, bool updated) {
|
||
|
|
||
|
// if the device name is empty, we do not even have to look for it
|
||
|
if (name.empty()) {
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
// check if caller wants only updated devices
|
||
|
if (updated) {
|
||
|
|
||
|
// iterate the devices
|
||
|
for (auto &device : this->devices) {
|
||
|
|
||
|
// check if the device names match
|
||
|
if (device.name == name) {
|
||
|
|
||
|
// lock the device since we are messing with updated
|
||
|
device.mutex->lock();
|
||
|
|
||
|
// was the device updated?
|
||
|
if (device.updated) {
|
||
|
|
||
|
// next call shouldn't trigger
|
||
|
device.updated = false;
|
||
|
|
||
|
// unlock the device
|
||
|
device.mutex->unlock();
|
||
|
|
||
|
// return the device
|
||
|
return &device;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// unlock the device again
|
||
|
device.mutex->unlock();
|
||
|
|
||
|
// return null since the device wasn't updated
|
||
|
return nullptr;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// just the usual "lookup by name"
|
||
|
for (auto &device : this->devices) {
|
||
|
if (device.name == name) {
|
||
|
return &device;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// device not found
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::add_callback_add(void *data, std::function<void (void *, Device *)> callback) {
|
||
|
this->callback_add.push_back(DeviceCallback {
|
||
|
.data = data,
|
||
|
.f = std::move(callback),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::remove_callback_add(void *data, const std::function<void (void *, Device *)> &callback) {
|
||
|
this->callback_add.erase(std::remove_if(
|
||
|
this->callback_add.begin(), this->callback_add.end(),
|
||
|
[data, callback](DeviceCallback const &cb) {
|
||
|
return cb.data == data && cb.f.target<void>() == callback.target<void>();
|
||
|
}), this->callback_add.end());
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::add_callback_change(void *data, std::function<void (void *, Device *)> callback) {
|
||
|
this->callback_change.push_back(DeviceCallback {
|
||
|
.data = data,
|
||
|
.f = std::move(callback),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::remove_callback_change(void * data, const std::function<void (void *, Device *)> &callback) {
|
||
|
this->callback_change.erase(std::remove_if(
|
||
|
this->callback_change.begin(), this->callback_change.end(),
|
||
|
[data, callback](DeviceCallback const &cb) {
|
||
|
return cb.data == data && cb.f.target<void>() == callback.target<void>();
|
||
|
}), this->callback_change.end());
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::add_callback_midi(void * data, std::function<void (void *, Device *,
|
||
|
uint8_t, uint8_t, uint8_t, uint8_t)> callback) {
|
||
|
this->callback_midi.push_back(MidiCallback {
|
||
|
.data = data,
|
||
|
.f = std::move(callback),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void rawinput::RawInputManager::remove_callback_midi(void * data, const std::function<void (void *, Device *,
|
||
|
uint8_t, uint8_t, uint8_t, uint8_t)> &callback) {
|
||
|
this->callback_midi.erase(std::remove_if(
|
||
|
this->callback_midi.begin(), this->callback_midi.end(),
|
||
|
[data, callback](MidiCallback const &cb) {
|
||
|
return cb.data == data && cb.f.target<void>() == callback.target<void>();
|
||
|
}), this->callback_midi.end());
|
||
|
}
|