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();
}