From 076a262b9e936860411213f3d2e2d2de97664c88 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 20 Oct 2020 11:30:15 -0500 Subject: [PATCH] InputCommon: Add Windows.Gaming.Input to ControllerInterface. --- Source/Core/DolphinLib.vcxproj | 6 + Source/Core/InputCommon/CMakeLists.txt | 2 + .../ControllerInterface/WGInput/WGInput.cpp | 693 ++++++++++++++++++ .../ControllerInterface/WGInput/WGInput.h | 12 + .../ControllerInterface/Win32/Win32.cpp | 5 + 5 files changed, 718 insertions(+) create mode 100644 Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp create mode 100644 Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h diff --git a/Source/Core/DolphinLib.vcxproj b/Source/Core/DolphinLib.vcxproj index 5485304bdd..190d0b60eb 100644 --- a/Source/Core/DolphinLib.vcxproj +++ b/Source/Core/DolphinLib.vcxproj @@ -27,6 +27,12 @@ {41279555-f94f-4ebc-99de-af863c10c5c4} + + + + + + \ No newline at end of file diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index e600004cda..4f9510d7a5 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -94,6 +94,8 @@ if(WIN32) ControllerInterface/DInput/XInputFilter.h ControllerInterface/Win32/Win32.cpp ControllerInterface/Win32/Win32.h + ControllerInterface/WGInput/WGInput.cpp + ControllerInterface/WGInput/WGInput.h ControllerInterface/XInput/XInput.cpp ControllerInterface/XInput/XInput.h ControllerInterface/ForceFeedback/ForceFeedbackDevice.cpp diff --git a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp new file mode 100644 index 0000000000..231af03d46 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp @@ -0,0 +1,693 @@ +// Copyright 2020 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "InputCommon/ControllerInterface/WGInput/WGInput.h" + +#include +#include + +#include + +#include +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +#pragma comment(lib, "runtimeobject.lib") + +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; +} // namespace + +namespace ciface::WGInput +{ +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. +// 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", + "Pad S", "Pad W", "Pad E", "Button A", "Button B", "Button X", + "Button Y", "Bumper L", "Trigger L", "Thumb L", "Bumper R", "Trigger R", + "Thumb R", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4", "Mode", + "Select", "Menu", "View", "Back", "Start", "Options", + "Share", "Pad N", "Pad S", "Pad W", "Pad E", "Letter A", + "Letter B", "Letter C", "Letter L", "Letter R", "Letter X", "Letter Y", + "Letter Z", "Cross", "Circle", "Square", "Triangle", "Bumper L", + "Trigger L", "Thumb L", "Left 1", "Left 2", "Left 3", "Bumper R", + "Trigger R", "Thumb R", "Right 1", "Right 2", "Right 3", "Paddle 1", + "Paddle 2", "Paddle 3", "Paddle 4", "Plus", "Minus", "Down Left Arrow", + "Dial L", "Dial R", "Suspension", +}; + +template +struct MemberName +{ + M T::*ptr; + const char* name; +}; + +static constexpr MemberName gamepad_trigger_names[] = { + {&WGI::GamepadReading::LeftTrigger, "Trigger L"}, + {&WGI::GamepadReading::RightTrigger, "Trigger R"}}; + +static constexpr MemberName gamepad_axis_names[] = { + {&WGI::GamepadReading::LeftThumbstickX, "Left X"}, + {&WGI::GamepadReading::LeftThumbstickY, "Left Y"}, + {&WGI::GamepadReading::RightThumbstickX, "Right X"}, + {&WGI::GamepadReading::RightThumbstickY, "Right Y"}}; + +static constexpr MemberName gamepad_motor_names[] = { + {&WGI::GamepadVibration::LeftMotor, "Motor L"}, + {&WGI::GamepadVibration::RightMotor, "Motor R"}, + {&WGI::GamepadVibration::LeftTrigger, "Trigger L"}, + {&WGI::GamepadVibration::RightTrigger, "Trigger R"}}; + +class Device : public Core::Device +{ +public: + Device(std::string name, ComPtr raw_controller, WGI::IGamepad* gamepad) + : m_name(std::move(name)), m_raw_controller(raw_controller), m_gamepad(gamepad) + { + // Buttons: + PopulateButtons(); + + // Add inputs for IGamepad interface if available. + if (m_gamepad) + { + // Axes: + for (auto& axis : gamepad_axis_names) + { + AddInput(new NamedAxis(&(m_gamepad_reading.*axis.ptr), 0.0, -1.0, axis.name)); + AddInput(new NamedAxis(&(m_gamepad_reading.*axis.ptr), 0.0, +1.0, axis.name)); + } + + // Triggers: + for (auto& trigger : gamepad_trigger_names) + AddInput(new NamedTrigger(&(m_gamepad_reading.*trigger.ptr), trigger.name)); + + // Motors: + for (auto& motor : gamepad_motor_names) + AddOutput(new NamedMotor(&(m_state_out.*motor.ptr), motor.name, this)); + } + + // Add IRawGameController's axes if IGamepad is not available. + // This may need to change if some devices expose additional axes. + // Maybe we can determine which additional axes are not in IGamepad's collection. + const bool use_raw_controller_axes = !m_gamepad; + + 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) + { + // AddAnalogInputs adds additional "FullAnalogSurface" Inputs. + AddAnalogInputs(new IndexedAxis(&axis, 0.5, +0.5, i), new IndexedAxis(&axis, 0.5, -0.5, i)); + ++i; + } + } + + // Apparently some devices (e.g. DS4) provide the IGameController interface + // but expose the dpad only on a switch (IRawGameController interface). + // We'll need to add switches regardless of available interfaces. + constexpr bool use_raw_controller_switches = true; + + // 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) + { + using gcsp = WGI::GameControllerSwitchPosition; + + 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) + { + // 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)); + } + + ++i; + } + } + + // Haptics: + 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; + } + } + +private: + // `boolean` comes from Windows API. (typedef of unsigned char) + using ButtonValueType = boolean; + + class Button : public Input + { + public: + explicit Button(const ButtonValueType* button) : m_button(*button) {} + ControlState GetState() const override { return ControlState(m_button != 0); } + + private: + const ButtonValueType& m_button; + }; + + // A button with one of the "labels" that WGI provides. + class NamedButton final : public Button + { + public: + NamedButton(const ButtonValueType* button, std::string_view name) : Button(button), m_name(name) + { + } + std::string GetName() const override { return std::string(m_name); } + + private: + const std::string_view m_name; + }; + + // A button with no label so we name it by its index. + class IndexedButton final : public Button + { + public: + IndexedButton(const ButtonValueType* button, u32 index) : Button(button), m_index(index) {} + std::string GetName() const override { return fmt::format("Button {}", m_index); } + + private: + u32 m_index; + }; + + class Axis : public Input + { + public: + Axis(const double* axis, double base, double range) + : m_base(base), m_range(range), m_axis(*axis) + { + } + + ControlState GetState() const override { return ControlState(m_axis - m_base) / m_range; } + + protected: + const double m_base; + const double m_range; + + private: + const double& m_axis; + }; + + class NamedAxis final : public Axis + { + public: + NamedAxis(const double* axis, double base, double range, std::string_view name) + : Axis(axis, base, range), m_name(name) + { + } + std::string GetName() const override + { + return fmt::format("{}{}", m_name, m_range < 0 ? '-' : '+'); + } + + private: + const std::string_view m_name; + }; + + class NamedTrigger final : public Axis + { + public: + NamedTrigger(const double* axis, std::string_view name) : Axis(axis, 0.0, 1.0), m_name(name) {} + std::string GetName() const override { return std::string(m_name); } + + private: + const std::string_view m_name; + }; + + class NamedMotor final : public Output + { + public: + NamedMotor(double* motor, std::string_view name, Device* parent) + : m_motor(*motor), m_name(name), m_parent(*parent) + { + } + std::string GetName() const override { return std::string(m_name); } + void SetState(ControlState state) override + { + if (m_motor == state) + return; + + m_motor = state; + m_parent.UpdateMotors(); + } + + private: + double& m_motor; + const std::string_view m_name; + Device& m_parent; + }; + + class IndexedAxis final : public Axis + { + public: + IndexedAxis(const double* axis, double base, double range, u32 index) + : Axis(axis, base, range), m_index(index) + { + } + std::string GetName() const override + { + return fmt::format("Axis {}{}", m_index, m_range < 0 ? '-' : '+'); + } + + private: + const u32 m_index; + }; + + class IndexedSwitch final : public Input + { + public: + IndexedSwitch(const WGI::GameControllerSwitchPosition* swtch, u32 index, + WGI::GameControllerSwitchPosition direction) + : m_switch(*swtch), m_index(index), m_direction(direction) + { + } + std::string GetName() const override + { + return fmt::format("Switch {} {}", m_index, "NESW"[m_direction / 2]); + } + ControlState GetState() const override + { + 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); + return ControlState(direction_diff <= 1 || direction_diff == 7); + } + + private: + const WGI::GameControllerSwitchPosition& m_switch; + const u32 m_index; + const WGI::GameControllerSwitchPosition m_direction; + }; + + class Battery : public Input + { + public: + Battery(const double* level) : m_level(*level) {} + + bool IsDetectable() const override { return false; } + + ControlState GetState() const override { return m_level; } + + std::string GetName() const override { return "Battery"; } + + private: + const double& m_level; + }; + + class SimpleHaptics : public Output + { + public: + SimpleHaptics(ComPtr haptics, + ComPtr feedback, + u32 haptics_index) + : m_haptics(haptics), m_feedback(feedback), m_haptics_index(haptics_index) + { + } + + void SetState(ControlState state) override + { + if (m_current_state == state) + return; + + m_current_state = state; + + if (state) + m_haptics->SendHapticFeedbackWithIntensity(m_feedback.Get(), state); + else + m_haptics->StopFeedback(); + } + + protected: + u32 GetHapticsIndex() const { return m_haptics_index; } + + private: + ComPtr m_haptics; + ComPtr m_feedback; + const u32 m_haptics_index; + ControlState m_current_state = 0; + }; + + class NamedFeedback final : public SimpleHaptics + { + public: + NamedFeedback(ComPtr haptics, + ComPtr feedback, + u32 haptics_index, std::string_view feedback_name) + : SimpleHaptics(haptics, feedback, haptics_index), m_feedback_name(feedback_name) + { + } + std::string GetName() const override + { + return fmt::format("{} {}", m_feedback_name, GetHapticsIndex()); + } + + private: + const std::string_view m_feedback_name; + }; + + 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) + { + WGI::GameControllerButtonLabel lbl = WGI::GameControllerButtonLabel_None; + m_raw_controller->GetButtonLabel(i, &lbl); + + if (lbl != WGI::GameControllerButtonLabel_None && lbl < wgi_button_names.size()) + AddInput(new NamedButton(&button, wgi_button_names[lbl])); + else + AddInput(new IndexedButton(&button, i)); + + ++i; + } + } + + void PopulateHaptics() + { + WGI::IRawGameController2* rgc2 = nullptr; + if (FAILED(m_raw_controller->QueryInterface(&rgc2)) || !rgc2) + return; + + 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) + { + 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) + { + 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) + { + case 0x1003: + waveform_name = "Click"; + break; + case 0x1004: + waveform_name = "Buzz"; + break; + case 0x1005: + waveform_name = "Rumble"; + break; + } + + if (!waveform_name.data()) + { + WARN_LOG(CONTROLLERINTERFACE, "WGInput: Unknown haptics feedback waveform: %d.", + waveform); + continue; + } + + AddOutput(new NamedFeedback(haptic, feedback, h, waveform_name)); + } + } + } + + std::string GetName() const override { return m_name; } + + std::string GetSource() const override { return std::string(SOURCE_NAME); } + + 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; + if (FAILED(m_raw_controller->GetCurrentReading(button_count, m_buttons.data(), switch_count, + m_switches.data(), axis_count, m_axes.data(), + ×tamp))) + { + ERROR_LOG(CONTROLLERINTERFACE, "WGInput: IRawGameController::GetCurrentReading failed."); + } + + // IGamepad: + if (m_gamepad && FAILED(m_gamepad->GetCurrentReading(&m_gamepad_reading))) + { + ERROR_LOG(CONTROLLERINTERFACE, "WGInput: IGamepad::GetCurrentReading failed."); + } + + // IGameControllerBatteryInfo: + if (m_controller_battery && !UpdateBatteryLevel()) + { + DEBUG_LOG(CONTROLLERINTERFACE, "WGInput: UpdateBatteryLevel failed."); + } + } + + 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) + { + case BatteryStatus::BatteryStatus_NotPresent: + m_battery_level = 0; + return true; + + case BatteryStatus::BatteryStatus_Idle: + case BatteryStatus::BatteryStatus_Charging: + m_battery_level = BATTERY_INPUT_MAX_VALUE; + return true; + + default: + break; + } + + ABI::Windows::Foundation::IReference*i_remaining = nullptr, *i_full = nullptr; + int remaining_value = 0; + int full_value = 0; + + 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))) + { + m_battery_level = BATTERY_INPUT_MAX_VALUE * remaining_value / full_value; + return true; + } + + return false; + } + + const std::string m_name; + + ComPtr const m_raw_controller; + std::vector m_buttons; + std::vector m_switches; + std::vector m_axes; + + WGI::IGamepad* m_gamepad = nullptr; + WGI::GamepadReading m_gamepad_reading{}; + WGI::GamepadVibration m_state_out{}; + + WGI::IGameControllerBatteryInfo* m_controller_battery = nullptr; + ControlState m_battery_level = 0; +}; + +void Init() +{ + if (g_runtime_initialized) + return; + + const HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED); + switch (hr) + { + case S_OK: + g_runtime_initialized = true; + g_runtime_needs_deinit = true; + break; + + case S_FALSE: + case RPC_E_CHANGED_MODE: + g_runtime_initialized = true; + break; + + default: + ERROR_LOG(CONTROLLERINTERFACE, "WGInput: RoInitialize failed."); + break; + } +} + +void DeInit() +{ + if (!g_runtime_initialized) + return; + + if (g_runtime_needs_deinit) + { + RoUninitialize(); + g_runtime_needs_deinit = false; + } + + g_runtime_initialized = false; +} + +void PopulateDevices() +{ + if (!g_runtime_initialized) + return; + + g_controller_interface.RemoveDevice( + [](const auto* dev) { return dev->GetSource() == SOURCE_NAME; }); + + using Microsoft::WRL::Wrappers::HStringReference; + + // WGI Interfaces to potentially use: + // Gamepad: Buttons, 2x Sticks and 2x Triggers, 4x Vibration Motors + // RawGameController: Buttons, Switches (Hats), Axes, Haptics + // The following are not implemented: + // ArcadeStick: Buttons (probably no need to specialize, literally just buttons) + // FlightStick: Buttons, HatSwitch, Pitch, Roll, Throttle, Yaw + // RacingWheel: Buttons, Clutch, Handbrake, PatternShifterGear, Throttle, Wheel, WheelMotor + // UINavigationController: Directions, Scrolling, etc. + + ComPtr raw_stats; + if (FAILED( + RoGetActivationFactory(HStringReference(L"Windows.Gaming.Input.RawGameController").Get(), + __uuidof(WGI::IRawGameControllerStatics), &raw_stats))) + { + ERROR_LOG(CONTROLLERINTERFACE, "WGInput: Failed to get IRawGameControllerStatics."); + return; + } + + ComPtr gamepad_stats; + if (FAILED(RoGetActivationFactory(HStringReference(L"Windows.Gaming.Input.Gamepad").Get(), + __uuidof(WGI::IGamepadStatics2), &gamepad_stats))) + { + ERROR_LOG(CONTROLLERINTERFACE, "WGInput: Failed to get IGamepadStatics2."); + return; + } + + IVectorView* raw_controllers; + if (FAILED(raw_stats->get_RawGameControllers(&raw_controllers))) + { + ERROR_LOG(CONTROLLERINTERFACE, "WGInput: get_RawGameControllers failed."); + 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) + { + std::string device_name; + + // Attempt to get the controller's name. + WGI::IRawGameController2* rgc2 = nullptr; + if (SUCCEEDED(raw_controller->QueryInterface(&rgc2)) && rgc2) + { + HSTRING hstr = {}; + if (SUCCEEDED(rgc2->get_DisplayName(&hstr)) && hstr) + device_name = StripSpaces(WStringToUTF8(WindowsGetStringRawBuffer(hstr, nullptr))); + } + + if (device_name.empty()) + { + ERROR_LOG(CONTROLLERINTERFACE, "WGInput: Failed to get device name."); + // 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)); + } + } +} + +} // namespace ciface::WGInput diff --git a/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h new file mode 100644 index 0000000000..54fa06fc12 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.h @@ -0,0 +1,12 @@ +// Copyright 2020 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace ciface::WGInput +{ +void Init(); +void DeInit(); +void PopulateDevices(); + +} // namespace ciface::WGInput diff --git a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp index a6ff77511e..a9c28efdc0 100644 --- a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp @@ -16,6 +16,7 @@ #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; @@ -42,6 +43,7 @@ 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(); }); } } @@ -53,6 +55,7 @@ void ciface::Win32::Init(void* hwnd) { s_hwnd = static_cast(hwnd); XInput::Init(); + WGInput::Init(); std::promise message_window_promise; @@ -147,6 +150,7 @@ void ciface::Win32::PopulateDevices(void* hwnd) s_first_populate_devices_asked.Set(); ciface::DInput::PopulateDevices(s_hwnd); ciface::XInput::PopulateDevices(); + ciface::WGInput::PopulateDevices(); } else { @@ -179,4 +183,5 @@ void ciface::Win32::DeInit() s_hwnd = nullptr; XInput::DeInit(); + WGInput::DeInit(); }