InputManager: Move device notification from main window

Can use the new CM_Register_Notification() function since we don't have
to care about Windows 7.

Hopefully should stop a very rare race between window destruction and
device unplugging. Also gets the Windows-specific stuff out of
MainWindow which is nice.
This commit is contained in:
Stenzek
2025-12-17 13:31:12 +10:00
parent a72d21f7ae
commit 0af8b74773
5 changed files with 86 additions and 70 deletions

View File

@@ -58,12 +58,6 @@
#include "moc_mainwindow.cpp"
#ifdef _WIN32
#include "common/windows_headers.h"
#include <Dbt.h>
#include <VersionHelpers.h>
#endif
LOG_CHANNEL(Host);
static constexpr std::array<std::pair<Qt::ToolBarArea, const char*>, 4> s_toolbar_areas = {{
@@ -191,10 +185,6 @@ MainWindow::~MainWindow()
// we compare here, since recreate destroys the window later
if (g_main_window == this)
g_main_window = nullptr;
#ifdef _WIN32
unregisterForDeviceNotifications();
#endif
}
void MainWindow::initialize()
@@ -210,10 +200,6 @@ void MainWindow::initialize()
connectSignals();
switchToGameListView();
#ifdef _WIN32
registerForDeviceNotifications();
#endif
}
QMenu* MainWindow::createPopupMenu()
@@ -231,49 +217,6 @@ void MainWindow::onStatusMessage(const QString& message)
m_ui.statusBar->showMessage(message);
}
void MainWindow::registerForDeviceNotifications()
{
#ifdef _WIN32
// We use these notifications to detect when a controller is connected or disconnected.
DEV_BROADCAST_DEVICEINTERFACE_W filter = {
sizeof(DEV_BROADCAST_DEVICEINTERFACE_W), DBT_DEVTYP_DEVICEINTERFACE, 0u, {}, {}};
m_device_notification_handle = RegisterDeviceNotificationW(
(HANDLE)winId(), &filter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
#endif
}
void MainWindow::unregisterForDeviceNotifications()
{
#ifdef _WIN32
if (!m_device_notification_handle)
return;
UnregisterDeviceNotification(static_cast<HDEVNOTIFY>(m_device_notification_handle));
m_device_notification_handle = nullptr;
#endif
}
#ifdef _WIN32
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result)
{
static constexpr const char win_type[] = "windows_generic_MSG";
if (eventType == QByteArray(win_type, sizeof(win_type) - 1))
{
const MSG* msg = static_cast<const MSG*>(message);
if (msg->message == WM_DEVICECHANGE && msg->wParam == DBT_DEVNODES_CHANGED)
{
g_emu_thread->reloadInputDevices();
*result = 1;
return true;
}
}
return QMainWindow::nativeEvent(eventType, message, result);
}
#endif
std::optional<WindowInfo> MainWindow::acquireRenderWindow(RenderAPI render_api, bool fullscreen,
bool exclusive_fullscreen, Error* error)
{

View File

@@ -153,10 +153,6 @@ protected:
void moveEvent(QMoveEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
#ifdef _WIN32
bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;
#endif
private:
/// Initializes the window. Call once at startup.
void initialize();
@@ -205,8 +201,6 @@ private:
void destroySubWindows();
void showAutoUpdaterWindow();
void registerForDeviceNotifications();
void unregisterForDeviceNotifications();
void notifyRAIntegrationOfWindowChange();
/// Fills menu with save state info and handlers.
@@ -359,10 +353,6 @@ private:
bool m_was_disc_change_request = false;
bool m_is_closing = false;
#ifdef _WIN32
void* m_device_notification_handle = nullptr;
#endif
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
QMenu* m_raintegration_menu = nullptr;
#endif

View File

@@ -251,7 +251,7 @@ if(WIN32)
xinput_source.h
)
target_link_libraries(util PRIVATE d3d12ma)
target_link_libraries(util PRIVATE winmm.lib Dwmapi.lib winhttp.lib)
target_link_libraries(util PRIVATE Cfgmgr32.lib Dwmapi.lib winhttp.lib)
if(CMAKE_BUILD_TYPE MATCHES "Debug|Devel")
target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime)

View File

