Merge pull request #11009 from shuffle2/device-notify

ciface/win32 and WGInput bug fixes
This commit is contained in:
Mai 2022-09-01 00:27:55 -04:00 committed by GitHub
commit 50550cf978
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 354 additions and 511 deletions

View File

@ -4,41 +4,32 @@
#include "InputCommon/ControllerInterface/WGInput/WGInput.h" #include "InputCommon/ControllerInterface/WGInput/WGInput.h"
#include <array> #include <array>
#include <map>
#include <string_view> #include <string_view>
// For CoGetApartmentType
#include <objbase.h>
#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 <winrt/base.h>
#include <winrt/windows.devices.haptics.h>
#include <winrt/windows.devices.power.h>
#include <winrt/windows.foundation.collections.h>
#include <winrt/windows.gaming.input.h>
#include <winrt/windows.system.power.h>
#pragma comment(lib, "windowsapp")
#include <fmt/format.h> #include <fmt/format.h>
#include <roapi.h>
#include <windows.gaming.input.h>
#include <wrl.h>
#include "Common/DynamicLibrary.h"
#include "Common/HRWrap.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
using PIsApiSetImplemented = BOOL(APIENTRY*)(PCSTR Contract); namespace WGI = winrt::Windows::Gaming::Input;
using PRoInitialize = decltype(&RoInitialize); namespace Haptics = winrt::Windows::Devices::Haptics;
using PRoUninitialize = decltype(&RoUninitialize); using winrt::Windows::Foundation::Collections::IVectorView;
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 ciface::WGInput 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. // These names correspond to the values of the GameControllerButtonLabel enum.
// "None" is not used. // "None" is not used.
// There are some overlapping names assuming no device exposes both // 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. // If needed we can prepend "Xbox" to relevant input names on conflict in the future.
static constexpr std::array wgi_button_names = { static constexpr std::array wgi_button_names = {
"None", "Back", "Start", "Menu", "View", "Pad N", "None", "Back", "Start", "Menu", "View", "Pad N",
@ -90,7 +81,7 @@ static constexpr MemberName<WGI::GamepadVibration, double> gamepad_motor_names[]
class Device : public Core::Device class Device : public Core::Device
{ {
public: public:
Device(std::string name, ComPtr<WGI::IRawGameController> 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) : m_name(std::move(name)), m_raw_controller(raw_controller), m_gamepad(gamepad)
{ {
// Buttons: // Buttons:
@ -122,17 +113,24 @@ public:
if (use_raw_controller_axes) if (use_raw_controller_axes)
{ {
// Axes: try
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. // Axes:
AddAnalogInputs(new IndexedAxis(&axis, 0.5, +0.5, i), new IndexedAxis(&axis, 0.5, -0.5, i)); m_axes.resize(m_raw_controller.AxisCount());
++i;
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): // Switches (Hats):
if (use_raw_controller_switches) if (use_raw_controller_switches)
{ {
INT32 switch_count = 0; try
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; m_switches.resize(m_raw_controller.SwitchCount());
// Accumulate switch kinds first, to ensure no inputs are added if there is any error.
std::vector<WGI::GameControllerSwitchKind> switch_kinds;
for (u32 i = 0; i < m_switches.size(); i++)
switch_kinds.push_back(m_raw_controller.GetSwitchKind(i));
WGI::GameControllerSwitchKind switch_kind; u32 i = 0;
m_raw_controller->GetSwitchKind(i, &switch_kind); for (auto& swtch : m_switches)
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. using gcsp = WGI::GameControllerSwitchPosition;
AddInput(new IndexedSwitch(&swtch, i, gcsp::GameControllerSwitchPosition_Left));
AddInput(new IndexedSwitch(&swtch, i, gcsp::GameControllerSwitchPosition_Right));
}
++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(); PopulateHaptics();
// Battery: // Battery:
if (SUCCEEDED(m_raw_controller->QueryInterface(&m_controller_battery)) && m_controller_battery) if (UpdateBatteryLevel())
{ AddInput(new Battery(&m_battery_level));
// It seems many controllers provide IGameControllerBatteryInfo with no battery info.
if (UpdateBatteryLevel())
AddInput(new Battery(&m_battery_level));
else
m_controller_battery = nullptr;
}
} }
int GetSortPriority() const override { return -1; } int GetSortPriority() const override { return -1; }
const WGI::RawGameController GetRawGameController() const { return m_raw_controller; }
private: private:
// `boolean` comes from Windows API. (typedef of unsigned char) using ButtonValueType = u8;
using ButtonValueType = boolean;
class Button : public Input class Button : public Input
{ {
@ -312,7 +313,7 @@ private:
public: public:
IndexedSwitch(const WGI::GameControllerSwitchPosition* swtch, u32 index, IndexedSwitch(const WGI::GameControllerSwitchPosition* swtch, u32 index,
WGI::GameControllerSwitchPosition direction) WGI::GameControllerSwitchPosition direction)
: m_switch(*swtch), m_index(index), m_direction(direction) : m_switch(*swtch), m_index(index), m_direction(static_cast<int32_t>(direction))
{ {
} }
std::string GetName() const override std::string GetName() const override
@ -321,20 +322,20 @@ private:
} }
ControlState GetState() const override ControlState GetState() const override
{ {
if (m_switch == WGI::GameControllerSwitchPosition_Center) if (m_switch == WGI::GameControllerSwitchPosition::Center)
return 0.0; return 0.0;
// All of the "inbetween" states (e.g. Up-Right) are one-off from the four cardinal // 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 // directions. This tests that the current switch state value is within 1 of the desired
// state. // state.
const auto direction_diff = std::abs(m_switch - m_direction); const auto direction_diff = std::abs(static_cast<int32_t>(m_switch) - m_direction);
return ControlState(direction_diff <= 1 || direction_diff == 7); return ControlState(direction_diff <= 1 || direction_diff == 7);
} }
private: private:
const WGI::GameControllerSwitchPosition& m_switch; const WGI::GameControllerSwitchPosition& m_switch;
const u32 m_index; const u32 m_index;
const WGI::GameControllerSwitchPosition m_direction; const int32_t m_direction;
}; };
class Battery : public Input class Battery : public Input
@ -355,9 +356,8 @@ private:
class SimpleHaptics : public Output class SimpleHaptics : public Output
{ {
public: public:
SimpleHaptics(ComPtr<ABI::Windows::Devices::Haptics::ISimpleHapticsController> haptics, SimpleHaptics(Haptics::SimpleHapticsController haptics,
ComPtr<ABI::Windows::Devices::Haptics::ISimpleHapticsControllerFeedback> feedback, Haptics::SimpleHapticsControllerFeedback feedback, u32 haptics_index)
u32 haptics_index)
: m_haptics(haptics), m_feedback(feedback), m_haptics_index(haptics_index) : m_haptics(haptics), m_feedback(feedback), m_haptics_index(haptics_index)
{ {
} }
@ -369,18 +369,25 @@ private:
m_current_state = state; m_current_state = state;
if (state) try
m_haptics->SendHapticFeedbackWithIntensity(m_feedback.Get(), state); {
else if (state)
m_haptics->StopFeedback(); m_haptics.SendHapticFeedback(m_feedback, state);
else
m_haptics.StopFeedback();
}
catch (winrt::hresult_error)
{
// Ignore
}
} }
protected: protected:
u32 GetHapticsIndex() const { return m_haptics_index; } u32 GetHapticsIndex() const { return m_haptics_index; }
private: private:
ComPtr<ABI::Windows::Devices::Haptics::ISimpleHapticsController> m_haptics; Haptics::SimpleHapticsController m_haptics;
ComPtr<ABI::Windows::Devices::Haptics::ISimpleHapticsControllerFeedback> m_feedback; Haptics::SimpleHapticsControllerFeedback m_feedback;
const u32 m_haptics_index; const u32 m_haptics_index;
ControlState m_current_state = 0; ControlState m_current_state = 0;
}; };
@ -388,9 +395,9 @@ private:
class NamedFeedback final : public SimpleHaptics class NamedFeedback final : public SimpleHaptics
{ {
public: public:
NamedFeedback(ComPtr<ABI::Windows::Devices::Haptics::ISimpleHapticsController> haptics, NamedFeedback(Haptics::SimpleHapticsController haptics,
ComPtr<ABI::Windows::Devices::Haptics::ISimpleHapticsControllerFeedback> feedback, Haptics::SimpleHapticsControllerFeedback feedback, u32 haptics_index,
u32 haptics_index, std::string_view feedback_name) std::string_view feedback_name)
: SimpleHaptics(haptics, feedback, haptics_index), m_feedback_name(feedback_name) : SimpleHaptics(haptics, feedback, haptics_index), m_feedback_name(feedback_name)
{ {
} }
@ -405,91 +412,77 @@ private:
void PopulateButtons() void PopulateButtons()
{ {
// Using RawGameController for buttons because it gives us a nice array instead of a bitmask. try
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; // Using RawGameController for buttons because it gives us a nice array instead of a bitmask.
m_raw_controller->GetButtonLabel(i, &lbl); m_buttons.resize(m_raw_controller.ButtonCount());
if (lbl != WGI::GameControllerButtonLabel_None && lbl < wgi_button_names.size()) u32 i = 0;
AddInput(new NamedButton(&button, wgi_button_names[lbl])); for (const auto& button : m_buttons)
else {
AddInput(new IndexedButton(&button, i)); 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<int32_t>(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() void PopulateHaptics()
{ {
WGI::IRawGameController2* rgc2 = nullptr; static const std::map<uint16_t, std::string> waveform_name_map{
if (FAILED(m_raw_controller->QueryInterface(&rgc2)) || !rgc2) {Haptics::KnownSimpleHapticsControllerWaveforms::Click(), "Click"},
return; {Haptics::KnownSimpleHapticsControllerWaveforms::BuzzContinuous(), "Buzz"},
{Haptics::KnownSimpleHapticsControllerWaveforms::RumbleContinuous(), "Rumble"},
};
IVectorView<ABI::Windows::Devices::Haptics::SimpleHapticsController*>* haptics = nullptr; try
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<ABI::Windows::Devices::Haptics::ISimpleHapticsController> haptic; // SimpleHapticsControllers is available from Windows 1709.
if (FAILED(haptics->GetAt(h, &haptic))) u32 haptics_index = 0;
continue; for (auto haptics_controller : m_raw_controller.SimpleHapticsControllers())
IVectorView<ABI::Windows::Devices::Haptics::SimpleHapticsControllerFeedback*>* 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<ABI::Windows::Devices::Haptics::ISimpleHapticsControllerFeedback> feedback; for (Haptics::SimpleHapticsControllerFeedback feedback :
if (FAILED(feedbacks->GetAt(f, &feedback))) haptics_controller.SupportedFeedback())
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: const uint16_t waveform = feedback.Waveform();
waveform_name = "Click"; auto waveform_name_it = waveform_name_map.find(waveform);
break; if (waveform_name_it == waveform_name_map.end())
case 0x1004: {
waveform_name = "Buzz"; WARN_LOG_FMT(CONTROLLERINTERFACE,
break; "WGInput: Unhandled haptics feedback waveform: 0x{:04x}.", waveform);
case 0x1005: continue;
waveform_name = "Rumble"; }
break; AddOutput(new NamedFeedback(haptics_controller, feedback, haptics_index,
waveform_name_it->second));
} }
++haptics_index;
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));
} }
} }
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; } std::string GetName() const override { return m_name; }
@ -499,217 +492,221 @@ private:
void UpdateInput() override void UpdateInput() override
{ {
// IRawGameController: // IRawGameController:
const auto button_count = UINT32(m_buttons.size()); static_assert(sizeof(bool) == sizeof(ButtonValueType));
const auto switch_count = UINT32(m_switches.size()); // This cludge is needed to workaround GetCurrentReading wanting array_view<bool>, while
const auto axis_count = UINT32(m_axes.size()); // using std::vector<bool> would create a bit-packed array, which isn't wanted. So, we keep
UINT64 timestamp = 0; // vector<u8> and view it as array<bool>.
const HRESULT raw_result = m_raw_controller->GetCurrentReading( auto buttons =
button_count, m_buttons.data(), switch_count, m_switches.data(), axis_count, m_axes.data(), winrt::array_view<bool>(reinterpret_cast<winrt::array_view<bool>::pointer>(&m_buttons[0]),
&timestamp); static_cast<winrt::array_view<bool>::size_type>(m_buttons.size()));
if (FAILED(raw_result)) try
{
m_raw_controller.GetCurrentReading(buttons, m_switches, m_axes);
}
catch (winrt::hresult_error error)
{ {
ERROR_LOG_FMT(CONTROLLERINTERFACE, ERROR_LOG_FMT(CONTROLLERINTERFACE,
"WGInput: IRawGameController::GetCurrentReading failed: {}", "WGInput: IRawGameController::GetCurrentReading failed: {:x}", error.code());
Common::HRWrap(raw_result));
} }
// IGamepad: // IGamepad:
if (m_gamepad) if (m_gamepad)
{ {
const HRESULT gamepad_result = m_gamepad->GetCurrentReading(&m_gamepad_reading); try
if (FAILED(gamepad_result))
{ {
ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: IGamepad::GetCurrentReading failed: {}", m_gamepad_reading = m_gamepad.GetCurrentReading();
Common::HRWrap(gamepad_result)); }
catch (winrt::hresult_error error)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: IGamepad::GetCurrentReading failed: {:x}",
error.code());
} }
} }
// IGameControllerBatteryInfo: // IGameControllerBatteryInfo:
if (m_controller_battery && !UpdateBatteryLevel()) if (!UpdateBatteryLevel())
{
DEBUG_LOG_FMT(CONTROLLERINTERFACE, "WGInput: UpdateBatteryLevel failed."); 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() bool UpdateBatteryLevel()
{ {
ABI::Windows::Devices::Power::IBatteryReport* report = nullptr; try
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: const winrt::Windows::Devices::Power::BatteryReport report =
m_battery_level = 0; m_raw_controller.TryGetBatteryReport();
return true; if (!report)
return false;
case BatteryStatus::BatteryStatus_Idle: // TryGetBatteryReport causes a memleak of 0x58 bytes each time it is called, up to at
case BatteryStatus::BatteryStatus_Charging: // least Windows 21H2. A hack to fix the memleak is recorded here for posterity.
m_battery_level = BATTERY_INPUT_MAX_VALUE; // auto report_ = *(uintptr_t***)&report;
return true; // auto rc = &report_[0x40 / 8][0x30 / 8];
// if (*rc == 2)
// (*rc)--;
default: using winrt::Windows::System::Power::BatteryStatus;
break; const BatteryStatus status = report.Status();
} switch (status)
{
case BatteryStatus::NotPresent:
m_battery_level = 0;
return true;
ABI::Windows::Foundation::IReference<int>*i_remaining = nullptr, *i_full = nullptr; case BatteryStatus::Idle:
int remaining_value = 0; case BatteryStatus::Charging:
int full_value = 0; m_battery_level = BATTERY_INPUT_MAX_VALUE;
return true;
if (report && SUCCEEDED(report->get_RemainingCapacityInMilliwattHours(&i_remaining)) && default:
i_remaining && SUCCEEDED(i_remaining->get_Value(&remaining_value)) && break;
SUCCEEDED(report->get_FullChargeCapacityInMilliwattHours(&i_full)) && i_full && }
SUCCEEDED(i_full->get_Value(&full_value)))
{ 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; m_battery_level = BATTERY_INPUT_MAX_VALUE * remaining_value / full_value;
return true; return true;
} }
catch (winrt::hresult_error)
return false; {
return false;
}
} }
const std::string m_name; const std::string m_name;
ComPtr<WGI::IRawGameController> const m_raw_controller; const WGI::RawGameController m_raw_controller;
std::vector<ButtonValueType> m_buttons; std::vector<ButtonValueType> m_buttons;
std::vector<WGI::GameControllerSwitchPosition> m_switches; std::vector<WGI::GameControllerSwitchPosition> m_switches;
std::vector<double> m_axes; std::vector<double> m_axes;
WGI::IGamepad* m_gamepad = nullptr; WGI::Gamepad m_gamepad = nullptr;
WGI::GamepadReading m_gamepad_reading{}; WGI::GamepadReading m_gamepad_reading{};
WGI::GamepadVibration m_state_out{}; WGI::GamepadVibration m_state_out{};
WGI::IGameControllerBatteryInfo* m_controller_battery = nullptr;
ControlState m_battery_level = 0; 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; APTTYPE apt_type{};
static Common::DynamicLibrary winrt_string_l1_1_0_handle; APTTYPEQUALIFIER apt_qualifier{};
if (!winrt_l1_1_0_handle.IsOpen() && !winrt_string_l1_1_0_handle.IsOpen()) 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"); device_name = StripWhitespace(WStringToUTF8(raw_game_controller.DisplayName().c_str()));
if (!kernelBase.IsOpen()) if (device_name.empty())
return false;
void* const IsApiSetImplemented_address = kernelBase.GetSymbolAddress("IsApiSetImplemented");
if (!IsApiSetImplemented_address)
return false;
if (!static_cast<PIsApiSetImplemented>(IsApiSetImplemented_address)(
"api-ms-win-core-winrt-l1-1-0"))
{ {
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<PIsApiSetImplemented>(IsApiSetImplemented_address)( }
"api-ms-win-core-winrt-string-l1-1-0")) catch (winrt::hresult_error)
{ {
return false; device_name = "Device";
} ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to get device name, using {}", device_name);
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");
} }
if (!winrt_l1_1_0_handle.IsOpen() || !winrt_string_l1_1_0_handle.IsOpen()) try
return false; {
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<Device>(std::move(device_name), raw_game_controller, gamepad);
g_RoInitialize_address = // Only add if it has some inputs/outputs.
static_cast<PRoInitialize>(winrt_l1_1_0_handle.GetSymbolAddress("RoInitialize")); if (dev->Inputs().size() || dev->Outputs().size())
if (!g_RoInitialize_address) g_controller_interface.AddDevice(std::move(dev));
return false; }
catch (winrt::hresult_error)
g_RoUninitialize_address = {
static_cast<PRoUninitialize>(winrt_l1_1_0_handle.GetSymbolAddress("RoUninitialize")); ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to add device {}", device_name);
if (!g_RoUninitialize_address) }
return false;
g_RoGetActivationFactory_address = static_cast<PRoGetActivationFactory>(
winrt_l1_1_0_handle.GetSymbolAddress("RoGetActivationFactory"));
if (!g_RoGetActivationFactory_address)
return false;
g_WindowsCreateStringReference_address = static_cast<PWindowsCreateStringReference>(
winrt_string_l1_1_0_handle.GetSymbolAddress("WindowsCreateStringReference"));
if (!g_WindowsCreateStringReference_address)
return false;
g_WindowsGetStringRawBuffer_address = static_cast<PWindowsGetStringRawBuffer>(
winrt_string_l1_1_0_handle.GetSymbolAddress("WindowsGetStringRawBuffer"));
if (!g_WindowsGetStringRawBuffer_address)
return false;
return true;
} }
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<const Device*>(dev)->GetRawGameController() == raw_game_controller;
});
}
#pragma warning(push)
// 'winrt::impl::implements_delegate<winrt::Windows::Foundation::EventHandler<winrt::Windows::Gaming::Input::RawGameController>,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() void Init()
{ {
if (g_runtime_initialized) if (!COMIsInitialized())
return;
if (!LoadFunctionPointers())
{ {
ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: System lacks support, skipping init."); // NOTE: Devices in g_controller_interface should only be accessed by threads that have had
return; // winrt (com) initialized.
winrt::init_apartment();
s_initialized_winrt = true;
} }
const HRESULT hr = g_RoInitialize_address(RO_INIT_MULTITHREADED); try
switch (hr)
{ {
case S_OK: // These events will be invoked from WGI-managed threadpool.
g_runtime_initialized = true; s_event_added = WGI::RawGameController::RawGameControllerAdded(
g_runtime_needs_deinit = true; [](auto&&, const WGI::RawGameController raw_game_controller) {
break; RemoveDevice(raw_game_controller);
AddDevice(raw_game_controller);
});
case S_FALSE: s_event_removed = WGI::RawGameController::RawGameControllerRemoved(
case RPC_E_CHANGED_MODE: [](auto&&, const WGI::RawGameController raw_game_controller) {
g_runtime_initialized = true; RemoveDevice(raw_game_controller);
break; });
}
default: catch (winrt::hresult_error)
ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: RoInitialize failed: {}", Common::HRWrap(hr)); {
break; ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to register event handlers");
} }
} }
#pragma warning(pop)
void DeInit() void DeInit()
{ {
if (!g_runtime_initialized) WGI::RawGameController::RawGameControllerAdded(s_event_added);
return; WGI::RawGameController::RawGameControllerRemoved(s_event_removed);
if (g_runtime_needs_deinit) if (s_initialized_winrt)
{ {
g_RoUninitialize_address(); winrt::uninit_apartment();
g_runtime_needs_deinit = false; s_initialized_winrt = false;
} }
g_runtime_initialized = false;
}
template <unsigned int size>
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() void PopulateDevices()
{ {
if (!g_runtime_initialized)
return;
g_controller_interface.RemoveDevice(
[](const auto* dev) { return dev->GetSource() == SOURCE_NAME; });
// WGI Interfaces to potentially use: // WGI Interfaces to potentially use:
// Gamepad: Buttons, 2x Sticks and 2x Triggers, 4x Vibration Motors // Gamepad: Buttons, 2x Sticks and 2x Triggers, 4x Vibration Motors
// RawGameController: Buttons, Switches (Hats), Axes, Haptics // RawGameController: Buttons, Switches (Hats), Axes, Haptics
@ -719,96 +716,23 @@ void PopulateDevices()
// RacingWheel: Buttons, Clutch, Handbrake, PatternShifterGear, Throttle, Wheel, WheelMotor // RacingWheel: Buttons, Clutch, Handbrake, PatternShifterGear, Throttle, Wheel, WheelMotor
// UINavigationController: Directions, Scrolling, etc. // UINavigationController: Directions, Scrolling, etc.
HSTRING_HEADER header_raw_game_controller; // RawGameController available from Windows 15063.
HSTRING hstr_raw_game_controller; // properties added in 1709: DisplayName, NonRoamableId, SimpleHapticsControllers
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;
}
HSTRING_HEADER header_gamepad; try
HSTRING hstr_gamepad;
if (FAILED(hr = WindowsCreateStringReferenceAutoSizeWrapper(L"Windows.Gaming.Input.Gamepad",
&header_gamepad, &hstr_gamepad)))
{ {
ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to create string reference: {}", for (const WGI::RawGameController& raw_game_controller :
Common::HRWrap(hr)); WGI::RawGameController::RawGameControllers())
return;
}
ComPtr<WGI::IRawGameControllerStatics> 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<WGI::IGamepadStatics2> 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<WGI::RawGameController*>* 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<WGI::IRawGameController> raw_controller;
if (SUCCEEDED(raw_controllers->GetAt(i, &raw_controller)) && raw_controller)
{ {
std::string device_name; RemoveDevice(raw_game_controller);
AddDevice(raw_game_controller);
// 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<Device>(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));
} }
} }
catch (winrt::hresult_error)
{
// Only reach here if RawGameControllers() failed
ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: PopulateDevices failed");
}
} }
} // namespace ciface::WGInput } // namespace ciface::WGInput

