Qt: Refactor LED bindings

Make it more general and bindable to both the mode and RGB LED, that way
it can be used on Dualshock 4 as well.
This commit is contained in:
Stenzek
2025-09-30 18:35:32 +10:00
parent 51942df7dd
commit ffa0e8d131
34 changed files with 486 additions and 486 deletions

View File

@@ -29,10 +29,7 @@ AnalogController::AnalogController(u32 index) : Controller(index)
m_rumble_config.fill(0xFF);
}
AnalogController::~AnalogController()
{
InputManager::SetPadModeLED(m_index, false);
}
AnalogController::~AnalogController() = default;
ControllerType AnalogController::GetType() const
{
@@ -183,6 +180,11 @@ float AnalogController::GetVibrationMotorState(u32 index) const
return (m_motor_state[SmallMotor] > 0) ? 1.0f : 0.0f;
}
float AnalogController::GetLEDState(u32 index) const
{
return BoolToFloat(index == 0 && m_analog_mode);
}
void AnalogController::SetBindState(u32 index, float value)
{
if (index == static_cast<s32>(Button::Analog))
@@ -342,7 +344,7 @@ void AnalogController::SetAnalogMode(bool enabled, bool show_message)
m_analog_mode = enabled;
InputManager::SetPadModeLED(m_index, enabled);
InputManager::SetPadLEDState(m_index, BoolToFloat(enabled));
INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
if (show_message)
@@ -790,8 +792,8 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
genb}
#define MOTOR(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::Motor, genb}
#define MODE_LED(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::ModeLED, genb}
#define MODE_LED(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::LED, genb}
// clang-format off
BUTTON("Up", TRANSLATE_NOOP("AnalogController", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogController::Button::Up, GenericInputBinding::DPadUp),
@@ -824,7 +826,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
MOTOR("LargeMotor", TRANSLATE_NOOP("AnalogController", "Large Motor"), ICON_PF_VIBRATION_L, 0, GenericInputBinding::LargeMotor),
MOTOR("SmallMotor", TRANSLATE_NOOP("AnalogController", "Small Motor"), ICON_PF_VIBRATION, 1, GenericInputBinding::SmallMotor),
MODE_LED("ModeLED", TRANSLATE_NOOP("AnalogController", "Mode LED"), ICON_PF_ANALOG_LEFT_RIGHT, 0, GenericInputBinding::ModeLED),
MODE_LED("AnalogLED", TRANSLATE_NOOP("AnalogController", "Analog LED"), ICON_PF_ANALOG_LEFT_RIGHT, 0, GenericInputBinding::ModeLED),
// clang-format on
#undef MOTOR

View File

@@ -73,6 +73,7 @@ public:
float GetBindState(u32 index) const override;
float GetVibrationMotorState(u32 index) const override;
float GetLEDState(u32 index) const override;
void SetBindState(u32 index, float value) override;
u32 GetButtonStateBits() const override;
std::optional<u32> GetAnalogInputBytes() const override;

View File

@@ -26,10 +26,7 @@ AnalogJoystick::AnalogJoystick(u32 index) : Controller(index)
Reset();
}
AnalogJoystick::~AnalogJoystick()
{
InputManager::SetPadModeLED(m_index, false);
}
AnalogJoystick::~AnalogJoystick() = default;
ControllerType AnalogJoystick::GetType() const
{
@@ -44,7 +41,8 @@ bool AnalogJoystick::InAnalogMode() const
void AnalogJoystick::Reset()
{
m_transfer_state = TransferState::Idle;
InputManager::SetPadModeLED(m_index, m_analog_mode);
m_analog_mode = true;
InputManager::SetPadLEDState(m_index, 1.0f);
}
bool AnalogJoystick::DoState(StateWrapper& sw, bool apply_input_state)
@@ -245,7 +243,7 @@ void AnalogJoystick::ToggleAnalogMode()
{
m_analog_mode = !m_analog_mode;
InputManager::SetPadModeLED(m_index, m_analog_mode);
InputManager::SetPadLEDState(m_index, BoolToFloat(m_analog_mode));
INFO_LOG("Joystick {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
Host::AddIconOSDMessage(
@@ -357,7 +355,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
InputBindingInfo::Type::HalfAxis, \
genb}
#define MODE_LED(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::ModeLED, genb}
{name, display_name, icon_name, index, InputBindingInfo::Type::LED, genb}
// clang-format off
BUTTON("Up", TRANSLATE_NOOP("AnalogJoystick", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogJoystick::Button::Up, GenericInputBinding::DPadUp),

View File

@@ -96,6 +96,11 @@ float Controller::GetVibrationMotorState(u32 index) const
return 0.0f;
}
float Controller::GetLEDState(u32 index) const
{
return 0.0f;
}
bool Controller::InAnalogMode() const
{
return false;

View File

@@ -80,6 +80,9 @@ public:
/// Returns the current state of the specified vibration motor.
virtual float GetVibrationMotorState(u32 index) const;
/// Returns the current state of the specified LED.
virtual float GetLEDState(u32 index) const;
/// Returns true if the controller supports analog mode, and it is active.
virtual bool InAnalogMode() const;

View File

@@ -382,8 +382,8 @@ static void PopulateGraphicsAdapterList();
static void PopulateGameListDirectoryCache(SettingsInterface* si);
static void PopulatePatchesAndCheatsList();
static void PopulatePostProcessingChain(SettingsInterface* si, const char* section);
static void BeginVibrationMotorBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
const char* key, std::string_view display_name);
static void BeginEffectBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
const char* key, std::string_view display_name);
static void DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
const char* name, std::string_view display_name, std::string_view icon_name,
bool show_type = true);
@@ -2478,6 +2478,9 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingIn
case InputBindingInfo::Type::Motor:
title.format(ICON_FA_BELL " {}", display_name);
break;
case InputBindingInfo::Type::LED:
title.format(ICON_FA_LIGHTBULB " {}", display_name);
break;
case InputBindingInfo::Type::Macro:
title.format(ICON_FA_PIZZA_SLICE " {}", display_name);
break;
@@ -2512,8 +2515,8 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingIn
if (clicked)
{
if (type == InputBindingInfo::Type::Motor)
BeginVibrationMotorBinding(bsi, type, section, name, display_name);
if (type == InputBindingInfo::Type::Motor || type == InputBindingInfo::Type::LED)
BeginEffectBinding(bsi, type, section, name, display_name);
else
s_state.input_binding_dialog.Start(bsi, type, section, name, display_name);
}
@@ -2672,46 +2675,49 @@ void FullscreenUI::InputBindingDialog::Draw()
EndRender();
}
void FullscreenUI::BeginVibrationMotorBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
const char* key, std::string_view display_name)
void FullscreenUI::BeginEffectBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
const char* key, std::string_view display_name)
{
// vibration motors use a list to select
const bool game_settings = IsEditingGameSettings(bsi);
InputManager::VibrationMotorList motors = InputManager::EnumerateVibrationMotors();
if (motors.empty())
const InputManager::DeviceEffectList effects = InputManager::EnumerateDeviceEffects(type);
if (effects.empty())
{
ShowToast({}, FSUI_STR("No devices with vibration motors were detected."));
return;
}
const TinyString current_binding = bsi->GetTinyStringValue(section, key);
size_t current_index = motors.size();
size_t current_index = effects.size();
ChoiceDialogOptions options;
options.reserve(motors.size() + 1);
for (size_t i = 0; i < motors.size(); i++)
options.reserve(effects.size() + 1);
for (size_t i = 0; i < effects.size(); i++)
{
const TinyString text = InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, motors[i]);
const TinyString text = InputManager::ConvertInputBindingKeyToString(effects[i].first, effects[i].second);
const bool this_index = (current_binding.view() == text);
current_index = this_index ? i : current_index;
options.emplace_back(text, this_index);
}
// empty/no mapping value
options.emplace_back(FSUI_STR("No Vibration"), current_binding.empty());
if (type == InputBindingInfo::Type::Motor)
options.emplace_back(FSUI_STR("No Vibration"), current_binding.empty());
else if (type == InputBindingInfo::Type::LED)
options.emplace_back(FSUI_STR("No LED"), current_binding.empty());
// add current value to list if it's not currently available
if (!current_binding.empty() && current_index == motors.size())
if (!current_binding.empty() && current_index == effects.size())
options.emplace_back(std::make_pair(std::string(current_binding.view()), true));
OpenChoiceDialog(display_name, false, std::move(options),
[game_settings, section = std::string(section), key = std::string(key),
motors = std::move(motors)](s32 index, const std::string& title, bool checked) {
effects = std::move(effects)](s32 index, const std::string& title, bool checked) {
if (index < 0)
return;
auto lock = Host::GetSettingsLock();
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
if (static_cast<size_t>(index) == motors.size())
if (static_cast<size_t>(index) == effects.size())
bsi->DeleteValue(section.c_str(), key.c_str());
else
bsi->SetStringValue(section.c_str(), key.c_str(), title.c_str());
@@ -4866,12 +4872,7 @@ void FullscreenUI::DrawControllerSettingsPage()
"SDLControllerEnhancedMode", false, bsi->GetBoolValue("InputSources", "SDL", true), false);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIGHTBULB, "SDL DualSense Player LED"),
FSUI_VSTR("Enable/Disable the Player LED on DualSense controllers."), "InputSources",
"SDLPS5PlayerLED", false, bsi->GetBoolValue("InputSources", "SDLControllerEnhancedMode", true),
false);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIGHTBULB, "SDL DualSense Mic Mute LED for Analog Mode"),
FSUI_VSTR("Enable/Disable using the DualSense controller's Mic Mute LED to indicate when Analog Mode is active."), "InputSources",
"SDLPS5MicMuteLEDForAnalogMode", false, bsi->GetBoolValue("InputSources", "SDLControllerEnhancedMode", true),
false);
"SDLPS5PlayerLED", false, bsi->GetBoolValue("InputSources", "SDL", true), false);
#ifdef _WIN32
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_GEAR, "Enable XInput Input Source"),
FSUI_VSTR("Support for controllers that use the XInput protocol. XInput should only be used if you "

View File

@@ -15,13 +15,15 @@ struct InputBindingInfo
Axis,
HalfAxis,
Motor,
ModeLED,
LED,
Pointer, // Absolute pointer, does not receive any events, but is queryable.
RelativePointer, // Receive relative mouse movement events, bind_index is offset by the axis.
Device, // Used for special-purpose device selection, e.g. force feedback.
Macro,
};
ALWAYS_INLINE static bool IsEffectType(Type type) { return (type >= Type::Motor && type <= Type::LED); }
const char* name;
const char* display_name;
Type bind_type;