@@ -34,6 +34,11 @@
#include <variant>
#include <vector>
#ifdef _WIN32
#include "common/windows_headers.h"
#include <cfgmgr32.h>
#endif
LOG_CHANNEL(InputManager);
namespace InputManager {
@@ -162,6 +167,13 @@ static void UpdateInputSourceState(const SettingsInterface& si, std::unique_lock
static const KeyCodeData* FindKeyCodeData(u32 usb_code);
#ifdef _WIN32
static DWORD WINAPI DeviceNotificationCallback(HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action,
PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize);
static void RegisterDeviceNotificationHandle();
static void UnregisterDeviceNotificationHandle();
#endif
// ------------------------------------------------------------------------
// Tracking host mouse movement and turning into relative events
// 4 axes: pointer left/right, wheel vertical/horizontal. Last/Next/Normalized.
@@ -210,6 +222,12 @@ struct ALIGN_TO_CACHE_LINE State
// Input sources. Keyboard/mouse don't exist here.
std::array<std::unique_ptr<InputSource>, static_cast<u32>(InputSourceType::Count)> input_sources;
#ifdef _WIN32
// Device notification handle for Windows.
HCMNOTIFICATION device_notification_handle = nullptr;
std::atomic_flag device_notification_reload_pending = ATOMIC_FLAG_INIT;
#endif
ALIGN_TO_CACHE_LINE
std::array<std::array<float, static_cast<u8>(InputPointerAxis::Count)>, InputManager::MAX_POINTER_DEVICES>
host_pointer_positions;
@@ -268,6 +286,8 @@ static constexpr const std::array s_legacy_key_names = {
} // namespace InputManager
ForceFeedbackDevice::~ForceFeedbackDevice() = default;
// ------------------------------------------------------------------------
// Binding Parsing
// ------------------------------------------------------------------------
@@ -2233,6 +2253,10 @@ void InputManager::CloseSources()
s_state.input_sources[i].reset();
}
}
#ifdef _WIN32
UnregisterDeviceNotificationHandle();
#endif
}
void InputManager::PollSources()
@@ -2428,6 +2452,19 @@ void InputManager::ReloadSourcesAndBindings(const SettingsInterface& si, const S
UpdateInputSourceState(si, settings_lock, InputSourceType::DInput, &InputSource::CreateDInputSource);
UpdateInputSourceState(si, settings_lock, InputSourceType::XInput, &InputSource::CreateXInputSource);
UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource);
// Request device notifications when using raw input or dinput, as we need to manually handle device changes
if (s_state.input_sources[static_cast<u32>(InputSourceType::RawInput)] ||
s_state.input_sources[static_cast<u32>(InputSourceType::DInput)])
{
if (!s_state.device_notification_handle)
RegisterDeviceNotificationHandle();
}
else
{
if (s_state.device_notification_handle)
UnregisterDeviceNotificationHandle();
}
#endif
#ifdef ENABLE_SDL
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource);
@@ -2442,6 +2479,52 @@ void InputManager::ReloadSourcesAndBindings(const SettingsInterface& si, const S
UpdateRelativeMouseMode();
}
ForceFeedbackDevice::~ForceFeedbackDevice()
#ifdef _WIN32
DWORD InputManager::DeviceNotificationCallback(HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action,
PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize)
{
if (Action != CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL && Action != CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL)
return ERROR_SUCCESS;
// This comes through on a thread pool worker, so we need to queue the reload on the core thread.
// We tend to get a few of these in quick succession, so try to batch the reloads together.
if (!s_state.device_notification_reload_pending.test_and_set(std::memory_order_acq_rel))
{
Host::RunOnCPUThread([]() {
DEV_LOG("Reloading input devices due to device notification.");
s_state.device_notification_reload_pending.clear(std::memory_order_release);
ReloadDevices();
});
}
return ERROR_SUCCESS;
}
void InputManager::RegisterDeviceNotificationHandle()
{
DebugAssert(!s_state.device_notification_handle);
s_state.device_notification_reload_pending.clear(std::memory_order_release);
// We use these notifications to detect when a controller is connected or disconnected.
CM_NOTIFY_FILTER filter = {};
filter.cbSize = sizeof(CM_NOTIFY_FILTER);
filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
filter.Flags = CM_NOTIFY_FILTER_FLAG_ALL_INTERFACE_CLASSES;
const CONFIGRET cr = CM_Register_Notification(&filter, nullptr, &InputManager::DeviceNotificationCallback,
&s_state.device_notification_handle);
if (cr != CR_SUCCESS)
ERROR_LOG("CM_Register_Notification() failed: {}", cr);
}
void InputManager::UnregisterDeviceNotificationHandle()
{
if (!s_state.device_notification_handle)
return;
CM_Unregister_Notification(s_state.device_notification_handle);
s_state.device_notification_handle = nullptr;
}
#endif

View File

@@ -13,7 +13,7 @@
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);Dwmapi.lib;winhttp.lib</AdditionalDependencies>
<AdditionalDependencies>%(AdditionalDependencies);Cfgmgr32.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>