From c55218200a05cbad8b1cfd71acab2dbb253df687 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 25 Sep 2025 13:23:26 -0500 Subject: [PATCH 1/2] WindowsDevice: Add DeviceChangeNotification class. --- Source/Core/Common/WindowsDevice.cpp | 59 ++++++++++++++++++++++++++++ Source/Core/Common/WindowsDevice.h | 29 ++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/Source/Core/Common/WindowsDevice.cpp b/Source/Core/Common/WindowsDevice.cpp index 4bbd2137749..34a540ed7bb 100644 --- a/Source/Core/Common/WindowsDevice.cpp +++ b/Source/Core/Common/WindowsDevice.cpp @@ -90,6 +90,65 @@ NullTerminatedStringList GetDeviceInterfaceList(LPGUID iface_class_guid, } } +static __callback DWORD OnDevicesChanged(_In_ HCMNOTIFICATION notify_handle, _In_opt_ PVOID context, + _In_ CM_NOTIFY_ACTION action, + _In_reads_bytes_(event_data_size) + PCM_NOTIFY_EVENT_DATA event_data, + _In_ DWORD event_data_size) +{ + auto& callback = *static_cast(context); + switch (action) + { + case CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL: + callback(DeviceChangeNotification::EventType::Arrival); + break; + case CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL: + callback(DeviceChangeNotification::EventType::Removal); + break; + default: + break; + } + return ERROR_SUCCESS; +} + +DeviceChangeNotification::DeviceChangeNotification() = default; + +DeviceChangeNotification::~DeviceChangeNotification() +{ + Unregister(); +} + +void DeviceChangeNotification::Register(CallbackType callback) +{ + Unregister(); + m_callback = std::move(callback); + + CM_NOTIFY_FILTER notify_filter{ + .cbSize = sizeof(notify_filter), + .FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE, + .u{.DeviceInterface{.ClassGuid = GUID_DEVINTERFACE_HID}}, + }; + const CONFIGRET cfg_rv = + CM_Register_Notification(¬ify_filter, &m_callback, OnDevicesChanged, &m_notify_handle); + if (cfg_rv != CR_SUCCESS) + { + ERROR_LOG_FMT(COMMON, "CM_Register_Notification failed: {:x}", cfg_rv); + } +} + +void DeviceChangeNotification::Unregister() +{ + if (m_notify_handle == nullptr) + return; + + const CONFIGRET cfg_rv = CM_Unregister_Notification(m_notify_handle); + if (cfg_rv != CR_SUCCESS) + { + ERROR_LOG_FMT(COMMON, "CM_Unregister_Notification failed: {:x}", cfg_rv); + } + m_notify_handle = nullptr; +} + } // namespace Common #endif diff --git a/Source/Core/Common/WindowsDevice.h b/Source/Core/Common/WindowsDevice.h index 8c682ac8500..78e8234c15a 100644 --- a/Source/Core/Common/WindowsDevice.h +++ b/Source/Core/Common/WindowsDevice.h @@ -22,6 +22,8 @@ #include #include +#include "Common/Functional.h" + namespace Common { std::optional GetDevNodeStringProperty(DEVINST device, @@ -71,6 +73,33 @@ struct NullTerminatedStringList NullTerminatedStringList GetDeviceInterfaceList(LPGUID iface_class_guid, DEVINSTID device_id, ULONG flags); +class DeviceChangeNotification +{ +public: + enum class EventType : bool + { + Arrival, + Removal, + }; + using CallbackType = Common::MoveOnlyFunction; + + DeviceChangeNotification(); + ~DeviceChangeNotification(); + + // FYI: Currently hardcoded to a GUID_DEVINTERFACE_HID filter. + void Register(CallbackType callback); + void Unregister(); + + DeviceChangeNotification(const DeviceChangeNotification&) = delete; + DeviceChangeNotification(DeviceChangeNotification&&) = delete; + void operator=(const DeviceChangeNotification&) = delete; + void operator=(DeviceChangeNotification&&) = delete; + +private: + CallbackType m_callback{}; + HCMNOTIFICATION m_notify_handle{nullptr}; +}; + } // namespace Common #endif From fc4cbf9c6fa76862b153d72baddde11edb8a3958 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 22 Dec 2025 23:18:36 -0600 Subject: [PATCH 2/2] HW/WiimoteReal: Cache the enumerated Wii remote HID interface list between calls to FindWiimoteHIDDevices. --- Source/Core/Core/HW/WiimoteReal/IOWin.cpp | 41 ++++++++++++++++------- Source/Core/Core/HW/WiimoteReal/IOWin.h | 17 ++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Source/Core/Core/HW/WiimoteReal/IOWin.cpp b/Source/Core/Core/HW/WiimoteReal/IOWin.cpp index e1cf4971e9f..29a7ef2d45b 100644 --- a/Source/Core/Core/HW/WiimoteReal/IOWin.cpp +++ b/Source/Core/Core/HW/WiimoteReal/IOWin.cpp @@ -24,7 +24,6 @@ #include "Common/ScopeGuard.h" #include "Common/StringUtil.h" #include "Common/Thread.h" -#include "Common/WindowsDevice.h" #include "Core/HW/WiimoteCommon/DataReport.h" #include "Core/HW/WiimoteCommon/WiimoteConstants.h" @@ -328,7 +327,12 @@ void WiimoteScannerWindows::RemoveRememberedWiimotes() NOTICE_LOG_FMT(WIIMOTE, "Removed remembered Wiimotes: {}", forget_count); } -WiimoteScannerWindows::WiimoteScannerWindows() = default; +WiimoteScannerWindows::WiimoteScannerWindows() +{ + m_device_change_notification.Register([this](Common::DeviceChangeNotification::EventType) { + m_devices_changed.store(true, std::memory_order_release); + }); +} void WiimoteScannerWindows::Update() { @@ -576,9 +580,9 @@ int WiimoteWindows::IOWrite(const u8* buf, size_t len) return write_result; } -auto WiimoteScannerWindows::FindWiimoteHIDDevices() -> FindResults +static std::vector GetAllWiimoteHIDInterfaces() { - FindResults results; + std::vector results; // Enumerate connected HID interfaces IDs. auto class_guid = GUID_DEVINTERFACE_HID; @@ -586,14 +590,6 @@ auto WiimoteScannerWindows::FindWiimoteHIDDevices() -> FindResults for (auto* hid_iface : Common::GetDeviceInterfaceList(&class_guid, nullptr, flags)) { - // TODO: WiimoteWindows::GetId() does a redundant conversion. - const auto hid_iface_utf8 = WStringToUTF8(hid_iface); - DEBUG_LOG_FMT(WIIMOTE, "Found HID interface: {}", hid_iface_utf8); - - // Are we already using this device? - if (!IsNewWiimote(hid_iface_utf8)) - continue; - // When connected via Bluetooth, this has a proper name like "Nintendo RVL-CNT-01". const auto parent_description = GetParentDeviceDescription(hid_iface); @@ -650,6 +646,27 @@ auto WiimoteScannerWindows::FindWiimoteHIDDevices() -> FindResults } // Once here, we are confident that this is a Wii device. + results.push_back({hid_iface, is_balance_board}); + } + + return results; +} + +auto WiimoteScannerWindows::FindWiimoteHIDDevices() -> FindResults +{ + if (m_devices_changed.exchange(false, std::memory_order_acquire)) + { + m_wiimote_hid_interfaces = GetAllWiimoteHIDInterfaces(); + INFO_LOG_FMT(WIIMOTE, "Found {} HID interface(s).", m_wiimote_hid_interfaces.size()); + } + + FindResults results; + + for (auto& [hid_iface, is_balance_board] : m_wiimote_hid_interfaces) + { + // Are we already using this device? + if (!IsNewWiimote(WStringToUTF8(hid_iface))) + continue; DEBUG_LOG_FMT(WIIMOTE, "Creating WiimoteWindows"); diff --git a/Source/Core/Core/HW/WiimoteReal/IOWin.h b/Source/Core/Core/HW/WiimoteReal/IOWin.h index b8bc29a86fe..aa7b4262887 100644 --- a/Source/Core/Core/HW/WiimoteReal/IOWin.h +++ b/Source/Core/Core/HW/WiimoteReal/IOWin.h @@ -6,7 +6,12 @@ #ifdef _WIN32 #include +#include +#include + #include "Common/SocketContext.h" +#include "Common/WindowsDevice.h" + #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/USBUtils.h" @@ -50,6 +55,12 @@ private: class WiimoteScannerWindows final : public WiimoteScannerBackend { public: + struct EnumeratedWiimoteInterface + { + std::wstring hid_iface; + std::optional is_balance_board; + }; + WiimoteScannerWindows(); bool IsReady() const override; @@ -64,6 +75,12 @@ public: private: FindResults FindWiimoteHIDDevices(); + + // This vector is updated after we receive a device change notification. + std::vector m_wiimote_hid_interfaces; + std::atomic_bool m_devices_changed{true}; + + Common::DeviceChangeNotification m_device_change_notification; }; } // namespace WiimoteReal