View File

@@ -24,32 +24,17 @@ JogCon::JogCon(u32 index) : Controller(index)
{
}
JogCon::~JogCon()
{
InputManager::SetPadModeLED(m_index, false);
}
JogCon::~JogCon() = default;
ControllerType JogCon::GetType() const
{
return ControllerType::JogCon;
}
bool JogCon::InAnalogMode() const
{
// JogCon uses JogCon mode
return InJogConMode();
}
bool JogCon::InJogConMode() const
{
return m_jogcon_mode;
}
void JogCon::Reset()
{
// Reset starts in jogcon mode?
m_jogcon_mode = true;
InputManager::SetPadModeLED(m_index, m_jogcon_mode);
SetJogConMode(true, false);
ResetTransferState();
ResetMotorConfig();
}
@@ -199,7 +184,7 @@ void JogCon::SetJogConMode(bool enabled, bool show_message)
m_jogcon_mode = enabled;
m_configuration_mode = enabled && m_configuration_mode;
InputManager::SetPadModeLED(m_index, enabled);
InputManager::SetPadLEDState(m_index, BoolToFloat(enabled));
INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_jogcon_mode ? "JogCon" : "Digital");
if (show_message)
@@ -642,7 +627,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
InputBindingInfo::Type::HalfAxis, \
genb}
#define MODE_LED(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::ModeLED, genb}
{name, display_name, icon_name, index, InputBindingInfo::Type::LED, genb}
// clang-format off
BUTTON("Up", TRANSLATE_NOOP("JogCon", "D-Pad Up"), ICON_PF_DPAD_UP, JogCon::Button::Up, GenericInputBinding::DPadUp),

View File

@@ -49,8 +49,6 @@ public:
static std::unique_ptr<JogCon> Create(u32 index);
ControllerType GetType() const override;
bool InAnalogMode() const override;
bool InJogConMode() const;
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;

View File

@@ -5,7 +5,6 @@
#include "host.h"
#include "system.h"
#include "util/input_manager.h"
#include "util/state_wrapper.h"
#include "common/assert.h"
@@ -27,26 +26,16 @@ NeGcon::NeGcon(u32 index) : Controller(index)
m_axis_state[static_cast<u8>(Axis::Steering)] = 0x80;
}
NeGcon::~NeGcon()
{
InputManager::SetPadModeLED(m_index, false);
}
NeGcon::~NeGcon() = default;
ControllerType NeGcon::GetType() const
{
return ControllerType::NeGcon;
}
bool NeGcon::InAnalogMode() const
{
// NeGcon is always analog
return true;
}
void NeGcon::Reset()
{
m_transfer_state = TransferState::Idle;
InputManager::SetPadModeLED(m_index, true);
}
bool NeGcon::DoState(StateWrapper& sw, bool apply_input_state)
@@ -281,8 +270,6 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
static_cast<u32>(NeGcon::Button::Count) + static_cast<u32>(halfaxis), \
InputBindingInfo::Type::HalfAxis, \
genb}
#define MODE_LED(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::ModeLED, genb}
// clang-format off
BUTTON("Up", TRANSLATE_NOOP("NeGcon", "D-Pad Up"), ICON_PF_DPAD_UP, NeGcon::Button::Up, GenericInputBinding::DPadUp),
@@ -298,13 +285,10 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
BUTTON("R", TRANSLATE_NOOP("NeGcon", "Right Trigger"), ICON_PF_RIGHT_TRIGGER_RT, NeGcon::Button::R, GenericInputBinding::R1),
AXIS("SteeringLeft", TRANSLATE_NOOP("NeGcon", "Steering (Twist) Left"), ICON_PF_ANALOG_LEFT, NeGcon::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft),
AXIS("SteeringRight", TRANSLATE_NOOP("NeGcon", "Steering (Twist) Right"), ICON_PF_ANALOG_RIGHT, NeGcon::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight),
MODE_LED("ModeLED", TRANSLATE_NOOP("NeGcon", "Mode LED"), ICON_PF_ANALOG_LEFT_RIGHT, 0, GenericInputBinding::ModeLED),
// clang-format on
#undef AXIS
#undef BUTTON
#undef MODE_LED
};
static const SettingInfo s_settings[] = {

View File

@@ -87,7 +87,6 @@ public:
static std::unique_ptr<NeGcon> Create(u32 index);
ControllerType GetType() const override;
bool InAnalogMode() const override;
void Reset() override;
bool DoState(StateWrapper& sw, bool apply_input_state) override;

View File

@@ -36,10 +36,7 @@ NeGconRumble::NeGconRumble(u32 index) : Controller(index)
m_rumble_config.fill(0xFF);
}
NeGconRumble::~NeGconRumble()
{
InputManager::SetPadModeLED(m_index, false);
}
NeGconRumble::~NeGconRumble() = default;
ControllerType NeGconRumble::GetType() const
{
@@ -238,7 +235,7 @@ void NeGconRumble::SetAnalogMode(bool enabled, bool show_message)
if (m_analog_mode == enabled)
return;
InputManager::SetPadModeLED(m_index, enabled);
InputManager::SetPadLEDState(m_index, BoolToFloat(enabled));
INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
if (show_message)
@@ -731,7 +728,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
#define MOTOR(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::Motor, genb}
#define MODE_LED(name, display_name, icon_name, index, genb) \
{name, display_name, icon_name, index, InputBindingInfo::Type::ModeLED, genb}
{name, display_name, icon_name, index, InputBindingInfo::Type::LED, genb}
// clang-format off
BUTTON("Up", TRANSLATE_NOOP("NeGconRumble", "D-Pad Up"), ICON_PF_DPAD_UP, NeGconRumble::Button::Up, GenericInputBinding::DPadUp),

View File

@@ -2037,7 +2037,7 @@ void System::DestroySystem()
Host::ClearOSDMessages(true);
});
InputManager::PauseVibration();
InputManager::ClearEffects();
InputManager::UpdateHostMouseMode();
if (g_settings.inhibit_screensaver)

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1202</width>
<height>646</height>
<width>1100</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
@@ -394,7 +394,7 @@
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputVibrationBindingWidget" name="LargeMotor">
<widget class="InputBindingWidget" name="LargeMotor">
<property name="minimumSize">
<size>
<width>100</width>
@@ -997,7 +997,7 @@
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputVibrationBindingWidget" name="SmallMotor">
<widget class="InputBindingWidget" name="SmallMotor">
<property name="minimumSize">
<size>
<width>100</width>
@@ -1090,59 +1090,6 @@
</item>
<item row="2" column="1">
<layout class="QGridLayout" name="gridLayout_32">
<item row="0" column="3">
<widget class="QGroupBox" name="groupBox_28">
<property name="title">
<string>R3</string>
</property>
<layout class="QGridLayout" name="gridLayout_29">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="R3">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="4">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_31">
<property name="title">
@@ -1205,6 +1152,81 @@
</layout>
</widget>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="groupBox_28">
<property name="title">
<string>R3</string>
</property>
<layout class="QGridLayout" name="gridLayout_29">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="R3">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="4">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_33">
<property name="title">
<string>Analog LED</string>
</property>
<layout class="QGridLayout" name="gridLayout_34">
<item row="0" column="0">
<widget class="InputBindingWidget" name="AnalogLED">
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
@@ -1215,11 +1237,6 @@
<extends>QPushButton</extends>
<header>inputbindingwidgets.h</header>
</customwidget>
<customwidget>
<class>InputVibrationBindingWidget</class>
<extends>QPushButton</extends>
<header>inputbindingwidgets.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="resources/duckstation-qt.qrc"/>

