diff --git a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp index f755a175a5..414d246069 100644 --- a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp +++ b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp @@ -4,41 +4,32 @@ #include "InputCommon/ControllerInterface/WGInput/WGInput.h" #include +#include #include +// For CoGetApartmentType +#include +#pragma comment(lib, "ole32") + +// NOTE: winrt translates com failures to c++ exceptions, so we must use try/catch in this file to +// prevent possible errors from escaping and terminating Dolphin. +#include +#include +#include +#include +#include +#include +#pragma comment(lib, "windowsapp") + #include -#include -#include -#include - -#include "Common/DynamicLibrary.h" -#include "Common/HRWrap.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" -using PIsApiSetImplemented = BOOL(APIENTRY*)(PCSTR Contract); -using PRoInitialize = decltype(&RoInitialize); -using PRoUninitialize = decltype(&RoUninitialize); -using PRoGetActivationFactory = decltype(&RoGetActivationFactory); -using PWindowsCreateStringReference = decltype(&WindowsCreateStringReference); -using PWindowsGetStringRawBuffer = decltype(&WindowsGetStringRawBuffer); - -namespace WGI = ABI::Windows::Gaming::Input; -using ABI::Windows::Foundation::Collections::IVectorView; -using Microsoft::WRL::ComPtr; - -namespace -{ -bool g_runtime_initialized = false; -bool g_runtime_needs_deinit = false; -PRoInitialize g_RoInitialize_address = nullptr; -PRoUninitialize g_RoUninitialize_address = nullptr; -PRoGetActivationFactory g_RoGetActivationFactory_address = nullptr; -PWindowsCreateStringReference g_WindowsCreateStringReference_address = nullptr; -PWindowsGetStringRawBuffer g_WindowsGetStringRawBuffer_address = nullptr; -} // namespace +namespace WGI = winrt::Windows::Gaming::Input; +namespace Haptics = winrt::Windows::Devices::Haptics; +using winrt::Windows::Foundation::Collections::IVectorView; namespace ciface::WGInput { @@ -47,7 +38,7 @@ static constexpr std::string_view SOURCE_NAME = "WGInput"; // These names correspond to the values of the GameControllerButtonLabel enum. // "None" is not used. // There are some overlapping names assuming no device exposes both -// GameControllerButtonLabel_XboxLeftBumper and GameControllerButtonLabel_LeftBumper. +// GameControllerButtonLabel::XboxLeftBumper and GameControllerButtonLabel::LeftBumper. // If needed we can prepend "Xbox" to relevant input names on conflict in the future. static constexpr std::array wgi_button_names = { "None", "Back", "Start", "Menu", "View", "Pad N", @@ -90,7 +81,7 @@ static constexpr MemberName gamepad_motor_names[] class Device : public Core::Device { public: - Device(std::string name, ComPtr raw_controller, WGI::IGamepad* gamepad) + Device(std::string name, WGI::RawGameController raw_controller, WGI::Gamepad gamepad) : m_name(std::move(name)), m_raw_controller(raw_controller), m_gamepad(gamepad) { // Buttons: @@ -122,17 +113,24 @@ public: if (use_raw_controller_axes) { - // Axes: - INT32 axis_count = 0; - if (SUCCEEDED(m_raw_controller->get_AxisCount(&axis_count))) - m_axes.resize(axis_count); - - u32 i = 0; - for (auto& axis : m_axes) + try { - // AddAnalogInputs adds additional "FullAnalogSurface" Inputs. - AddAnalogInputs(new IndexedAxis(&axis, 0.5, +0.5, i), new IndexedAxis(&axis, 0.5, -0.5, i)); - ++i; + // Axes: + m_axes.resize(m_raw_controller.AxisCount()); + + u32 i = 0; + for (auto& axis : m_axes) + { + // AddAnalogInputs adds additional "FullAnalogSurface" Inputs. + AddAnalogInputs(new IndexedAxis(&axis, 0.5, +0.5, i), + new IndexedAxis(&axis, 0.5, -0.5, i)); + ++i; + } + } + catch (winrt::hresult_error) + { + // If AxisCount failed, m_axes will remain zero-sized; nothing to do. + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating axes"); } } @@ -144,29 +142,37 @@ public: // Switches (Hats): if (use_raw_controller_switches) { - INT32 switch_count = 0; - if (SUCCEEDED(m_raw_controller->get_SwitchCount(&switch_count))) - m_switches.resize(switch_count); - - u32 i = 0; - for (auto& swtch : m_switches) + try { - using gcsp = WGI::GameControllerSwitchPosition; + m_switches.resize(m_raw_controller.SwitchCount()); + // Accumulate switch kinds first, to ensure no inputs are added if there is any error. + std::vector switch_kinds; + for (u32 i = 0; i < m_switches.size(); i++) + switch_kinds.push_back(m_raw_controller.GetSwitchKind(i)); - WGI::GameControllerSwitchKind switch_kind; - m_raw_controller->GetSwitchKind(i, &switch_kind); - - AddInput(new IndexedSwitch(&swtch, i, gcsp::GameControllerSwitchPosition_Up)); - AddInput(new IndexedSwitch(&swtch, i, gcsp::GameControllerSwitchPosition_Down)); - - if (switch_kind != WGI::GameControllerSwitchKind_TwoWay) + u32 i = 0; + for (auto& swtch : m_switches) { - // If it's not a "two-way" switch (up/down only) then add the left/right inputs. - AddInput(new IndexedSwitch(&swtch, i, gcsp::GameControllerSwitchPosition_Left)); - AddInput(new IndexedSwitch(&swtch, i, gcsp::GameControllerSwitchPosition_Right)); - } + using gcsp = WGI::GameControllerSwitchPosition; - ++i; + AddInput(new IndexedSwitch(&swtch, i, gcsp::Up)); + AddInput(new IndexedSwitch(&swtch, i, gcsp::Down)); + + if (switch_kinds[i] != WGI::GameControllerSwitchKind::TwoWay) + { + // If it's not a "two-way" switch (up/down only) then add the left/right inputs. + AddInput(new IndexedSwitch(&swtch, i, gcsp::Left)); + AddInput(new IndexedSwitch(&swtch, i, gcsp::Right)); + } + + ++i; + } + } + catch (winrt::hresult_error) + { + // Safe as no inputs will have been added. + m_switches.clear(); + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating switches"); } } @@ -174,21 +180,16 @@ public: PopulateHaptics(); // Battery: - if (SUCCEEDED(m_raw_controller->QueryInterface(&m_controller_battery)) && m_controller_battery) - { - // It seems many controllers provide IGameControllerBatteryInfo with no battery info. - if (UpdateBatteryLevel()) - AddInput(new Battery(&m_battery_level)); - else - m_controller_battery = nullptr; - } + if (UpdateBatteryLevel()) + AddInput(new Battery(&m_battery_level)); } int GetSortPriority() const override { return -1; } + const WGI::RawGameController GetRawGameController() const { return m_raw_controller; } + private: - // `boolean` comes from Windows API. (typedef of unsigned char) - using ButtonValueType = boolean; + using ButtonValueType = u8; class Button : public Input { @@ -312,7 +313,7 @@ private: public: IndexedSwitch(const WGI::GameControllerSwitchPosition* swtch, u32 index, WGI::GameControllerSwitchPosition direction) - : m_switch(*swtch), m_index(index), m_direction(direction) + : m_switch(*swtch), m_index(index), m_direction(static_cast(direction)) { } std::string GetName() const override @@ -321,20 +322,20 @@ private: } ControlState GetState() const override { - if (m_switch == WGI::GameControllerSwitchPosition_Center) + if (m_switch == WGI::GameControllerSwitchPosition::Center) return 0.0; // All of the "inbetween" states (e.g. Up-Right) are one-off from the four cardinal // directions. This tests that the current switch state value is within 1 of the desired // state. - const auto direction_diff = std::abs(m_switch - m_direction); + const auto direction_diff = std::abs(static_cast(m_switch) - m_direction); return ControlState(direction_diff <= 1 || direction_diff == 7); } private: const WGI::GameControllerSwitchPosition& m_switch; const u32 m_index; - const WGI::GameControllerSwitchPosition m_direction; + const int32_t m_direction; }; class Battery : public Input @@ -355,9 +356,8 @@ private: class SimpleHaptics : public Output { public: - SimpleHaptics(ComPtr haptics, - ComPtr feedback, - u32 haptics_index) + SimpleHaptics(Haptics::SimpleHapticsController haptics, + Haptics::SimpleHapticsControllerFeedback feedback, u32 haptics_index) : m_haptics(haptics), m_feedback(feedback), m_haptics_index(haptics_index) { } @@ -369,18 +369,25 @@ private: m_current_state = state; - if (state) - m_haptics->SendHapticFeedbackWithIntensity(m_feedback.Get(), state); - else - m_haptics->StopFeedback(); + try + { + if (state) + m_haptics.SendHapticFeedback(m_feedback, state); + else + m_haptics.StopFeedback(); + } + catch (winrt::hresult_error) + { + // Ignore + } } protected: u32 GetHapticsIndex() const { return m_haptics_index; } private: - ComPtr m_haptics; - ComPtr m_feedback; + Haptics::SimpleHapticsController m_haptics; + Haptics::SimpleHapticsControllerFeedback m_feedback; const u32 m_haptics_index; ControlState m_current_state = 0; }; @@ -388,9 +395,9 @@ private: class NamedFeedback final : public SimpleHaptics { public: - NamedFeedback(ComPtr haptics, - ComPtr feedback, - u32 haptics_index, std::string_view feedback_name) + NamedFeedback(Haptics::SimpleHapticsController haptics, + Haptics::SimpleHapticsControllerFeedback feedback, u32 haptics_index, + std::string_view feedback_name) : SimpleHaptics(haptics, feedback, haptics_index), m_feedback_name(feedback_name) { } @@ -405,91 +412,77 @@ private: void PopulateButtons() { - // Using RawGameController for buttons because it gives us a nice array instead of a bitmask. - INT32 button_count = 0; - if (SUCCEEDED(m_raw_controller->get_ButtonCount(&button_count))) - m_buttons.resize(button_count); - - u32 i = 0; - for (auto& button : m_buttons) + try { - WGI::GameControllerButtonLabel lbl = WGI::GameControllerButtonLabel_None; - m_raw_controller->GetButtonLabel(i, &lbl); + // Using RawGameController for buttons because it gives us a nice array instead of a bitmask. + m_buttons.resize(m_raw_controller.ButtonCount()); - if (lbl != WGI::GameControllerButtonLabel_None && lbl < wgi_button_names.size()) - AddInput(new NamedButton(&button, wgi_button_names[lbl])); - else - AddInput(new IndexedButton(&button, i)); + u32 i = 0; + for (const auto& button : m_buttons) + { + WGI::GameControllerButtonLabel lbl{WGI::GameControllerButtonLabel::None}; + try + { + lbl = m_raw_controller.GetButtonLabel(i); + } + catch (winrt::hresult_error) + { + lbl = WGI::GameControllerButtonLabel::None; + } - ++i; + const int32_t button_name_idx = static_cast(lbl); + if (lbl != WGI::GameControllerButtonLabel::None && + button_name_idx < wgi_button_names.size()) + AddInput(new NamedButton(&button, wgi_button_names[button_name_idx])); + else + AddInput(new IndexedButton(&button, i)); + + ++i; + } + } + catch (winrt::hresult_error) + { + // If ButtonCount failed, m_buttons will be zero-sized. + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating buttons"); } } void PopulateHaptics() { - WGI::IRawGameController2* rgc2 = nullptr; - if (FAILED(m_raw_controller->QueryInterface(&rgc2)) || !rgc2) - return; + static const std::map waveform_name_map{ + {Haptics::KnownSimpleHapticsControllerWaveforms::Click(), "Click"}, + {Haptics::KnownSimpleHapticsControllerWaveforms::BuzzContinuous(), "Buzz"}, + {Haptics::KnownSimpleHapticsControllerWaveforms::RumbleContinuous(), "Rumble"}, + }; - IVectorView* haptics = nullptr; - if (FAILED(rgc2->get_SimpleHapticsControllers(&haptics)) || !haptics) - return; - - unsigned int haptic_count = 0; - if (FAILED(haptics->get_Size(&haptic_count))) - return; - - for (unsigned int h = 0; h != haptic_count; ++h) + try { - ComPtr haptic; - if (FAILED(haptics->GetAt(h, &haptic))) - continue; - - IVectorView* feedbacks = - nullptr; - if (FAILED(haptic->get_SupportedFeedback(&feedbacks)) || !feedbacks) - continue; - - unsigned int feedback_count = 0; - if (FAILED(haptics->get_Size(&feedback_count))) - continue; - - for (unsigned int f = 0; f != feedback_count; ++f) + // SimpleHapticsControllers is available from Windows 1709. + u32 haptics_index = 0; + for (auto haptics_controller : m_raw_controller.SimpleHapticsControllers()) { - ComPtr feedback; - if (FAILED(feedbacks->GetAt(f, &feedback))) - continue; - - UINT16 waveform = 0; - if (FAILED(feedback->get_Waveform(&waveform))) - continue; - - std::string_view waveform_name{}; - - // Haptic Usage Page from HID spec. - switch (waveform) + for (Haptics::SimpleHapticsControllerFeedback feedback : + haptics_controller.SupportedFeedback()) { - case 0x1003: - waveform_name = "Click"; - break; - case 0x1004: - waveform_name = "Buzz"; - break; - case 0x1005: - waveform_name = "Rumble"; - break; + const uint16_t waveform = feedback.Waveform(); + auto waveform_name_it = waveform_name_map.find(waveform); + if (waveform_name_it == waveform_name_map.end()) + { + WARN_LOG_FMT(CONTROLLERINTERFACE, + "WGInput: Unhandled haptics feedback waveform: 0x{:04x}.", waveform); + continue; + } + AddOutput(new NamedFeedback(haptics_controller, feedback, haptics_index, + waveform_name_it->second)); } - - if (!waveform_name.data()) - { - WARN_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Unknown haptics feedback waveform: 0x{:04x}.", - waveform); - continue; - } - - AddOutput(new NamedFeedback(haptic, feedback, h, waveform_name)); + ++haptics_index; } } + catch (winrt::hresult_error) + { + // Don't need to cleanup any state. It's OK if some outputs were added. + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Error populating haptics"); + } } std::string GetName() const override { return m_name; } @@ -499,217 +492,221 @@ private: void UpdateInput() override { // IRawGameController: - const auto button_count = UINT32(m_buttons.size()); - const auto switch_count = UINT32(m_switches.size()); - const auto axis_count = UINT32(m_axes.size()); - UINT64 timestamp = 0; - const HRESULT raw_result = m_raw_controller->GetCurrentReading( - button_count, m_buttons.data(), switch_count, m_switches.data(), axis_count, m_axes.data(), - ×tamp); - if (FAILED(raw_result)) + static_assert(sizeof(bool) == sizeof(ButtonValueType)); + // This cludge is needed to workaround GetCurrentReading wanting array_view, while + // using std::vector would create a bit-packed array, which isn't wanted. So, we keep + // vector and view it as array. + auto buttons = + winrt::array_view(reinterpret_cast::pointer>(&m_buttons[0]), + static_cast::size_type>(m_buttons.size())); + try + { + m_raw_controller.GetCurrentReading(buttons, m_switches, m_axes); + } + catch (winrt::hresult_error error) { ERROR_LOG_FMT(CONTROLLERINTERFACE, - "WGInput: IRawGameController::GetCurrentReading failed: {}", - Common::HRWrap(raw_result)); + "WGInput: IRawGameController::GetCurrentReading failed: {:x}", error.code()); } // IGamepad: if (m_gamepad) { - const HRESULT gamepad_result = m_gamepad->GetCurrentReading(&m_gamepad_reading); - if (FAILED(gamepad_result)) + try { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: IGamepad::GetCurrentReading failed: {}", - Common::HRWrap(gamepad_result)); + m_gamepad_reading = m_gamepad.GetCurrentReading(); + } + catch (winrt::hresult_error error) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: IGamepad::GetCurrentReading failed: {:x}", + error.code()); } } // IGameControllerBatteryInfo: - if (m_controller_battery && !UpdateBatteryLevel()) - { + if (!UpdateBatteryLevel()) DEBUG_LOG_FMT(CONTROLLERINTERFACE, "WGInput: UpdateBatteryLevel failed."); + } + + void UpdateMotors() + { + try + { + m_gamepad.Vibration(m_state_out); + } + catch (winrt::hresult_error) + { + // Ignore } } - void UpdateMotors() { m_gamepad->put_Vibration(m_state_out); } - bool UpdateBatteryLevel() { - ABI::Windows::Devices::Power::IBatteryReport* report = nullptr; - - if (FAILED(m_controller_battery->TryGetBatteryReport(&report)) || !report) - return false; - - using ABI::Windows::System::Power::BatteryStatus; - BatteryStatus status; - if (FAILED(report->get_Status(&status))) - return false; - - switch (status) + try { - case BatteryStatus::BatteryStatus_NotPresent: - m_battery_level = 0; - return true; + const winrt::Windows::Devices::Power::BatteryReport report = + m_raw_controller.TryGetBatteryReport(); + if (!report) + return false; - case BatteryStatus::BatteryStatus_Idle: - case BatteryStatus::BatteryStatus_Charging: - m_battery_level = BATTERY_INPUT_MAX_VALUE; - return true; + // TryGetBatteryReport causes a memleak of 0x58 bytes each time it is called, up to at + // least Windows 21H2. A hack to fix the memleak is recorded here for posterity. + // auto report_ = *(uintptr_t***)&report; + // auto rc = &report_[0x40 / 8][0x30 / 8]; + // if (*rc == 2) + // (*rc)--; - default: - break; - } + using winrt::Windows::System::Power::BatteryStatus; + const BatteryStatus status = report.Status(); + switch (status) + { + case BatteryStatus::NotPresent: + m_battery_level = 0; + return true; - ABI::Windows::Foundation::IReference*i_remaining = nullptr, *i_full = nullptr; - int remaining_value = 0; - int full_value = 0; + case BatteryStatus::Idle: + case BatteryStatus::Charging: + m_battery_level = BATTERY_INPUT_MAX_VALUE; + return true; - if (report && SUCCEEDED(report->get_RemainingCapacityInMilliwattHours(&i_remaining)) && - i_remaining && SUCCEEDED(i_remaining->get_Value(&remaining_value)) && - SUCCEEDED(report->get_FullChargeCapacityInMilliwattHours(&i_full)) && i_full && - SUCCEEDED(i_full->get_Value(&full_value))) - { + default: + break; + } + + const int32_t full_value = report.FullChargeCapacityInMilliwattHours().GetInt32(); + const int32_t remaining_value = report.RemainingCapacityInMilliwattHours().GetInt32(); m_battery_level = BATTERY_INPUT_MAX_VALUE * remaining_value / full_value; return true; } - - return false; + catch (winrt::hresult_error) + { + return false; + } } const std::string m_name; - ComPtr const m_raw_controller; + const WGI::RawGameController m_raw_controller; std::vector m_buttons; std::vector m_switches; std::vector m_axes; - WGI::IGamepad* m_gamepad = nullptr; + WGI::Gamepad m_gamepad = nullptr; WGI::GamepadReading m_gamepad_reading{}; WGI::GamepadVibration m_state_out{}; - WGI::IGameControllerBatteryInfo* m_controller_battery = nullptr; ControlState m_battery_level = 0; }; -static bool LoadFunctionPointers() +static thread_local bool s_initialized_winrt; +static winrt::event_token s_event_added, s_event_removed; + +static bool COMIsInitialized() { - static Common::DynamicLibrary winrt_l1_1_0_handle; - static Common::DynamicLibrary winrt_string_l1_1_0_handle; - if (!winrt_l1_1_0_handle.IsOpen() && !winrt_string_l1_1_0_handle.IsOpen()) + APTTYPE apt_type{}; + APTTYPEQUALIFIER apt_qualifier{}; + return CoGetApartmentType(&apt_type, &apt_qualifier) == S_OK; +} + +static void AddDevice(const WGI::RawGameController& raw_game_controller) +{ + // Get user-facing device name if available. Otherwise generate a name from vid/pid. + std::string device_name; + try { - Common::DynamicLibrary kernelBase("KernelBase.dll"); - if (!kernelBase.IsOpen()) - return false; - - void* const IsApiSetImplemented_address = kernelBase.GetSymbolAddress("IsApiSetImplemented"); - if (!IsApiSetImplemented_address) - return false; - if (!static_cast(IsApiSetImplemented_address)( - "api-ms-win-core-winrt-l1-1-0")) + device_name = StripWhitespace(WStringToUTF8(raw_game_controller.DisplayName().c_str())); + if (device_name.empty()) { - return false; + const u16 vid = raw_game_controller.HardwareVendorId(); + const u16 pid = raw_game_controller.HardwareProductId(); + device_name = fmt::format("Device_{:04x}:{:04x}", vid, pid); + INFO_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Empty device name, using {}", device_name); } - if (!static_cast(IsApiSetImplemented_address)( - "api-ms-win-core-winrt-string-l1-1-0")) - { - return false; - } - - winrt_l1_1_0_handle.Open("api-ms-win-core-winrt-l1-1-0.dll"); - winrt_string_l1_1_0_handle.Open("api-ms-win-core-winrt-string-l1-1-0.dll"); + } + catch (winrt::hresult_error) + { + device_name = "Device"; + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to get device name, using {}", device_name); } - if (!winrt_l1_1_0_handle.IsOpen() || !winrt_string_l1_1_0_handle.IsOpen()) - return false; + try + { + WGI::Gamepad gamepad = WGI::Gamepad::FromGameController(raw_game_controller); + // Note that gamepad may be nullptr here. The Device class will deal with this. + auto dev = std::make_shared(std::move(device_name), raw_game_controller, gamepad); - g_RoInitialize_address = - static_cast(winrt_l1_1_0_handle.GetSymbolAddress("RoInitialize")); - if (!g_RoInitialize_address) - return false; - - g_RoUninitialize_address = - static_cast(winrt_l1_1_0_handle.GetSymbolAddress("RoUninitialize")); - if (!g_RoUninitialize_address) - return false; - - g_RoGetActivationFactory_address = static_cast( - winrt_l1_1_0_handle.GetSymbolAddress("RoGetActivationFactory")); - if (!g_RoGetActivationFactory_address) - return false; - - g_WindowsCreateStringReference_address = static_cast( - winrt_string_l1_1_0_handle.GetSymbolAddress("WindowsCreateStringReference")); - if (!g_WindowsCreateStringReference_address) - return false; - - g_WindowsGetStringRawBuffer_address = static_cast( - winrt_string_l1_1_0_handle.GetSymbolAddress("WindowsGetStringRawBuffer")); - if (!g_WindowsGetStringRawBuffer_address) - return false; - - return true; + // Only add if it has some inputs/outputs. + if (dev->Inputs().size() || dev->Outputs().size()) + g_controller_interface.AddDevice(std::move(dev)); + } + catch (winrt::hresult_error) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to add device {}", device_name); + } } +static void RemoveDevice(const WGI::RawGameController& raw_game_controller) +{ + g_controller_interface.RemoveDevice([&](const auto* dev) { + if (dev->GetSource() != SOURCE_NAME) + return false; + return static_cast(dev)->GetRawGameController() == raw_game_controller; + }); +} + +#pragma warning(push) +// 'winrt::impl::implements_delegate,H>': +// class has virtual functions, but its non-trivial destructor is not virtual; instances of this +// class may not be destructed correctly +// (H is the lambda) +#pragma warning(disable : 4265) + void Init() { - if (g_runtime_initialized) - return; - - if (!LoadFunctionPointers()) + if (!COMIsInitialized()) { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: System lacks support, skipping init."); - return; + // NOTE: Devices in g_controller_interface should only be accessed by threads that have had + // winrt (com) initialized. + winrt::init_apartment(); + s_initialized_winrt = true; } - const HRESULT hr = g_RoInitialize_address(RO_INIT_MULTITHREADED); - switch (hr) + try { - case S_OK: - g_runtime_initialized = true; - g_runtime_needs_deinit = true; - break; + // 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); + }); - case S_FALSE: - case RPC_E_CHANGED_MODE: - g_runtime_initialized = true; - break; - - default: - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: RoInitialize failed: {}", Common::HRWrap(hr)); - break; + s_event_removed = WGI::RawGameController::RawGameControllerRemoved( + [](auto&&, const WGI::RawGameController raw_game_controller) { + RemoveDevice(raw_game_controller); + }); + } + catch (winrt::hresult_error) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to register event handlers"); } } +#pragma warning(pop) + void DeInit() { - if (!g_runtime_initialized) - return; + WGI::RawGameController::RawGameControllerAdded(s_event_added); + WGI::RawGameController::RawGameControllerRemoved(s_event_removed); - if (g_runtime_needs_deinit) + if (s_initialized_winrt) { - g_RoUninitialize_address(); - g_runtime_needs_deinit = false; + winrt::uninit_apartment(); + s_initialized_winrt = false; } - - g_runtime_initialized = false; -} - -template -static HRESULT WindowsCreateStringReferenceAutoSizeWrapper(wchar_t const (&source_string)[size], - HSTRING_HEADER* hstring_header, - HSTRING* hstring) -{ - return g_WindowsCreateStringReference_address(source_string, size - 1, hstring_header, hstring); } void PopulateDevices() { - if (!g_runtime_initialized) - return; - - g_controller_interface.RemoveDevice( - [](const auto* dev) { return dev->GetSource() == SOURCE_NAME; }); - // WGI Interfaces to potentially use: // Gamepad: Buttons, 2x Sticks and 2x Triggers, 4x Vibration Motors // RawGameController: Buttons, Switches (Hats), Axes, Haptics @@ -719,96 +716,23 @@ void PopulateDevices() // RacingWheel: Buttons, Clutch, Handbrake, PatternShifterGear, Throttle, Wheel, WheelMotor // UINavigationController: Directions, Scrolling, etc. - HSTRING_HEADER header_raw_game_controller; - HSTRING hstr_raw_game_controller; - HRESULT hr; - if (FAILED(hr = WindowsCreateStringReferenceAutoSizeWrapper( - L"Windows.Gaming.Input.RawGameController", &header_raw_game_controller, - &hstr_raw_game_controller))) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to create string reference: {}", - Common::HRWrap(hr)); - return; - } + // RawGameController available from Windows 15063. + // properties added in 1709: DisplayName, NonRoamableId, SimpleHapticsControllers - HSTRING_HEADER header_gamepad; - HSTRING hstr_gamepad; - if (FAILED(hr = WindowsCreateStringReferenceAutoSizeWrapper(L"Windows.Gaming.Input.Gamepad", - &header_gamepad, &hstr_gamepad))) + try { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to create string reference: {}", - Common::HRWrap(hr)); - return; - } - - ComPtr raw_stats; - if (FAILED(hr = g_RoGetActivationFactory_address( - hstr_raw_game_controller, __uuidof(WGI::IRawGameControllerStatics), &raw_stats))) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to get IRawGameControllerStatics: {}", - Common::HRWrap(hr)); - return; - } - - ComPtr gamepad_stats; - if (FAILED(hr = g_RoGetActivationFactory_address(hstr_gamepad, __uuidof(WGI::IGamepadStatics2), - &gamepad_stats))) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to get IGamepadStatics2: {}", - Common::HRWrap(hr)); - return; - } - - IVectorView* raw_controllers; - if (FAILED(hr = raw_stats->get_RawGameControllers(&raw_controllers))) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: get_RawGameControllers failed: {}", - Common::HRWrap(hr)); - return; - } - - unsigned int raw_count = 0; - raw_controllers->get_Size(&raw_count); - for (unsigned i = 0; i != raw_count; ++i) - { - ComPtr raw_controller; - if (SUCCEEDED(raw_controllers->GetAt(i, &raw_controller)) && raw_controller) + for (const WGI::RawGameController& raw_game_controller : + WGI::RawGameController::RawGameControllers()) { - std::string device_name; - - // Attempt to get the controller's name. - WGI::IRawGameController2* rgc2 = nullptr; - if (SUCCEEDED(hr = raw_controller->QueryInterface(&rgc2)) && rgc2) - { - HSTRING hstr = {}; - if (SUCCEEDED(hr = rgc2->get_DisplayName(&hstr)) && hstr) - { - device_name = - StripWhitespace(WStringToUTF8(g_WindowsGetStringRawBuffer_address(hstr, nullptr))); - } - } - - if (device_name.empty()) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to get device name: {}", - Common::HRWrap(hr)); - // Set a default name if we couldn't query the name or it was empty. - device_name = "Device"; - } - - WGI::IGameController* gamecontroller = nullptr; - WGI::IGamepad* gamepad = nullptr; - if (SUCCEEDED(raw_controller->QueryInterface(&gamecontroller)) && gamecontroller) - gamepad_stats->FromGameController(gamecontroller, &gamepad); - - // Note that gamepad may be nullptr here. The Device class will deal with this. - auto dev = std::make_shared(std::move(device_name), raw_controller, gamepad); - - // Only add if it has some inputs/outputs. - if (dev->Inputs().size() || dev->Outputs().size()) - g_controller_interface.AddDevice(std::move(dev)); + RemoveDevice(raw_game_controller); + AddDevice(raw_game_controller); } } + catch (winrt::hresult_error) + { + // Only reach here if RawGameControllers() failed + ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: PopulateDevices failed"); + } } } // namespace ciface::WGInput diff --git a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp index 42aa12a141..21e9847964 100644 --- a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp @@ -3,35 +3,38 @@ #include "InputCommon/ControllerInterface/Win32/Win32.h" -#include +#include +#include +// must be before hidclass +#include + +#include -#include -#include #include -#include #include "Common/Flag.h" -#include "Common/HRWrap.h" #include "Common/Logging/Log.h" -#include "Common/ScopeGuard.h" -#include "Common/Thread.h" #include "InputCommon/ControllerInterface/DInput/DInput.h" #include "InputCommon/ControllerInterface/WGInput/WGInput.h" #include "InputCommon/ControllerInterface/XInput/XInput.h" -constexpr UINT WM_DOLPHIN_STOP = WM_USER; +#pragma comment(lib, "OneCoreUAP.Lib") // Dolphin's render window static HWND s_hwnd; -// Windows messaging window (hidden) -static HWND s_message_window; -static std::thread s_thread; static std::mutex s_populate_mutex; +// TODO is this really needed? static Common::Flag s_first_populate_devices_asked; +static HCMNOTIFICATION s_notify_handle; -static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +_Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALLBACK + OnDevicesChanged(_In_ HCMNOTIFICATION hNotify, _In_opt_ PVOID Context, + _In_ CM_NOTIFY_ACTION Action, + _In_reads_bytes_(EventDataSize) PCM_NOTIFY_EVENT_DATA EventData, + _In_ DWORD EventDataSize) { - if (message == WM_INPUT_DEVICE_CHANGE) + if (Action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL || + Action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { // Windows automatically sends this message before we ask for it and before we are "ready" to // listen for it. @@ -43,147 +46,63 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARA g_controller_interface.PlatformPopulateDevices([] { ciface::DInput::PopulateDevices(s_hwnd); ciface::XInput::PopulateDevices(); - ciface::WGInput::PopulateDevices(); }); } } - - return DefWindowProc(hwnd, message, wparam, lparam); + return ERROR_SUCCESS; } void ciface::Win32::Init(void* hwnd) { s_hwnd = static_cast(hwnd); + XInput::Init(); WGInput::Init(); - std::promise message_window_promise; - - s_thread = std::thread([&message_window_promise] { - Common::SetCurrentThreadName("ciface::Win32 Message Loop"); - - HWND message_window = nullptr; - Common::ScopeGuard promise_guard([&] { message_window_promise.set_value(message_window); }); - - HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - if (FAILED(hr)) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "CoInitializeEx failed: {}", Common::HRWrap(hr)); - return; - } - Common::ScopeGuard uninit([] { CoUninitialize(); }); - - const auto window_name = TEXT("DolphinWin32ControllerInterface"); - - WNDCLASSEX window_class_info{}; - window_class_info.cbSize = sizeof(window_class_info); - window_class_info.lpfnWndProc = WindowProc; - window_class_info.hInstance = GetModuleHandle(nullptr); - window_class_info.lpszClassName = window_name; - - ATOM window_class = RegisterClassEx(&window_class_info); - if (!window_class) - { - NOTICE_LOG_FMT(CONTROLLERINTERFACE, "RegisterClassEx failed: {}", - Common::HRWrap(GetLastError())); - return; - } - Common::ScopeGuard unregister([&window_class] { - if (!UnregisterClass(MAKEINTATOM(window_class), GetModuleHandle(nullptr))) - ERROR_LOG_FMT(CONTROLLERINTERFACE, "UnregisterClass failed: {}", - Common::HRWrap(GetLastError())); - }); - - message_window = CreateWindowEx(0, window_name, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, - nullptr, nullptr); - promise_guard.Exit(); - if (!message_window) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "CreateWindowEx failed: {}", - Common::HRWrap(GetLastError())); - return; - } - Common::ScopeGuard destroy([&] { - if (!DestroyWindow(message_window)) - ERROR_LOG_FMT(CONTROLLERINTERFACE, "DestroyWindow failed: {}", - Common::HRWrap(GetLastError())); - }); - - std::array devices; - // game pad devices - devices[0].usUsagePage = 0x01; - devices[0].usUsage = 0x05; - devices[0].dwFlags = RIDEV_DEVNOTIFY; - devices[0].hwndTarget = message_window; - // joystick devices - devices[1].usUsagePage = 0x01; - devices[1].usUsage = 0x04; - devices[1].dwFlags = RIDEV_DEVNOTIFY; - devices[1].hwndTarget = message_window; - - if (!RegisterRawInputDevices(devices.data(), static_cast(devices.size()), - static_cast(sizeof(decltype(devices)::value_type)))) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "RegisterRawInputDevices failed: {}", GetLastError()); - return; - } - - MSG msg; - while (GetMessage(&msg, nullptr, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - if (msg.message == WM_DOLPHIN_STOP) - break; - } - }); - - s_message_window = message_window_promise.get_future().get(); + 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, nullptr, OnDevicesChanged, &s_notify_handle); + if (cfg_rv != CR_SUCCESS) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Register_Notification failed: {:x}", cfg_rv); + } } void ciface::Win32::PopulateDevices(void* hwnd) { - if (s_thread.joinable()) - { - s_hwnd = static_cast(hwnd); - // To avoid blocking this thread until the async population has finished, directly do it here - // (we need the DInput Keyboard and Mouse "default" device to always be added without any wait). - std::lock_guard lk_population(s_populate_mutex); - s_first_populate_devices_asked.Set(); - ciface::DInput::PopulateDevices(s_hwnd); - ciface::XInput::PopulateDevices(); - ciface::WGInput::PopulateDevices(); - } - else - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, - "win32 asked to populate devices, but device thread isn't running"); - } + s_hwnd = static_cast(hwnd); + std::lock_guard lk_population(s_populate_mutex); + s_first_populate_devices_asked.Set(); + ciface::DInput::PopulateDevices(s_hwnd); + ciface::XInput::PopulateDevices(); + ciface::WGInput::PopulateDevices(); } void ciface::Win32::ChangeWindow(void* hwnd) { - if (s_thread.joinable()) // "Has init?" - { - s_hwnd = static_cast(hwnd); - std::lock_guard lk_population(s_populate_mutex); - ciface::DInput::ChangeWindow(s_hwnd); - } + s_hwnd = static_cast(hwnd); + std::lock_guard lk_population(s_populate_mutex); + ciface::DInput::ChangeWindow(s_hwnd); } void ciface::Win32::DeInit() { - NOTICE_LOG_FMT(CONTROLLERINTERFACE, "win32 DeInit"); - if (s_thread.joinable()) - { - PostMessage(s_message_window, WM_DOLPHIN_STOP, 0, 0); - s_thread.join(); - s_message_window = nullptr; - s_first_populate_devices_asked.Clear(); - DInput::DeInit(); - } + s_first_populate_devices_asked.Clear(); + DInput::DeInit(); s_hwnd = nullptr; + if (s_notify_handle) + { + const CONFIGRET cfg_rv = CM_Unregister_Notification(s_notify_handle); + if (cfg_rv != CR_SUCCESS) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Unregister_Notification failed: {:x}", cfg_rv); + } + s_notify_handle = nullptr; + } + XInput::DeInit(); WGInput::DeInit(); }