From 83d4b692b8bdde2b56fea18937d74451b8b1b974 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Wed, 17 Jan 2024 12:39:45 +0100 Subject: [PATCH] InputCommon/WGInput: Handle add/remove events on separate thread to prevent deadlocks. In particular this is triggered when running Dolphin with the Steam overlay. --- .../ControllerInterface/WGInput/WGInput.cpp | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp index 14c1072992..2fa3fc2e77 100644 --- a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp +++ b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // For CoGetApartmentType #include @@ -25,7 +26,9 @@ #include "Common/HRWrap.h" #include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" #include "Common/StringUtil.h" +#include "Common/WorkQueueThread.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" namespace WGI = winrt::Windows::Gaming::Input; @@ -605,8 +608,21 @@ private: ControlState m_battery_level = 0; }; +enum class AddRemoveEventType +{ + AddOrReplace, + Remove, +}; + +struct AddRemoveEvent +{ + AddRemoveEventType type; + WGI::RawGameController raw_game_controller; +}; + static thread_local bool s_initialized_winrt; static winrt::event_token s_event_added, s_event_removed; +static Common::WorkQueueThread s_device_add_remove_queue; static bool COMIsInitialized() { @@ -668,6 +684,36 @@ static void RemoveDevice(const WGI::RawGameController& raw_game_controller) // (H is the lambda) #pragma warning(disable : 4265) +static void HandleAddRemoveEvent(AddRemoveEvent evt) +{ + try + { + winrt::init_apartment(); + } + catch (const winrt::hresult_error& ex) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, + "WGInput: Failed to CoInitialize for add/remove controller event: {}", + WStringToUTF8(ex.message())); + return; + } + Common::ScopeGuard coinit_guard([] { winrt::uninit_apartment(); }); + + switch (evt.type) + { + case AddRemoveEventType::AddOrReplace: + RemoveDevice(evt.raw_game_controller); + AddDevice(evt.raw_game_controller); + break; + case AddRemoveEventType::Remove: + RemoveDevice(evt.raw_game_controller); + break; + default: + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Invalid add/remove controller event: {}", + std::to_underlying(evt.type)); + } +} + void Init() { if (!COMIsInitialized()) @@ -678,18 +724,21 @@ void Init() s_initialized_winrt = true; } + s_device_add_remove_queue.Reset("WGInput Add/Remove Device Thread", HandleAddRemoveEvent); + try { // These events will be invoked from WGI-managed threadpool. s_event_added = WGI::RawGameController::RawGameControllerAdded( - [](auto&&, const WGI::RawGameController raw_game_controller) { - RemoveDevice(raw_game_controller); - AddDevice(raw_game_controller); + [](auto&&, WGI::RawGameController raw_game_controller) { + s_device_add_remove_queue.EmplaceItem( + AddRemoveEvent{AddRemoveEventType::AddOrReplace, std::move(raw_game_controller)}); }); s_event_removed = WGI::RawGameController::RawGameControllerRemoved( - [](auto&&, const WGI::RawGameController raw_game_controller) { - RemoveDevice(raw_game_controller); + [](auto&&, WGI::RawGameController raw_game_controller) { + s_device_add_remove_queue.EmplaceItem( + AddRemoveEvent{AddRemoveEventType::Remove, std::move(raw_game_controller)}); }); } catch (winrt::hresult_error) @@ -702,6 +751,8 @@ void Init() void DeInit() { + s_device_add_remove_queue.Shutdown(); + WGI::RawGameController::RawGameControllerAdded(s_event_added); WGI::RawGameController::RawGameControllerRemoved(s_event_removed);