View File

@@ -739,7 +739,7 @@
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputVibrationBindingWidget" name="LargeMotor">
<widget class="InputBindingWidget" name="LargeMotor">
<property name="minimumSize">
<size>
<width>100</width>
@@ -779,7 +779,7 @@
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputVibrationBindingWidget" name="SmallMotor">
<widget class="InputBindingWidget" name="SmallMotor">
<property name="minimumSize">
<size>
<width>100</width>
@@ -811,7 +811,7 @@
<header>inputbindingwidgets.h</header>
</customwidget>
<customwidget>
<class>InputVibrationBindingWidget</class>
<class>InputEffectBindingWidget</class>
<extends>QPushButton</extends>
<header>inputbindingwidgets.h</header>
</customwidget>

View File

@@ -499,7 +499,8 @@ void ControllerBindingWidget::createBindingWidgets(QWidget* parent)
{
if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis ||
bi.type == InputBindingInfo::Type::Pointer || bi.type == InputBindingInfo::Type::RelativePointer ||
bi.type == InputBindingInfo::Type::Device || bi.type == InputBindingInfo::Type::Motor)
bi.type == InputBindingInfo::Type::Device || bi.type == InputBindingInfo::Type::Motor ||
bi.type == InputBindingInfo::Type::LED)
{
if (!axis_gbox)
{
@@ -507,14 +508,10 @@ void ControllerBindingWidget::createBindingWidgets(QWidget* parent)
axis_layout = new QGridLayout(axis_gbox);
}
QGroupBox* gbox =
QGroupBox* const gbox =
new QGroupBox(QtUtils::StringViewToQString(m_controller_info->GetBindingDisplayName(bi)), axis_gbox);
QVBoxLayout* temp = new QVBoxLayout(gbox);
QWidget* widget;
if (bi.type != InputBindingInfo::Type::Motor)
widget = new InputBindingWidget(gbox, sif, bi.type, getConfigSection(), bi.name);
else
widget = new InputVibrationBindingWidget(gbox, getDialog(), getConfigSection(), bi.name);
QVBoxLayout* const temp = new QVBoxLayout(gbox);
QWidget* const widget = new InputBindingWidget(gbox, sif, bi.type, getConfigSection(), bi.name);
temp->addWidget(widget);
axis_layout->addWidget(gbox, row, column);
@@ -586,7 +583,8 @@ void ControllerBindingWidget::bindBindingWidgets(QWidget* parent)
{
if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis ||
bi.type == InputBindingInfo::Type::Button || bi.type == InputBindingInfo::Type::Pointer ||
bi.type == InputBindingInfo::Type::RelativePointer)
bi.type == InputBindingInfo::Type::RelativePointer || bi.type == InputBindingInfo::Type::Motor ||
bi.type == InputBindingInfo::Type::LED)
{
InputBindingWidget* widget = parent->findChild<InputBindingWidget*>(QString::fromUtf8(bi.name));
if (!widget)
@@ -597,12 +595,6 @@ void ControllerBindingWidget::bindBindingWidgets(QWidget* parent)
widget->initialize(sif, bi.type, config_section, bi.name);
}
else if (bi.type == InputBindingInfo::Type::Motor)
{
InputVibrationBindingWidget* widget = parent->findChild<InputVibrationBindingWidget*>(QString::fromUtf8(bi.name));
if (widget)
widget->setKey(getDialog(), config_section, bi.name);
}
}
}

View File

