#include "rawinput.h" #include #include #include #include #include #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 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 button_cap_data(static_cast(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 button_caps_list; std::vector button_caps_names; std::vector> button_states; std::vector> 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(static_cast(button_count), false)); button_up.emplace_back(std::vector(static_cast(button_count), 0.0)); button_down.emplace_back(std::vector(static_cast(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 button_output_cap_data(static_cast(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 button_output_caps_list; std::vector button_output_caps_names; std::vector> 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(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(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(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(1, false)); } } // get value caps USHORT value_cap_length = caps.NumberInputValueCaps; std::vector 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 value_caps_list; std::vector value_caps_names; std::vector value_states(value_cap_length, 0.5f); std::vector value_states_raw(value_cap_length, 0); std::vector 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 value_output_cap_data(static_cast(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 value_output_caps_list; std::vector value_output_caps_names; std::vector 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(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(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(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(16 * 128); midi_device_midi_info->states_events = std::vector(16 * 128); midi_device_midi_info->bind_states = std::vector(16 * 128); midi_device_midi_info->velocity = std::vector(16 * 128); midi_device_midi_info->freeze = false; midi_device_midi_info->controls_precision = std::vector(16 * 32); midi_device_midi_info->controls_precision_bind = std::vector(16 * 32); midi_device_midi_info->controls_precision_msb = std::vector(16 * 32); midi_device_midi_info->controls_precision_lsb = std::vector(16 * 32); midi_device_midi_info->controls_precision_set = std::vector(16 * 32); midi_device_midi_info->controls_single = std::vector(16 * 44); midi_device_midi_info->controls_single_bind = std::vector(16 * 44); midi_device_midi_info->controls_single_set = std::vector(16 * 44); midi_device_midi_info->controls_onoff = std::vector(16 * 6); midi_device_midi_info->controls_onoff_bind = std::vector(16 * 6); midi_device_midi_info->controls_onoff_set = std::vector(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 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(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 detail_data( reinterpret_cast(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(desc_size); if (!SetupDiGetDeviceRegistryProperty( devinfo, &devinfo_data, SPDRP_DEVICEDESC, nullptr, desc_data.get(), desc_size, nullptr)) { continue; } // kthxbye device_description = std::string(reinterpret_cast(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 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(lParam); SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(create_params->lpCreateParams)); break; } case WM_INPUT: { // get reference auto ref = reinterpret_cast(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 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(button_count); std::vector usages(static_cast(usages_length)); if (HidP_GetUsages( HidP_Input, button_caps.UsagePage, button_caps.LinkCollection, usages.data(), &usages_length, reinterpret_cast(device.hidInfo->preparsed_data.get()), reinterpret_cast(data_hid.bRawData), data_hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { continue; } // update buttons std::vector 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(&value_raw), reinterpret_cast(device.hidInfo->preparsed_data.get()), reinterpret_cast(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(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(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 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_list; std::vector 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(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(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(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(usage_value), reinterpret_cast(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(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(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(&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 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 callback) { this->callback_add.push_back(DeviceCallback { .data = data, .f = std::move(callback), }); } void rawinput::RawInputManager::remove_callback_add(void *data, const std::function &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() == callback.target(); }), this->callback_add.end()); } void rawinput::RawInputManager::add_callback_change(void *data, std::function callback) { this->callback_change.push_back(DeviceCallback { .data = data, .f = std::move(callback), }); } void rawinput::RawInputManager::remove_callback_change(void * data, const std::function &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() == callback.target(); }), this->callback_change.end()); } void rawinput::RawInputManager::add_callback_midi(void * data, std::function callback) { this->callback_midi.push_back(MidiCallback { .data = data, .f = std::move(callback), }); } void rawinput::RawInputManager::remove_callback_midi(void * data, const std::function &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() == callback.target(); }), this->callback_midi.end()); }