Files
spice2x-r3d/src/spice2x/rawinput/hotplug.cpp
T
bicarusandGitHub 2dea0d27bf rawinput: fix hotplug for keyboard and mouse (#810)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Fix a long-standing issue of hotplug not working for keyboards and mice.
The code only registered for HID notifications; we also need to do it
for the keyboard GUID and mouse GUID.

## Testing
Confirmed that keyboards and mice can be hot unplugged and plugged.
2026-07-18 17:38:59 -07:00

162 lines
6.4 KiB
C++

#include <initguid.h>
#include "hotplug.h"
#include <dbt.h>
#include <devguid.h>
#include <hidclass.h>
#include <ntddkbd.h>
#include <ntddmou.h>
#include <objbase.h>
#include "misc/eamuse.h"
#include "rawinput.h"
#include "util/fileutils.h"
#include "util/logging.h"
namespace rawinput {
static bool is_rawinput_interface(const GUID &guid) {
return IsEqualGUID(guid, GUID_DEVINTERFACE_HID)
|| IsEqualGUID(guid, GUID_DEVINTERFACE_KEYBOARD)
|| IsEqualGUID(guid, GUID_DEVINTERFACE_MOUSE);
}
HotplugManager::HotplugManager(RawInputManager *ri_mgr, HWND hWnd) : ri_mgr(ri_mgr) {
auto register_notification = [hWnd](const GUID &guid, const char *name) -> HANDLE {
DEV_BROADCAST_DEVICEINTERFACE settings {
.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE),
.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE,
.dbcc_reserved = 0,
.dbcc_classguid = guid,
.dbcc_name = {0},
};
auto notification = RegisterDeviceNotification(hWnd, &settings, DEVICE_NOTIFY_WINDOW_HANDLE);
if (notification == nullptr) {
log_warning("hotplug", "failed to register {} notifications: {}", name, GetLastError());
}
return notification;
};
// register notifications
this->hotplug_hid = register_notification(GUID_DEVINTERFACE_HID, "HID");
this->hotplug_keyboard = register_notification(GUID_DEVINTERFACE_KEYBOARD, "keyboard");
this->hotplug_mouse = register_notification(GUID_DEVINTERFACE_MOUSE, "mouse");
}
HotplugManager::~HotplugManager() {
// unregister notifications
for (auto notification : {this->hotplug_hid, this->hotplug_keyboard, this->hotplug_mouse}) {
if (notification != nullptr) {
UnregisterDeviceNotification(notification);
}
}
}
LRESULT CALLBACK HotplugManager::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
// check for device change
if (msg == WM_DEVICECHANGE) {
// check type
switch (wParam) {
case DBT_DEVICEARRIVAL: {
// check arrived device type
auto hdr = (DEV_BROADCAST_HDR*) lParam;
switch (hdr->dbch_devicetype) {
case DBT_DEVTYP_DEVICEINTERFACE: {
auto dev = (const DEV_BROADCAST_DEVICEINTERFACE *) hdr;
// check if this is one of the registered RawInput interfaces
if (!is_rawinput_interface(dev->dbcc_classguid)) {
break;
}
// keyboard and mouse class events accompany the HID event, so scan MIDI only once
if (IsEqualGUID(dev->dbcc_classguid, GUID_DEVINTERFACE_HID)) {
this->ri_mgr->midi_scan_start();
}
// hotplug
std::string name(dev->dbcc_name);
this->ri_mgr->devices_scan_rawinput(name);
this->ri_mgr->devices_register();
break;
}
case DBT_DEVTYP_VOLUME: {
auto dev = (const DEV_BROADCAST_VOLUME *) hdr;
auto unitmask = dev->dbcv_unitmask;
// check drive
unsigned long bit;
while (_BitScanForward(&bit, unitmask)) {
// remove drive from unitmask so it is not scanned again
unitmask &= ~(1 << bit);
// convert bit to drive letter char
char drive = 'A' + bit;
log_info("hotplug", "detected volume arrival: {}", drive);
#ifndef SPICETOOLS_SPICECFG_STANDALONE
// auto insert cards
for (int player = 0; player <= 1; player++) {
std::string path = to_string(drive) + ":\\card" + to_string(player) + ".txt";
if (fileutils::file_exists(path)) {
uint8_t card_data[8];
if (eamuse_get_card_from_file(path, card_data, player)) {
eamuse_card_insert(player, card_data);
}
}
}
#endif
}
break;
}
}
// success
return TRUE;
}
case DBT_DEVICEREMOVECOMPLETE: {
// check arrived device type
auto hdr = (DEV_BROADCAST_HDR*) lParam;
switch (hdr->dbch_devicetype) {
case DBT_DEVTYP_DEVICEINTERFACE: {
auto dev = (DEV_BROADCAST_DEVICEINTERFACE*) hdr;
if (!is_rawinput_interface(dev->dbcc_classguid)) {
break;
}
std::string name(dev->dbcc_name);
// destruct device
log_misc("hotplug", "device interface removal: {}", name);
this->ri_mgr->devices_remove(name);
}
}
// success
return TRUE;
}
case DBT_DEVNODES_CHANGED: {
// catch-all device-tree change: MIDI and XInput have no targeted
// arrival/removal notification, so this is our only hook to detect them
// can be a little noisy as it gets called on every device
log_misc("hotplug", "device tree changed, rescanning MIDI + XInput");
this->ri_mgr->midi_scan_start();
this->ri_mgr->devices_scan_xinput();
break;
}
}
}
// default
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}