@@ -29,8 +29,6 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
"SDLTouchpadAsPointer", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLPS5PlayerLED, "InputSources",
"SDLPS5PlayerLED", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLPS5MicMuteLEDForAnalogMode, "InputSources",
"SDLPS5MicMuteLEDForAnalogMode", false);
connect(m_ui.enableSDLSource, &QCheckBox::checkStateChanged, this,
&ControllerGlobalSettingsWidget::updateSDLOptionsEnabled);
connect(m_ui.ledSettings, &QToolButton::clicked, this, &ControllerGlobalSettingsWidget::ledSettingsClicked);
@@ -122,8 +120,6 @@ void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled()
m_ui.enableTouchPadAsPointer->setEnabled(enabled);
if (m_ui.enableSDLPS5PlayerLED)
m_ui.enableSDLPS5PlayerLED->setEnabled(enabled);
if (m_ui.enableSDLPS5MicMuteLEDForAnalogMode)
m_ui.enableSDLPS5MicMuteLEDForAnalogMode->setEnabled(enabled);
if (m_ui.ledSettings)
m_ui.ledSettings->setEnabled(enabled);
}
@@ -148,7 +144,7 @@ void ControllerLEDSettingsDialog::linkButton(ColorPickerButton* button, u32 play
{
std::string key = fmt::format("Player{}LED", player_id);
const u32 current_value =
SDLInputSource::ParseRGBForPlayerId(m_dialog->getStringValue("SDLExtra", key.c_str(), ""), player_id);
SDLInputSource::ParseRGBForPlayerId(m_dialog->getStringValue("SDLExtra", key.c_str(), ""), player_id, false);
button->setColor(current_value);
connect(button, &ColorPickerButton::colorChanged, this, [this, key = std::move(key)](u32 new_rgb) {

View File

@@ -29,13 +29,6 @@
<string>SDL Input Source</string>
</property>
<layout class="QGridLayout" name="sdlGridLayout">
<item row="3" column="1">
<widget class="QCheckBox" name="enableSDLPS5MicMuteLEDForAnalogMode">
<property name="text">
<string>Use DualSense Mic Mute LED for Analog Mode</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>

View File

@@ -277,10 +277,17 @@ void InputBindingWidget::onClicked()
return;
}
if (isListeningForInput())
stopListeningForInput();
if (InputBindingInfo::IsEffectType(m_bind_type))
{
showEffectBindingDialog();
}
else
{
if (isListeningForInput())
stopListeningForInput();
startListeningForInput(TIMEOUT_FOR_SINGLE_BINDING);
startListeningForInput(TIMEOUT_FOR_SINGLE_BINDING);
}
}
void InputBindingWidget::onInputListenTimerTimeout()
@@ -410,90 +417,65 @@ void InputBindingWidget::openDialog()
dlg->show();
}
InputVibrationBindingWidget::InputVibrationBindingWidget(QWidget* parent)
void InputBindingWidget::showEffectBindingDialog()
{
connect(this, &QPushButton::clicked, this, &InputVibrationBindingWidget::onClicked);
}
std::vector<InputBindingKey> options;
QStringList option_names;
QString current;
InputVibrationBindingWidget::InputVibrationBindingWidget(QWidget* parent, ControllerSettingsWindow* dialog,
std::string section_name, std::string key_name)
{
setMinimumWidth(225);
setMaximumWidth(225);
connect(this, &QPushButton::clicked, this, &InputVibrationBindingWidget::onClicked);
setKey(dialog, std::move(section_name), std::move(key_name));
}
InputVibrationBindingWidget::~InputVibrationBindingWidget()
{
}
void InputVibrationBindingWidget::setKey(ControllerSettingsWindow* dialog, std::string section_name,
std::string key_name)
{
m_dialog = dialog;
m_section_name = std::move(section_name);
m_key_name = std::move(key_name);
m_binding = Host::GetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str());
setText(QString::fromStdString(m_binding));
}
void InputVibrationBindingWidget::clearBinding()
{
m_binding = {};
Host::DeleteBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
Host::CommitBaseSettingChanges();
g_emu_thread->reloadInputBindings();
setText(QString());
}
void InputVibrationBindingWidget::onClicked()
{
QInputDialog dialog(QtUtils::GetRootWidget(this));
const QString full_key(
QStringLiteral("%1/%2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name)));
const QString current(QString::fromStdString(m_binding));
QStringList input_options = g_emu_thread->getInputDeviceListModel()->getVibrationMotorList();
if (!current.isEmpty() && input_options.indexOf(current) < 0)
const InputDeviceListModel::EffectList& all_options = g_emu_thread->getInputDeviceListModel()->getEffectList();
options.reserve(all_options.size());
option_names.reserve(all_options.size());
for (const auto& [type, key] : g_emu_thread->getInputDeviceListModel()->getEffectList())
{
input_options.append(current);
if (type != m_bind_type)
continue;
const TinyString name = InputManager::ConvertInputBindingKeyToString(type, key);
if (name.empty())
continue;
QString qname = QtUtils::StringViewToQString(name);
if (!m_bindings.empty() && name == m_bindings.front())
current = qname;
options.push_back(key);
option_names.push_back(std::move(qname));
}
else if (input_options.isEmpty())
if (options.empty())
{
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"),
tr("No devices with vibration motors were detected."));
(m_bind_type == InputBindingInfo::Type::Motor) ?
tr("No devices with vibration motors were detected.") :
tr("No devices with LEDs were detected."));
return;
}
// TODO: Multiple options? needs a custom dialog
const QString full_key(
QStringLiteral("%1/%2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name)));
QInputDialog input_dialog(this);
input_dialog.setWindowTitle(full_key);
input_dialog.setLabelText(tr("Select vibration motor for %1.").arg(full_key));
input_dialog.setLabelText(tr("Select device and effect for %1.").arg(full_key));
input_dialog.setInputMode(QInputDialog::TextInput);
input_dialog.setOptions(QInputDialog::UseListViewForComboBoxItems);
input_dialog.setComboBoxEditable(false);
input_dialog.setComboBoxItems(std::move(input_options));
input_dialog.setComboBoxItems(option_names);
input_dialog.setTextValue(current);
if (input_dialog.exec() == QDialog::Rejected)
return;
const QString new_value(input_dialog.textValue());
m_binding = new_value.toStdString();
Host::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_binding.c_str());
Host::CommitBaseSettingChanges();
g_emu_thread->reloadInputBindings();
setText(new_value);
}
void InputVibrationBindingWidget::mouseReleaseEvent(QMouseEvent* e)
{
if (e->button() == Qt::RightButton)
const QString new_value = input_dialog.textValue();
for (qsizetype i = 0; i < option_names.size(); i++)
{
clearBinding();
return;
if (new_value == option_names[i])
{
m_new_bindings.clear();
m_new_bindings.push_back(options[i]);
setNewBinding();
reloadBinding();
return;
}
}
QPushButton::mouseReleaseEvent(e);
}

View File

@@ -56,6 +56,8 @@ protected:
void onInputListenTimerTimeout();
void inputManagerHookCallback(InputBindingKey key, float value);
void showEffectBindingDialog();
SettingsInterface* m_sif = nullptr;
InputBindingInfo::Type m_bind_type = InputBindingInfo::Type::Unknown;
std::string m_section_name;
@@ -68,30 +70,3 @@ protected:
QPoint m_input_listen_start_position{};
bool m_mouse_mapping_enabled = false;
};
class InputVibrationBindingWidget : public QPushButton
{
Q_OBJECT
public:
explicit InputVibrationBindingWidget(QWidget* parent);
InputVibrationBindingWidget(QWidget* parent, ControllerSettingsWindow* dialog, std::string section_name,
std::string key_name);
~InputVibrationBindingWidget();
void setKey(ControllerSettingsWindow* dialog, std::string section_name, std::string key_name);
void clearBinding();
protected:
virtual void mouseReleaseEvent(QMouseEvent* e) override;
void onClicked();
private:
std::string m_section_name;
std::string m_key_name;
std::string m_binding;
ControllerSettingsWindow* m_dialog;
};

View File