View File

@ -3,35 +3,38 @@
#include "InputCommon/ControllerInterface/Win32/Win32.h" #include "InputCommon/ControllerInterface/Win32/Win32.h"
#include <windows.h> #include <Windows.h>
#include <cfgmgr32.h>
// must be before hidclass
#include <initguid.h>
#include <hidclass.h>
#include <array>
#include <future>
#include <mutex> #include <mutex>
#include <thread>
#include "Common/Flag.h" #include "Common/Flag.h"
#include "Common/HRWrap.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Common/Thread.h"
#include "InputCommon/ControllerInterface/DInput/DInput.h" #include "InputCommon/ControllerInterface/DInput/DInput.h"
#include "InputCommon/ControllerInterface/WGInput/WGInput.h" #include "InputCommon/ControllerInterface/WGInput/WGInput.h"
#include "InputCommon/ControllerInterface/XInput/XInput.h" #include "InputCommon/ControllerInterface/XInput/XInput.h"
constexpr UINT WM_DOLPHIN_STOP = WM_USER; #pragma comment(lib, "OneCoreUAP.Lib")
// Dolphin's render window // Dolphin's render window
static HWND s_hwnd; static HWND s_hwnd;
// Windows messaging window (hidden)
static HWND s_message_window;
static std::thread s_thread;
static std::mutex s_populate_mutex; static std::mutex s_populate_mutex;
// TODO is this really needed?
static Common::Flag s_first_populate_devices_asked; 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 // Windows automatically sends this message before we ask for it and before we are "ready" to
// listen for it. // listen for it.
@ -43,147 +46,63 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARA
g_controller_interface.PlatformPopulateDevices([] { g_controller_interface.PlatformPopulateDevices([] {
ciface::DInput::PopulateDevices(s_hwnd); ciface::DInput::PopulateDevices(s_hwnd);
ciface::XInput::PopulateDevices(); ciface::XInput::PopulateDevices();
ciface::WGInput::PopulateDevices();
}); });
} }
} }
return ERROR_SUCCESS;
return DefWindowProc(hwnd, message, wparam, lparam);
} }
void ciface::Win32::Init(void* hwnd) void ciface::Win32::Init(void* hwnd)
{ {
s_hwnd = static_cast<HWND>(hwnd); s_hwnd = static_cast<HWND>(hwnd);
XInput::Init(); XInput::Init();
WGInput::Init(); WGInput::Init();
std::promise<HWND> message_window_promise; CM_NOTIFY_FILTER notify_filter{.cbSize = sizeof(notify_filter),
.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE,
s_thread = std::thread([&message_window_promise] { .u{.DeviceInterface{.ClassGuid = GUID_DEVINTERFACE_HID}}};
Common::SetCurrentThreadName("ciface::Win32 Message Loop"); const CONFIGRET cfg_rv =
CM_Register_Notification(&notify_filter, nullptr, OnDevicesChanged, &s_notify_handle);
HWND message_window = nullptr; if (cfg_rv != CR_SUCCESS)
Common::ScopeGuard promise_guard([&] { message_window_promise.set_value(message_window); }); {
ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Register_Notification failed: {:x}", cfg_rv);
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<RAWINPUTDEVICE, 2> 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<UINT>(devices.size()),
static_cast<UINT>(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();
} }
void ciface::Win32::PopulateDevices(void* hwnd) void ciface::Win32::PopulateDevices(void* hwnd)
{ {
if (s_thread.joinable()) s_hwnd = static_cast<HWND>(hwnd);
{ std::lock_guard lk_population(s_populate_mutex);
s_hwnd = static_cast<HWND>(hwnd); s_first_populate_devices_asked.Set();
// To avoid blocking this thread until the async population has finished, directly do it here ciface::DInput::PopulateDevices(s_hwnd);
// (we need the DInput Keyboard and Mouse "default" device to always be added without any wait). ciface::XInput::PopulateDevices();
std::lock_guard lk_population(s_populate_mutex); ciface::WGInput::PopulateDevices();
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");
}
} }
void ciface::Win32::ChangeWindow(void* hwnd) void ciface::Win32::ChangeWindow(void* hwnd)
{ {
if (s_thread.joinable()) // "Has init?" s_hwnd = static_cast<HWND>(hwnd);
{ std::lock_guard lk_population(s_populate_mutex);
s_hwnd = static_cast<HWND>(hwnd); ciface::DInput::ChangeWindow(s_hwnd);
std::lock_guard lk_population(s_populate_mutex);
ciface::DInput::ChangeWindow(s_hwnd);
}
} }
void ciface::Win32::DeInit() void ciface::Win32::DeInit()
{ {
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "win32 DeInit"); s_first_populate_devices_asked.Clear();
if (s_thread.joinable()) DInput::DeInit();
{
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_hwnd = nullptr; 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(); XInput::DeInit();
WGInput::DeInit(); WGInput::DeInit();
} }