diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index adc4d0d05..9195adbe0 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -58,12 +58,6 @@ #include "moc_mainwindow.cpp" -#ifdef _WIN32 -#include "common/windows_headers.h" -#include -#include -#endif - LOG_CHANNEL(Host); static constexpr std::array, 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(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(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 MainWindow::acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, Error* error) { diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index bfea4d1e5..761bf8227 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -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 diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index a3e8ec12d..50362d79f 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -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) diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp index 7d49bd9de..ac6a3edfc 100644 --- a/src/util/input_manager.cpp +++ b/src/util/input_manager.cpp @@ -34,6 +34,11 @@ #include #include +#ifdef _WIN32 +#include "common/windows_headers.h" +#include +#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, static_cast(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(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(InputSourceType::RawInput)] || + s_state.input_sources[static_cast(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 diff --git a/src/util/util.props b/src/util/util.props index 5da4f34e9..682de29ae 100644 --- a/src/util/util.props +++ b/src/util/util.props @@ -13,7 +13,7 @@ - %(AdditionalDependencies);Dwmapi.lib;winhttp.lib + %(AdditionalDependencies);Cfgmgr32.lib;Dwmapi.lib;winhttp.lib