@@ -2586,36 +2586,33 @@ void InputDeviceListModel::enumerateDevices()
DebugAssert(g_emu_thread->isCurrentThread());
const InputManager::DeviceList devices = InputManager::EnumerateDevices();
const InputManager::VibrationMotorList motors = InputManager::EnumerateVibrationMotors();
const InputManager::DeviceEffectList effects = InputManager::EnumerateDeviceEffects();
DeviceList new_devices;
new_devices.reserve(devices.size());
for (const auto& [key, identifier, device_name] : devices)
new_devices.emplace_back(key, QString::fromStdString(identifier), QString::fromStdString(device_name));
QStringList new_motors;
new_motors.reserve(motors.size());
for (const auto& key : motors)
{
new_motors.push_back(
QtUtils::StringViewToQString(InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, key)));
}
EffectList new_effects;
new_effects.reserve(effects.size());
for (const auto& [type, key] : effects)
new_effects.emplace_back(type, key);
QMetaObject::invokeMethod(this, &InputDeviceListModel::resetLists, Qt::QueuedConnection, new_devices, new_motors);
QMetaObject::invokeMethod(this, &InputDeviceListModel::resetLists, Qt::QueuedConnection, new_devices, new_effects);
}
void InputDeviceListModel::resetLists(const DeviceList& devices, const QStringList& motors)
void InputDeviceListModel::resetLists(const DeviceList& devices, const EffectList& effects)
{
beginResetModel();
m_devices = devices;
m_vibration_motors = motors;
m_effects = effects;
endResetModel();
}
void InputDeviceListModel::onDeviceConnected(const InputBindingKey& key, const QString& identifier,
const QString& device_name, const QStringList& vibration_motors)
const QString& device_name, const EffectList& effects)
{
for (const auto& it : m_devices)
{
@@ -2628,7 +2625,7 @@ void InputDeviceListModel::onDeviceConnected(const InputBindingKey& key, const Q
m_devices.emplace_back(key, identifier, device_name);
endInsertRows();
m_vibration_motors.append(vibration_motors);
m_effects.append(effects);
}
void InputDeviceListModel::onDeviceDisconnected(const InputBindingKey& key, const QString& identifier)
@@ -2642,12 +2639,12 @@ void InputDeviceListModel::onDeviceDisconnected(const InputBindingKey& key, cons
m_devices.remove(i);
endRemoveRows();
// remove vibration motors too
const QString motor_prefix = QStringLiteral("%1/").arg(identifier);
for (qsizetype j = 0; j < m_vibration_motors.size();)
// remove effects too
const QString effect_prefix = QStringLiteral("%1/").arg(identifier);
for (qsizetype j = 0; j < m_effects.size();)
{
if (m_vibration_motors[j].startsWith(motor_prefix))
m_vibration_motors.remove(j);
if (m_effects[j].second.source_type == key.source_type && m_effects[j].second.source_index == key.source_index)
m_effects.remove(j);
else
j++;
}
@@ -2659,22 +2656,19 @@ void InputDeviceListModel::onDeviceDisconnected(const InputBindingKey& key, cons
void Host::OnInputDeviceConnected(InputBindingKey key, std::string_view identifier, std::string_view device_name)
{
// get the motors for this device to append to the list
QStringList vibration_motor_list;
const InputManager::VibrationMotorList im_vibration_motor_list = InputManager::EnumerateVibrationMotors(key);
if (!im_vibration_motor_list.empty())
// get the effects for this device to append to the list
InputDeviceListModel::EffectList qeffect_list;
const InputManager::DeviceEffectList effect_list = InputManager::EnumerateDeviceEffects(std::nullopt, key);
if (!effect_list.empty())
{
vibration_motor_list.reserve(im_vibration_motor_list.size());
for (const InputBindingKey& motor_key : im_vibration_motor_list)
{
vibration_motor_list.push_back(QtUtils::StringViewToQString(
InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, motor_key)));
}
qeffect_list.reserve(effect_list.size());
for (const auto& [eff_type, eff_key] : effect_list)
qeffect_list.emplace_back(eff_type, eff_key);
}
QMetaObject::invokeMethod(g_emu_thread->getInputDeviceListModel(), &InputDeviceListModel::onDeviceConnected,
Qt::QueuedConnection, key, QtUtils::StringViewToQString(identifier),
QtUtils::StringViewToQString(device_name), vibration_motor_list);
QtUtils::StringViewToQString(device_name), qeffect_list);
if (System::IsValid() || GPUThread::IsFullscreenUIRequested())
{

View File

@@ -7,6 +7,7 @@
#include "core/game_list.h"
#include "core/host.h"
#include "core/input_types.h"
#include "core/system.h"
#include "core/types.h"
@@ -277,15 +278,14 @@ public:
};
using DeviceList = QList<Device>;
using EffectList = QList<QPair<InputBindingInfo::Type, InputBindingKey>>;
explicit InputDeviceListModel(QObject* parent = nullptr);
~InputDeviceListModel() override;
// Safe to access on UI thread.
ALWAYS_INLINE const DeviceList& getDeviceList() const { return m_devices; }
ALWAYS_INLINE const QStringList& getVibrationMotorList() const { return m_vibration_motors; }
static QIcon getIconForKey(const InputBindingKey& key);
ALWAYS_INLINE const EffectList& getEffectList() const { return m_effects; }
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
@@ -294,14 +294,16 @@ public:
void enumerateDevices();
void onDeviceConnected(const InputBindingKey& key, const QString& identifier, const QString& device_name,
const QStringList& vibration_motors);
const EffectList& effects);
void onDeviceDisconnected(const InputBindingKey& key, const QString& identifier);
static QIcon getIconForKey(const InputBindingKey& key);
private:
void resetLists(const DeviceList& devices, const QStringList& motors);
void resetLists(const DeviceList& devices, const EffectList& motors);
DeviceList m_devices;
QStringList m_vibration_motors;
EffectList m_effects;
};
class QtAsyncTask : public QObject

View File

@@ -316,7 +316,8 @@ InputManager::DeviceList DInputSource::EnumerateDevices()
return ret;
}
InputManager::VibrationMotorList DInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
InputManager::DeviceEffectList DInputSource::EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device)
{
return {};
}
@@ -337,6 +338,11 @@ void DInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey s
// not supported
}
void DInputSource::UpdateLEDState(InputBindingKey key, float intensity)
{
// not supported
}
bool DInputSource::ContainsDevice(std::string_view device) const
{
return device.starts_with("DInput-");

View File

@@ -41,11 +41,13 @@ public:
void PollEvents() override;
std::optional<float> GetCurrentValue(InputBindingKey key) override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
InputManager::DeviceEffectList EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
void UpdateLEDState(InputBindingKey key, float intensity) override;
bool ContainsDevice(std::string_view device) const override;
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;

View File

@@ -88,17 +88,12 @@ struct PadVibrationBinding
}
};
struct PadModeLEDBinding
struct PadLEDBinding
{
struct ModeLED
{
InputBindingKey binding;
InputSource* source;
bool enabled;
};
ModeLED led;
u32 pad_index = 0;
InputBindingKey binding;
InputSource* source;
float last_intensity;
u32 pad_index;
};
struct MacroButton
@@ -192,8 +187,8 @@ using BindingMap = std::unordered_multimap<InputBindingKey, std::shared_ptr<Inpu
/// This is an array of all the pad vibration bindings, indexed by pad index.
using VibrationBindingArray = std::vector<PadVibrationBinding>;
/// This is an array of all the pad Mode LED bindings, indexed by pad index.
using ModeLEDBindingArray = std::vector<PadModeLEDBinding>;
/// This is an array of all the pad LED bindings, indexed by pad index.
using PadLEDBindingArray = std::vector<PadLEDBinding>;
/// Callback for pointer movement events. The key is the pointer key, and the value is the axis value.
using PointerMoveCallback = std::function<void(InputBindingKey key, float value)>;
@@ -204,7 +199,7 @@ struct ALIGN_TO_CACHE_LINE State
{
BindingMap binding_map;
VibrationBindingArray pad_vibration_array;
ModeLEDBindingArray pad_mode_led_array;
PadLEDBindingArray pad_led_array;
std::vector<MacroButton> macro_buttons;
std::vector<std::pair<u32, PointerMoveCallback>> pointer_move_callbacks;
std::recursive_mutex mutex;
@@ -383,6 +378,9 @@ TinyString InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type b
{
TinyString ret;
// in case the source disappears, very unlikely
const auto lock = std::unique_lock(s_state.mutex);
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer ||
binding_type == InputBindingInfo::Type::Device)
{
@@ -968,7 +966,7 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
bool vibration_binding_valid = false;
PadVibrationBinding vibration_binding = {};
vibration_binding.pad_index = pad_index;
PadModeLEDBinding led_binding = {};
PadLEDBinding led_binding = {};
led_binding.pad_index = pad_index;
for (const Controller::ControllerBindingInfo& bi : cinfo.bindings)
@@ -1045,13 +1043,24 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
}
break;
case InputBindingInfo::Type::ModeLED:
case InputBindingInfo::Type::LED:
{
if (bindings.empty())
continue;
if (ParseBindingAndGetSource(bindings.front(), &led_binding.led.binding, &led_binding.led.source))
s_state.pad_mode_led_array.push_back(std::move(led_binding));
break;
if (ParseBindingAndGetSource(bindings.front(), &led_binding.binding, &led_binding.source))
{
// If we're reloading bindings due to e.g. device connection, sync the LED state.
if (Controller* controller = System::GetController(pad_index))
led_binding.last_intensity = controller->GetLEDState(bi.bind_index);
// Need to pass it through unconditionally, otherwise if the LED was on it'll stay on.
led_binding.source->UpdateLEDState(led_binding.binding, led_binding.last_intensity);
s_state.pad_led_array.push_back(std::move(led_binding));
}
}
break;
case InputBindingInfo::Type::Pointer:
case InputBindingInfo::Type::Device:
@@ -1790,7 +1799,6 @@ void InputManager::OnInputDeviceConnected(InputBindingKey key, std::string_view
{
INFO_LOG("Device '{}' connected: '{}'", identifier, device_name);
Host::OnInputDeviceConnected(key, identifier, device_name);
SyncInputDeviceModeLEDOnConnection(identifier);
}
void InputManager::OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier)
@@ -1812,52 +1820,6 @@ std::unique_ptr<ForceFeedbackDevice> InputManager::CreateForceFeedbackDevice(con
return {};
}
// ------------------------------------------------------------------------
// Analog Mode LED
// ------------------------------------------------------------------------
void InputManager::SetPadModeLED(u32 pad_index, bool enabled)
{
for (PadModeLEDBinding& pad : s_state.pad_mode_led_array)
{
if (pad.pad_index != pad_index)
continue;
PadModeLEDBinding::ModeLED& led = pad.led;
if (led.enabled == enabled)
continue;
led.enabled = enabled;
led.source->UpdateModeLEDState(led.binding, enabled);
}
}
void InputManager::SyncInputDeviceModeLEDOnConnection(std::string_view identifier)
{
const std::optional<s32> player_id = StringUtil::FromChars<s32>(identifier.substr(identifier.find('-') + 1));
if (!player_id.has_value() || player_id.value() < 0)
return;
for (PadModeLEDBinding& pad : s_state.pad_mode_led_array)
{
PadModeLEDBinding::ModeLED& led = pad.led;
if (!led.source->ContainsDevice(identifier))
continue;
if (led.binding.source_index == static_cast<u32>(player_id.value()))
{
Controller* controller = System::GetController(pad.pad_index);
if (!controller)
return;
led.enabled = controller->InAnalogMode();
led.source->UpdateModeLEDState(led.binding, led.enabled);
}
}
}
// ------------------------------------------------------------------------
// Vibration
// ------------------------------------------------------------------------
@@ -1977,6 +1939,36 @@ void InputManager::UpdateContinuedVibration()
}
}
// ------------------------------------------------------------------------
// LEDs
// ------------------------------------------------------------------------
void InputManager::SetPadLEDState(u32 pad_index, float intensity)
{
for (PadLEDBinding& pad : s_state.pad_led_array)
{
if (pad.pad_index != pad_index || pad.last_intensity == intensity)
continue;
pad.last_intensity = intensity;
pad.source->UpdateLEDState(pad.binding, intensity);
}
}
void InputManager::ClearEffects()
{
PauseVibration();
for (PadLEDBinding& pad : s_state.pad_led_array)
{
if (pad.last_intensity == 0.0f)
continue;
pad.last_intensity = 0.0f;
pad.source->UpdateLEDState(pad.binding, 0.0f);
}
}
// ------------------------------------------------------------------------
// Macros
// ------------------------------------------------------------------------
@@ -2176,7 +2168,7 @@ void InputManager::ReloadBindings(const SettingsInterface& binding_si, const Set
s_state.binding_map.clear();
s_state.pad_vibration_array.clear();
s_state.pad_mode_led_array.clear();
s_state.pad_led_array.clear();
s_state.macro_buttons.clear();
s_state.pointer_move_callbacks.clear();
@@ -2294,17 +2286,18 @@ InputManager::DeviceList InputManager::EnumerateDevices()
return ret;
}
InputManager::VibrationMotorList InputManager::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
InputManager::DeviceEffectList InputManager::EnumerateDeviceEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device)
{
std::unique_lock lock(s_state.mutex);
VibrationMotorList ret;
DeviceEffectList ret;
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_state.input_sources[i])
{
VibrationMotorList devs = s_state.input_sources[i]->EnumerateVibrationMotors(for_device);
DeviceEffectList devs = s_state.input_sources[i]->EnumerateEffects(type, for_device);
if (ret.empty())
ret = std::move(devs);
else

View File

@@ -53,7 +53,7 @@ enum class InputSubclass : u32
ControllerHat = 2,
ControllerMotor = 3,
ControllerHaptic = 4,
ControllerModeLED = 5,
ControllerLED = 5,
SensorAccelerometer = 0,
};
@@ -277,8 +277,9 @@ using DeviceList = std::vector<std::tuple<InputBindingKey, std::string, std::str
DeviceList EnumerateDevices();
/// Enumerates available vibration motors at the time of call.
using VibrationMotorList = std::vector<InputBindingKey>;
VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device = std::nullopt);
using DeviceEffectList = std::vector<std::pair<InputBindingInfo::Type, InputBindingKey>>;
DeviceEffectList EnumerateDeviceEffects(std::optional<InputBindingInfo::Type> type = std::nullopt,
std::optional<InputBindingKey> for_device = std::nullopt);
/// Retrieves bindings that match the generic bindings for the specified device.
GenericInputBindingMapping GetGenericBindingMapping(std::string_view device);
@@ -340,8 +341,8 @@ void RemoveHook();
/// Returns true if there is an interception hook present.
bool HasHook();
void SetPadModeLED(u32 pad_index, bool enabled);
void SyncInputDeviceModeLEDOnConnection(std::string_view identifier);
/// Internal method used by pads to dispatch LED updates to input sources.
void SetPadLEDState(u32 pad_index, float intensity);
/// Internal method used by pads to dispatch vibration updates to input sources.
/// Intensity is normalized from 0 to 1.
@@ -351,6 +352,9 @@ void SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensi
/// The pad vibration state will internally remain, so that when emulation is unpaused, the effect resumes.
void PauseVibration();
/// Disables all vibration and LED effects. Call when stopping emulation.
void ClearEffects();
/// Returns the number of currently-connected pointer devices.
u32 GetPointerCount();

