dolphin/Source/Core/InputCommon/ControllerInterface/WGInput/WGInput.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

616 lines
20 KiB
C++
Raw Normal View History

// Copyright 2020 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "InputCommon/ControllerInterface/WGInput/WGInput.h"
#include <array>
2022-08-08 09:17:48 +00:00
#include <map>
#include <string_view>
#include <fmt/format.h>
2022-08-08 09:17:48 +00:00
// TODO winrt translates com failures to c++ exceptions, so we must use try/catch in this file to
// prevent possible errors from terminating Dolphin.
#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 "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
2022-08-08 09:17:48 +00:00
namespace WGI = winrt::Windows::Gaming::Input;
namespace Haptics = winrt::Windows::Devices::Haptics;
using winrt::Windows::Foundation::Collections::IVectorView;
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
2022-08-08 09:17:48 +00:00
// 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 <typename T, typename M>
struct MemberName
{
M T::*ptr;
const char* name;
};
static constexpr MemberName<WGI::GamepadReading, double> gamepad_trigger_names[] = {
{&WGI::GamepadReading::LeftTrigger, "Trigger L"},
{&WGI::GamepadReading::RightTrigger, "Trigger R"}};
static constexpr MemberName<WGI::GamepadReading, double> 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<WGI::GamepadVibration, double> 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:
2022-08-08 09:17:48 +00:00
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:
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:
2022-08-08 09:17:48 +00:00
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;
}
}
// 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)
{
2022-08-08 09:17:48 +00:00
m_switches.resize(m_raw_controller.SwitchCount());
u32 i = 0;
for (auto& swtch : m_switches)
{
using gcsp = WGI::GameControllerSwitchPosition;
2022-08-08 09:17:48 +00:00
WGI::GameControllerSwitchKind switch_kind = m_raw_controller.GetSwitchKind(i);
2022-08-08 09:17:48 +00:00
AddInput(new IndexedSwitch(&swtch, i, gcsp::Up));
AddInput(new IndexedSwitch(&swtch, i, gcsp::Down));
2022-08-08 09:17:48 +00:00
if (switch_kind != WGI::GameControllerSwitchKind::TwoWay)
{
// If it's not a "two-way" switch (up/down only) then add the left/right inputs.
2022-08-08 09:17:48 +00:00
AddInput(new IndexedSwitch(&swtch, i, gcsp::Left));
AddInput(new IndexedSwitch(&swtch, i, gcsp::Right));
}
++i;
}
}
// Haptics:
PopulateHaptics();
// Battery:
2022-08-08 09:17:48 +00:00
if (UpdateBatteryLevel())
AddInput(new Battery(&m_battery_level));
}
int GetSortPriority() const override { return -1; }
2022-08-08 09:17:48 +00:00
const WGI::RawGameController GetRawGameController() const { return m_raw_controller; }
private:
2022-08-08 09:17:48 +00:00
using ButtonValueType = u8;
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)
2022-08-08 09:17:48 +00:00
: m_switch(*swtch), m_index(index), m_direction(static_cast<int32_t>(direction))
{
}
std::string GetName() const override
{
return fmt::format("Switch {} {}", m_index, "NESW"[m_direction / 2]);
}
ControlState GetState() const override
{
2022-08-08 09:17:48 +00:00
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.
2022-08-08 09:17:48 +00:00
const auto direction_diff = std::abs(static_cast<int32_t>(m_switch) - m_direction);
return ControlState(direction_diff <= 1 || direction_diff == 7);
}
private:
const WGI::GameControllerSwitchPosition& m_switch;
const u32 m_index;
2022-08-08 09:17:48 +00:00
const int32_t 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:
2022-08-08 09:17:48 +00:00
SimpleHaptics(Haptics::SimpleHapticsController haptics,
Haptics::SimpleHapticsControllerFeedback 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)
2022-08-08 09:17:48 +00:00
m_haptics.SendHapticFeedback(m_feedback, state);
else
2022-08-08 09:17:48 +00:00
m_haptics.StopFeedback();
}
protected:
u32 GetHapticsIndex() const { return m_haptics_index; }
private:
2022-08-08 09:17:48 +00:00
Haptics::SimpleHapticsController m_haptics;
Haptics::SimpleHapticsControllerFeedback m_feedback;
const u32 m_haptics_index;
ControlState m_current_state = 0;
};
class NamedFeedback final : public SimpleHaptics
{
public:
2022-08-08 09:17:48 +00:00
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)
{
}
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.
2022-08-08 09:17:48 +00:00
m_buttons.resize(m_raw_controller.ButtonCount());
u32 i = 0;
2022-08-08 09:17:48 +00:00
for (const auto& button : m_buttons)
{
2022-08-08 09:17:48 +00:00
const WGI::GameControllerButtonLabel lbl = m_raw_controller.GetButtonLabel(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;
}
}
void PopulateHaptics()
{
2022-08-08 09:17:48 +00:00
static const std::map<uint16_t, std::string> waveform_name_map{
{Haptics::KnownSimpleHapticsControllerWaveforms::Click(), "Click"},
{Haptics::KnownSimpleHapticsControllerWaveforms::BuzzContinuous(), "Buzz"},
{Haptics::KnownSimpleHapticsControllerWaveforms::RumbleContinuous(), "Rumble"},
};
// SimpleHapticsControllers is available from Windows 1709.
u32 haptics_index = 0;
for (auto haptics_controller : m_raw_controller.SimpleHapticsControllers())
{
2022-08-08 09:17:48 +00:00
for (Haptics::SimpleHapticsControllerFeedback feedback :
haptics_controller.SupportedFeedback())
{
2022-08-08 09:17:48 +00:00
const uint16_t waveform = feedback.Waveform();
auto waveform_name_it = waveform_name_map.find(waveform);
if (waveform_name_it == waveform_name_map.end())
{
2022-08-08 09:17:48 +00:00
WARN_LOG_FMT(CONTROLLERINTERFACE,
"WGInput: Unhandled haptics feedback waveform: 0x{:04x}.", waveform);
continue;
}
2022-08-08 09:17:48 +00:00
AddOutput(new NamedFeedback(haptics_controller, feedback, haptics_index,
waveform_name_it->second));
}
2022-08-08 09:17:48 +00:00
++haptics_index;
}
}
std::string GetName() const override { return m_name; }
std::string GetSource() const override { return std::string(SOURCE_NAME); }
void UpdateInput() override
{
// IRawGameController:
2022-08-08 09:17:48 +00:00
static_assert(sizeof(bool) == sizeof(ButtonValueType));
// This cludge is needed to workaround GetCurrentReading wanting array_view<bool>, while
// using std::vector<bool> would create a bit-packed array, which isn't wanted. So, we keep
// vector<u8> and view it as array<bool>.
auto buttons =
winrt::array_view<bool>(reinterpret_cast<winrt::array_view<bool>::pointer>(&m_buttons[0]),
static_cast<winrt::array_view<bool>::size_type>(m_buttons.size()));
m_raw_controller.GetCurrentReading(buttons, m_switches, m_axes);
// IGamepad:
if (m_gamepad)
2022-08-08 09:17:48 +00:00
m_gamepad_reading = m_gamepad.GetCurrentReading();
// IGameControllerBatteryInfo:
2022-08-08 09:17:48 +00:00
if (!UpdateBatteryLevel())
DEBUG_LOG_FMT(CONTROLLERINTERFACE, "WGInput: UpdateBatteryLevel failed.");
}
2022-08-08 09:17:48 +00:00
void UpdateMotors() { m_gamepad.Vibration(m_state_out); }
bool UpdateBatteryLevel()
{
2022-08-08 09:17:48 +00:00
const winrt::Windows::Devices::Power::BatteryReport report =
m_raw_controller.TryGetBatteryReport();
if (!report)
return false;
2022-08-08 09:17:48 +00:00
// TryGetBatteryReport causes a memleak of 0x58 bytes each time it is called, up to at
// least Windows 21H2. This is a big hack to "fix" the refcount.
auto report_ = *(uintptr_t***)&report;
auto rc = &report_[0x40 / 8][0x30 / 8];
if (*rc == 2)
(*rc)--;
2022-08-08 09:17:48 +00:00
using winrt::Windows::System::Power::BatteryStatus;
const BatteryStatus status = report.Status();
switch (status)
{
2022-08-08 09:17:48 +00:00
case BatteryStatus::NotPresent:
m_battery_level = 0;
return true;
2022-08-08 09:17:48 +00:00
case BatteryStatus::Idle:
case BatteryStatus::Charging:
m_battery_level = BATTERY_INPUT_MAX_VALUE;
return true;
default:
break;
}
2022-08-08 09:17:48 +00:00
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;
}
const std::string m_name;
2022-08-08 09:17:48 +00:00
const WGI::RawGameController m_raw_controller;
std::vector<ButtonValueType> m_buttons;
std::vector<WGI::GameControllerSwitchPosition> m_switches;
std::vector<double> m_axes;
2022-08-08 09:17:48 +00:00
WGI::Gamepad m_gamepad = nullptr;
WGI::GamepadReading m_gamepad_reading{};
WGI::GamepadVibration m_state_out{};
ControlState m_battery_level = 0;
};
2022-08-08 09:17:48 +00:00
static thread_local bool s_initialized_winrt;
static winrt::event_token s_event_added, s_event_removed;
static void AddDevice(const WGI::RawGameController& raw_game_controller)
{
2022-08-08 09:17:48 +00:00
// Get user-facing device name if available. Otherwise generate a name from vid/pid.
auto device_name =
std::string(StripWhitespace(WStringToUTF8(raw_game_controller.DisplayName().c_str())));
if (device_name.empty())
{
2022-08-08 09:17:48 +00:00
const u16 vid = raw_game_controller.HardwareVendorId();
const u16 pid = raw_game_controller.HardwareProductId();
device_name = fmt::format("Device_{:04x}:{:04x}", vid, pid);
ERROR_LOG_FMT(CONTROLLERINTERFACE, "WGInput: Failed to get device name, using {}", device_name);
}
2022-08-08 09:17:48 +00:00
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);
2022-08-08 09:17:48 +00:00
// Only add if it has some inputs/outputs.
if (dev->Inputs().size() || dev->Outputs().size())
g_controller_interface.AddDevice(std::move(dev));
}
2022-08-08 09:17:48 +00:00
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;
});
}
2022-08-08 09:17:48 +00:00
#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()
{
2022-08-08 09:17:48 +00:00
try
{
2022-08-08 09:17:48 +00:00
// TODO currently, com gets initialized in STA previously on the thread that calls this
// function. need to ensure Devices in g_controller_interface only get accessed by threads that
// have had com initialized.
// winrt::init_apartment();
// s_initialized_winrt = true;
}
2022-08-08 09:17:48 +00:00
catch (...)
{
2022-08-08 09:17:48 +00:00
s_initialized_winrt = false;
}
2022-08-08 09:17:48 +00:00
// XXX If SDL is enabled, these get broken after the first one is fired.
// 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);
});
s_event_removed = WGI::RawGameController::RawGameControllerRemoved(
[](auto&&, const WGI::RawGameController raw_game_controller) {
RemoveDevice(raw_game_controller);
});
}
2022-08-08 09:17:48 +00:00
#pragma warning(pop)
void DeInit()
{
2022-08-08 09:17:48 +00:00
WGI::RawGameController::RawGameControllerAdded(s_event_added);
WGI::RawGameController::RawGameControllerRemoved(s_event_removed);
2022-08-08 09:17:48 +00:00
if (s_initialized_winrt)
{
2022-08-08 09:17:48 +00:00
winrt::uninit_apartment();
s_initialized_winrt = false;
}
}
void PopulateDevices()
{
// 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.
2022-08-08 09:17:48 +00:00
// RawGameController available from Windows 15063.
// properties added in 1709: DisplayName, NonRoamableId, SimpleHapticsControllers
2022-08-08 09:17:48 +00:00
for (const WGI::RawGameController& raw_game_controller :
WGI::RawGameController::RawGameControllers())
{
2022-08-08 09:17:48 +00:00
RemoveDevice(raw_game_controller);
AddDevice(raw_game_controller);
}
}
} // namespace ciface::WGInput