View File

@@ -20,11 +20,6 @@ void InputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey sm
UpdateMotorState(small_key, small_intensity);
}
void InputSource::UpdateModeLEDState(InputBindingKey key, bool enabled)
{
}
InputBindingKey InputSource::MakeGenericControllerDeviceKey(InputSourceType clazz, u32 controller_index)
{
InputBindingKey key = {};

View File

@@ -59,7 +59,8 @@ public:
virtual InputManager::DeviceList EnumerateDevices() = 0;
/// Enumerates available vibration motors at the time of call.
virtual InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) = 0;
virtual InputManager::DeviceEffectList EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device) = 0;
/// Retrieves bindings that match the generic bindings for the specified device.
/// Returns false if it's not one of our devices.
@@ -72,8 +73,8 @@ public:
virtual void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity);
/// Enables/Disables the source's Analog Mode LED.
virtual void UpdateModeLEDState(InputBindingKey key, bool enabled);
/// Adjusts intensities of LEDs or other indicators on the device.
virtual void UpdateLEDState(InputBindingKey key, float intensity) = 0;
/// Creates a force-feedback device from this source.
virtual std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) = 0;

View File

@@ -11,6 +11,7 @@
#include "common/bitutils.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/gsvector.h"
#include "common/log.h"
#include "common/path.h"
#include "common/string_util.h"
@@ -148,11 +149,11 @@ static constexpr std::array<const char*, 4> s_sdl_hat_direction_names = {{
// clang-format on
}};
static constexpr std::array<const char*, 4> s_sdl_default_led_colors = {{
"0000ff", // SDL-0
"ff0000", // SDL-1
"00ff00", // SDL-2
"ffff00", // SDL-3
static constexpr std::array<std::array<u32, 2>, 4> s_sdl_default_led_colors = {{
{0x000070, 0x0000ff}, // SDL-0
{0x700000, 0xff0000}, // SDL-1
{0x007000, 0x00ff00}, // SDL-2
{0xffff00, 0x707000}, // SDL-3
}};
#ifdef _WIN32
@@ -198,11 +199,6 @@ static constexpr const SettingInfo s_sdl_advanced_settings_info[] = {
#endif
};
static void SetControllerRGBLED(SDL_Gamepad* gp, u32 color)
{
SDL_SetGamepadLED(gp, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
}
static void SDLLogCallback(void* userdata, int category, SDL_LogPriority priority, const char* message)
{
static constexpr Log::Level priority_map[SDL_LOG_PRIORITY_COUNT] = {
@@ -272,22 +268,24 @@ void SDLInputSource::LoadSettings(const SettingsInterface& si)
{
for (u32 i = 0; i < MAX_LED_COLORS; i++)
{
const u32 color = GetRGBForPlayerId(si, i);
if (m_led_colors[i] == color)
continue;
bool changed = false;
for (u32 active = 0; active < 2; active++)
{
const u32 color = GetRGBForPlayerId(si, i, active != 0);
changed = (changed || m_led_colors[i][active] != color);
m_led_colors[i][active] = color;
}
m_led_colors[i] = color;
const auto it = GetControllerDataForPlayerId(i);
if (it == m_controllers.end() || !it->gamepad || !it->has_led)
continue;
SetControllerRGBLED(it->gamepad, color);
if (changed)
{
const auto it = GetControllerDataForPlayerId(i);
if (it != m_controllers.end() && it->gamepad && it->has_led)
SetControllerRGBLED(it->gamepad, it->has_rgb_led, m_led_colors[i], it->rgb_led_intensity);
}
}
m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
m_controller_ps5_player_led = si.GetBoolValue("InputSources", "SDLPS5PlayerLED", false);
m_controller_ps5_mic_mute_led_for_analog_mode = si.GetBoolValue("InputSources", "SDLPS5MicMuteLEDForAnalogMode");
m_controller_touchpad_as_pointer = si.GetBoolValue("InputSources", "SDLTouchpadAsPointer", false);
m_sdl_hints = si.GetKeyValueList("SDLHints");
@@ -313,7 +311,6 @@ void InputSource::CopySDLSourceSettings(SettingsInterface* dest_si, const Settin
dest_si->CopyBoolValue(src_si, "InputSources", "SDLControllerEnhancedMode");
dest_si->CopyBoolValue(src_si, "InputSources", "SDLPS5PlayerLED");
dest_si->CopyBoolValue(src_si, "InputSources", "SDLPS5MicMuteLEDForAnalogMode");
dest_si->CopyBoolValue(src_si, "InputSources", "SDLTouchpadAsPointer");
dest_si->CopySection(src_si, "SDLHints");
@@ -321,36 +318,27 @@ void InputSource::CopySDLSourceSettings(SettingsInterface* dest_si, const Settin
si.CopyValue(dest_si, src_si, "InputSources");
}
u32 SDLInputSource::GetRGBForPlayerId(const SettingsInterface& si, u32 player_id)
u32 SDLInputSource::GetRGBForPlayerId(const SettingsInterface& si, u32 player_id, bool active)
{
return ParseRGBForPlayerId(si.GetStringValue("SDLExtra", TinyString::from_format("Player{}LED", player_id).c_str(),
s_sdl_default_led_colors[player_id]),
player_id);
return ParseRGBForPlayerId(
si.GetStringValue("SDLExtra", TinyString::from_format("Player{}{}LED", player_id, active ? "Active" : "")),
player_id, active);
}
u32 SDLInputSource::ParseRGBForPlayerId(std::string_view str, u32 player_id)
u32 SDLInputSource::ParseRGBForPlayerId(std::string_view str, u32 player_id, bool active)
{
if (player_id >= MAX_LED_COLORS)
return 0;
const u32 default_color = StringUtil::FromChars<u32>(s_sdl_default_led_colors[player_id], 16).value_or(0);
const u32 color = StringUtil::FromChars<u32>(str, 16).value_or(default_color);
return color;
return StringUtil::FromChars<u32>(str, 16).value_or(s_sdl_default_led_colors[player_id][BoolToUInt32(active)]);
}
bool SDLInputSource::IsPS5Controller(SDL_Gamepad* gp)
static bool IsPS5Controller(SDL_Gamepad* gp)
{
typedef struct
{
u16 vendor;
u16 product;
} ControllerDescriptor_t;
// https://github.com/libsdl-org/SDL/blob/4b93e7488f10f5d713cb12ef04deb2bec4c55481/src/joystick/controller_list.h#L153,L154
const ControllerDescriptor_t supported_controllers[] = {
{0x054c, 0x0ce6}, // Sony DualSense Controller
{0x054c, 0x0df2} // Sony DualSense Edge Controller
static constexpr const std::pair<u16, u16> supported_controllers[] = {
{static_cast<u16>(0x054c), static_cast<u16>(0x0ce6)}, // Sony DualSense Controller
{static_cast<u16>(0x054c), static_cast<u16>(0x0df2)} // Sony DualSense Edge Controller
};
const u16 gamepad_vendor = SDL_GetGamepadVendor(gp);
const u16 gamepad_product = SDL_GetGamepadProduct(gp);
@@ -358,7 +346,7 @@ bool SDLInputSource::IsPS5Controller(SDL_Gamepad* gp)
bool supported = false;
for (auto& supported_controller : supported_controllers)
{
supported |= (supported_controller.vendor == gamepad_vendor && supported_controller.product == gamepad_product);
supported |= (supported_controller.first == gamepad_vendor && supported_controller.second == gamepad_product);
if (supported)
break;
}
@@ -366,27 +354,56 @@ bool SDLInputSource::IsPS5Controller(SDL_Gamepad* gp)
return supported;
}
void SDLInputSource::UpdateModeLEDState(InputBindingKey key, bool enabled)
void SDLInputSource::UpdateLEDState(InputBindingKey key, float intensity)
{
if (key.source_subtype != InputSubclass::ControllerModeLED)
return;
DebugAssert(key.source_type == InputSourceType::SDL && key.source_subtype == InputSubclass::ControllerLED);
auto it = GetControllerDataForPlayerId(key.source_index);
if (it == m_controllers.end())
return;
it->mode_led = enabled;
if (key.data == 0)
{
// RGB LED
if (!it->has_led || it->rgb_led_intensity == intensity)
return;
SendModeLEDUpdate(&(*it));
it->rgb_led_intensity = intensity;
SetControllerRGBLED(it->gamepad, it->has_rgb_led, m_led_colors[std::min(key.source_index, MAX_LED_COLORS)],
intensity);
}
else if (key.data == 1)
{
// Mode LED
const bool mode_led_enabled = (intensity > 0.0f);
if (!it->has_mode_led || it->mode_led_state == mode_led_enabled)
return;
it->mode_led_state = mode_led_enabled;
SetControllerMicMuteLED(it->gamepad, mode_led_enabled);
}
}
void SDLInputSource::SendModeLEDUpdate(ControllerData* cd)
void SDLInputSource::SetControllerRGBLED(SDL_Gamepad* gp, bool has_rgb_led, const std::array<u32, 2>& colors,
float intensity)
{
if (cd->has_mode_led && m_controller_ps5_mic_mute_led_for_analog_mode)
EnablePS5MicMuteLED(cd->gamepad, cd->mode_led);
if (has_rgb_led)
{
const GSVector4 c0 = GSVector4::rgba32(colors[0]);
const GSVector4 c1 = GSVector4::rgba32(colors[1]);
const GSVector4 c = c0 + (c1 - c0) * GSVector4(intensity);
const u32 color = c.rgba32();
SDL_SetGamepadLED(gp, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
}
else
{
const u8 color = static_cast<u8>(std::clamp(static_cast<int>(std::round(255.0f * intensity)), 0, 255));
SDL_SetGamepadLED(gp, color, color, color);
}
}
void SDLInputSource::EnablePS5MicMuteLED(SDL_Gamepad* gp, bool enabled)
void SDLInputSource::SetControllerMicMuteLED(SDL_Gamepad* gp, bool enabled)
{
// https://github.com/libsdl-org/SDL/blob/1aba421bd301fa663e5bc83dc8af97caf6a6968a/src/joystick/hidapi/SDL_hidapi_ps5.c#L169
typedef struct
@@ -418,11 +435,12 @@ void SDLInputSource::EnablePS5MicMuteLED(SDL_Gamepad* gp, bool enabled)
SDL_zero(effects);
// https://github.com/libsdl-org/SDL/blob/1aba421bd301fa663e5bc83dc8af97caf6a6968a/src/joystick/hidapi/SDL_hidapi_ps5.c#749
effects.ucEnableBits2 |= 0x01; // 0x00: Block Mute LED, 0x01: Allow Mute LED
effects.ucMicLightMode = static_cast<int>(enabled); // 0x00: Disable Mute LED, 0x01: Enable Mute LED, 0x02: Enable Mute LED (Pulsing)
effects.ucEnableBits2 |= 0x01; // 0x00: Block Mute LED, 0x01: Allow Mute LED
effects.ucMicLightMode =
static_cast<Uint8>(enabled); // 0x00: Disable Mute LED, 0x01: Enable Mute LED, 0x02: Enable Mute LED (Pulsing)
if (!SDL_SendGamepadEffect(gp, &effects, sizeof(effects)))
ERROR_LOG("Error {} Mic Mute LED: {}", (enabled ? "enabling" : "disabling"), SDL_GetError());
ERROR_LOG("Failed to set mic mute LED to {}: {}", enabled, SDL_GetError());
}
std::span<const SettingInfo> SDLInputSource::GetAdvancedSettingsInfo()
@@ -657,10 +675,16 @@ std::optional<InputBindingKey> SDLInputSource::ParseKeyString(std::string_view d
}
}
}
else if (binding.starts_with("ModeLED"))
else if (binding.ends_with("LED"))
{
key.source_subtype = InputSubclass::ControllerModeLED;
key.data = 0;
key.source_subtype = InputSubclass::ControllerLED;
if (binding == "RGBLED")
key.data = 0;
else if (binding == "MuteLED")
key.data = 1;
else
return std::nullopt;
return key;
}
else
@@ -738,6 +762,10 @@ TinyString SDLInputSource::ConvertKeyToString(InputBindingKey key)
{
ret.format("SDL-{}/Haptic", static_cast<u32>(key.source_index));
}
else if (key.source_subtype == InputSubclass::ControllerLED)
{
ret.format("SDL-{}/{}", static_cast<u32>(key.source_index), (key.data != 0) ? "MuteLED" : "RGBLED");
}
}
return ret;
@@ -1067,11 +1095,15 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
if (!cd.haptic && !cd.use_gamepad_rumble)
VERBOSE_LOG("Rumble is not supported on '{}'", name);
cd.has_mode_led = IsPS5Controller(gamepad);
// Check for LED support, reset it in case a previous instance left it enabled
if ((cd.has_mode_led = IsPS5Controller(gamepad)))
SetControllerMicMuteLED(gamepad, false);
cd.has_led = (gamepad && SDL_GetBooleanProperty(properties, SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN, false));
cd.has_rgb_led = (gamepad && SDL_GetBooleanProperty(properties, SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN, false));
cd.has_led =
(cd.has_rgb_led || (gamepad && SDL_GetBooleanProperty(properties, SDL_PROP_GAMEPAD_CAP_MONO_LED_BOOLEAN, false)));
if (cd.has_led && player_id >= 0 && static_cast<u32>(player_id) < MAX_LED_COLORS)
SetControllerRGBLED(gamepad, m_led_colors[player_id]);
SetControllerRGBLED(gamepad, cd.has_rgb_led, m_led_colors[player_id], 0.0f);
m_controllers.push_back(std::move(cd));
@@ -1281,9 +1313,10 @@ std::optional<float> SDLInputSource::GetCurrentValue(InputBindingKey key)
return ret;
}
InputManager::VibrationMotorList SDLInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
InputManager::DeviceEffectList SDLInputSource::EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device)
{
InputManager::VibrationMotorList ret;
InputManager::DeviceEffectList ret;
if (for_device.has_value() && for_device->source_type != InputSourceType::SDL)
return ret;
@@ -1298,21 +1331,41 @@ InputManager::VibrationMotorList SDLInputSource::EnumerateVibrationMotors(std::o
key.source_index = cd.player_id;
if (cd.use_gamepad_rumble || cd.haptic_left_right_effect)
if (type.value_or(InputBindingInfo::Type::Motor) == InputBindingInfo::Type::Motor)
{
// two motors
key.source_subtype = InputSubclass::ControllerMotor;
key.data = 0;
ret.push_back(key);
key.data = 1;
ret.push_back(key);
if (cd.use_gamepad_rumble || cd.haptic_left_right_effect)
{
// two motors
key.source_subtype = InputSubclass::ControllerMotor;
key.data = 0;
ret.emplace_back(InputBindingInfo::Type::Motor, key);
key.data = 1;
ret.emplace_back(InputBindingInfo::Type::Motor, key);
}
else if (cd.haptic)
{
// haptic effect
key.source_subtype = InputSubclass::ControllerHaptic;
key.data = 0;
ret.emplace_back(InputBindingInfo::Type::Motor, key);
}
}
else if (cd.haptic)
if (type.value_or(InputBindingInfo::Type::LED) == InputBindingInfo::Type::LED)
{
// haptic effect
key.source_subtype = InputSubclass::ControllerHaptic;
key.data = 0;
ret.push_back(key);
if (cd.has_led)
{
key.source_subtype = InputSubclass::ControllerLED;
key.data = 0;
ret.emplace_back(InputBindingInfo::Type::LED, key);
}
if (cd.has_mode_led)
{
key.source_subtype = InputSubclass::ControllerLED;
key.data = 1;
ret.emplace_back(InputBindingInfo::Type::LED, key);
}
}
}
@@ -1365,7 +1418,9 @@ bool SDLInputSource::GetGenericBindingMapping(std::string_view device, GenericIn
}
if (it->has_mode_led)
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/ModeLED", pid));
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/MuteLED", pid));
else if (it->has_led)
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/RGBLED", pid));
return true;
}

View File

@@ -32,13 +32,13 @@ public:
void PollEvents() override;
std::optional<float> GetCurrentValue(InputBindingKey key) override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
InputManager::DeviceEffectList EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
void UpdateModeLEDState(InputBindingKey key, bool enabled) override;
void UpdateLEDState(InputBindingKey key, float intensity) override;
bool ContainsDevice(std::string_view device) const override;
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;
@@ -51,11 +51,8 @@ public:
SDL_Joystick* GetJoystickForDevice(std::string_view device);
static u32 GetRGBForPlayerId(const SettingsInterface& si, u32 player_id);
static u32 ParseRGBForPlayerId(std::string_view str, u32 player_id);
static bool IsPS5Controller(SDL_Gamepad* gp);
static void EnablePS5MicMuteLED(SDL_Gamepad* gp, bool enabled);
static u32 GetRGBForPlayerId(const SettingsInterface& si, u32 player_id, bool active);
static u32 ParseRGBForPlayerId(std::string_view str, u32 player_id, bool active);
static std::span<const SettingInfo> GetAdvancedSettingsInfo();
@@ -75,10 +72,12 @@ private:
int player_id;
float last_touch_x;
float last_touch_y;
float rgb_led_intensity;
bool use_gamepad_rumble : 1;
bool has_led : 1;
bool mode_led : 1;
bool has_rgb_led : 1;
bool has_mode_led : 1;
bool mode_led_state : 1;
// Used to disable Joystick controls that are used in GameController inputs so we don't get double events
std::vector<bool> joy_button_used_in_gc;
@@ -109,11 +108,14 @@ private:
bool HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev);
bool HandleJoystickHatEvent(const SDL_JoyHatEvent* ev);
void SendRumbleUpdate(ControllerData* cd);
void SendModeLEDUpdate(ControllerData* cd);
static bool ControllerHasMicLED(SDL_Gamepad* gp);
static void SetControllerRGBLED(SDL_Gamepad* gp, bool has_rgb_led, const std::array<u32, 2>& colors, float intensity);
static void SetControllerMicMuteLED(SDL_Gamepad* gp, bool enabled);
ControllerDataVector m_controllers;
std::array<u32, MAX_LED_COLORS> m_led_colors{};
std::array<std::array<u32, 2>, MAX_LED_COLORS> m_led_colors{};
std::vector<std::pair<std::string, std::string>> m_sdl_hints;
bool m_sdl_subsystem_initialized = false;
@@ -125,7 +127,6 @@ private:
{
bool m_controller_enhanced_mode : 1;
bool m_controller_ps5_player_led : 1;
bool m_controller_ps5_mic_mute_led_for_analog_mode : 1;
bool m_joystick_xbox_hidapi : 1;

View File

@@ -103,6 +103,10 @@ void Win32RawInputSource::UpdateMotorState(InputBindingKey large_key, InputBindi
{
}
void Win32RawInputSource::UpdateLEDState(InputBindingKey key, float intensity)
{
}
bool Win32RawInputSource::ContainsDevice(std::string_view device) const
{
return false;
@@ -130,8 +134,8 @@ std::unique_ptr<ForceFeedbackDevice> Win32RawInputSource::CreateForceFeedbackDev
return {};
}
InputManager::VibrationMotorList
Win32RawInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
InputManager::DeviceEffectList Win32RawInputSource::EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device)
{
return {};
}

View File

@@ -28,11 +28,13 @@ public:
void PollEvents() override;
std::optional<float> GetCurrentValue(InputBindingKey key) override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
InputManager::DeviceEffectList EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
void UpdateLEDState(InputBindingKey key, float intensity) override;
bool ContainsDevice(std::string_view device) const override;
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;

View File

@@ -369,13 +369,17 @@ std::unique_ptr<ForceFeedbackDevice> XInputSource::CreateForceFeedbackDevice(std
return {};
}
InputManager::VibrationMotorList XInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
InputManager::DeviceEffectList XInputSource::EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device)
{
InputManager::VibrationMotorList ret;
InputManager::DeviceEffectList ret;
if (for_device.has_value() && for_device->source_type != InputSourceType::XInput)
return ret;
if (type.has_value() && type.value() != InputBindingInfo::Type::Motor)
return ret;
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
if (for_device.has_value() && for_device->source_index != i)
@@ -386,10 +390,10 @@ InputManager::VibrationMotorList XInputSource::EnumerateVibrationMotors(std::opt
continue;
if (cd.has_large_motor)
ret.push_back(MakeGenericControllerMotorKey(InputSourceType::XInput, i, 0));
ret.emplace_back(InputBindingInfo::Type::Motor, MakeGenericControllerMotorKey(InputSourceType::XInput, i, 0));
if (cd.has_small_motor)
ret.push_back(MakeGenericControllerMotorKey(InputSourceType::XInput, i, 1));
ret.emplace_back(InputBindingInfo::Type::Motor, MakeGenericControllerMotorKey(InputSourceType::XInput, i, 1));
}
return ret;
@@ -585,6 +589,11 @@ void XInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey s
m_xinput_set_state(large_key.source_index, &cd.last_vibration);
}
void XInputSource::UpdateLEDState(InputBindingKey key, float intensity)
{
// not supported
}
std::unique_ptr<InputSource> InputSource::CreateXInputSource()
{
return std::make_unique<XInputSource>();

View File

@@ -43,11 +43,13 @@ public:
void PollEvents() override;
std::optional<float> GetCurrentValue(InputBindingKey key) override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
InputManager::DeviceEffectList EnumerateEffects(std::optional<InputBindingInfo::Type> type,
std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
void UpdateLEDState(InputBindingKey key, float intensity) override;
bool ContainsDevice(std::string_view device) const override;
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;