Use DualSense Mic Mute LED for Analog Mode (#3574)

* Use DualSense Mic Mute LED for Analog Mode

* Fix function casing and move function call outside loop

* Refactored code to use binds

- `InputManager` no longer uses API specific logic
- The Mic Mute LED gets bound as `ModeLED = SDL-0/ModeLED` only for DualSense controllers
- Changed DualSense detection to use Vendor & Product ID
This commit is contained in:
Ariel Nogueira Kovaljski
2025-10-01 22:54:13 -03:00
committed by GitHub
parent c790972265
commit 51942df7dd
17 changed files with 292 additions and 5 deletions

View File

@@ -29,7 +29,10 @@ AnalogController::AnalogController(u32 index) : Controller(index)
m_rumble_config.fill(0xFF);
}
AnalogController::~AnalogController() = default;
AnalogController::~AnalogController()
{
InputManager::SetPadModeLED(m_index, false);
}
ControllerType AnalogController::GetType() const
{
@@ -339,6 +342,8 @@ void AnalogController::SetAnalogMode(bool enabled, bool show_message)
m_analog_mode = enabled;
InputManager::SetPadModeLED(m_index, enabled);
INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
if (show_message)
{
@@ -785,6 +790,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}
// clang-format off
BUTTON("Up", TRANSLATE_NOOP("AnalogController", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogController::Button::Up, GenericInputBinding::DPadUp),
@@ -816,11 +823,14 @@ 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),
// clang-format on
#undef MOTOR
#undef AXIS
#undef BUTTON
#undef MODE_LED
};
static constexpr const char* s_invert_settings[] = {

View File

@@ -6,6 +6,7 @@
#include "system.h"
#include "util/imgui_manager.h"
#include "util/input_manager.h"
#include "util/state_wrapper.h"
#include "common/bitutils.h"
@@ -25,7 +26,10 @@ AnalogJoystick::AnalogJoystick(u32 index) : Controller(index)
Reset();
}
AnalogJoystick::~AnalogJoystick() = default;
AnalogJoystick::~AnalogJoystick()
{
InputManager::SetPadModeLED(m_index, false);
}
ControllerType AnalogJoystick::GetType() const
{
@@ -40,6 +44,7 @@ bool AnalogJoystick::InAnalogMode() const
void AnalogJoystick::Reset()
{
m_transfer_state = TransferState::Idle;
InputManager::SetPadModeLED(m_index, m_analog_mode);
}
bool AnalogJoystick::DoState(StateWrapper& sw, bool apply_input_state)
@@ -240,6 +245,8 @@ void AnalogJoystick::ToggleAnalogMode()
{
m_analog_mode = !m_analog_mode;
InputManager::SetPadModeLED(m_index, m_analog_mode);
INFO_LOG("Joystick {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
Host::AddIconOSDMessage(
fmt::format("analog_mode_toggle_{}", m_index), ICON_FA_GAMEPAD,
@@ -349,6 +356,8 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
static_cast<u32>(AnalogJoystick::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("AnalogJoystick", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogJoystick::Button::Up, GenericInputBinding::DPadUp),
@@ -377,10 +386,13 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
AXIS("RRight", TRANSLATE_NOOP("AnalogJoystick", "Right Stick Right"), ICON_PF_RIGHT_ANALOG_RIGHT, AnalogJoystick::HalfAxis::RRight, GenericInputBinding::RightStickRight),
AXIS("RDown", TRANSLATE_NOOP("AnalogJoystick", "Right Stick Down"), ICON_PF_RIGHT_ANALOG_DOWN, AnalogJoystick::HalfAxis::RDown, GenericInputBinding::RightStickDown),
AXIS("RUp", TRANSLATE_NOOP("AnalogJoystick", "Right Stick Up"), ICON_PF_RIGHT_ANALOG_UP, AnalogJoystick::HalfAxis::RUp, GenericInputBinding::RightStickUp),
MODE_LED("ModeLED", TRANSLATE_NOOP("AnalogJoystick", "Mode LED"), ICON_PF_ANALOG_LEFT_RIGHT, 0, GenericInputBinding::ModeLED),
// clang-format on
#undef AXIS
#undef BUTTON
#undef MODE_LED
};
static constexpr const char* s_invert_settings[] = {

View File

@@ -4868,6 +4868,10 @@ void FullscreenUI::DrawControllerSettingsPage()
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);
#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 "
@@ -9765,6 +9769,7 @@ TRANSLATE_NOOP("FullscreenUI", "Enable VRAM Write Replacement");
TRANSLATE_NOOP("FullscreenUI", "Enable XInput Input Source");
TRANSLATE_NOOP("FullscreenUI", "Enable debugging when supported by the host's renderer API. Only for developer use.");
TRANSLATE_NOOP("FullscreenUI", "Enable/Disable the Player LED on DualSense controllers.");
TRANSLATE_NOOP("FullscreenUI", "Enable/Disable using the DualSense controller's Mic Mute LED to indicate when Analog Mode is active.");
TRANSLATE_NOOP("FullscreenUI", "Enables alignment and bus exceptions. Not needed for any known games.");
TRANSLATE_NOOP("FullscreenUI", "Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles.");
TRANSLATE_NOOP("FullscreenUI", "Enables an additional three controller slots on each port. Not supported in all games.");
@@ -10053,6 +10058,7 @@ TRANSLATE_NOOP("FullscreenUI", "Runahead");
TRANSLATE_NOOP("FullscreenUI", "Runahead/Rewind");
TRANSLATE_NOOP("FullscreenUI", "Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater performance when using graphical enhancements with the hardware renderer.");
TRANSLATE_NOOP("FullscreenUI", "SDL DualSense Player LED");
TRANSLATE_NOOP("FullscreenUI", "SDL DualSense Mic Mute LED for Analog Mode");
TRANSLATE_NOOP("FullscreenUI", "SDL DualShock 4 / DualSense Enhanced Mode");
TRANSLATE_NOOP("FullscreenUI", "Safe Mode");
TRANSLATE_NOOP("FullscreenUI", "Save Controller Preset");

View File

@@ -15,6 +15,7 @@ struct InputBindingInfo
Axis,
HalfAxis,
Motor,
ModeLED,
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.
@@ -68,5 +69,7 @@ enum class GenericInputBinding : u8
LargeMotor, // Low frequency vibration.
SmallMotor, // High frequency vibration.
ModeLED, // Indicates Digital/Analog mode.
Count,
};

View File

@@ -24,17 +24,32 @@ JogCon::JogCon(u32 index) : Controller(index)
{
}
JogCon::~JogCon() = default;
JogCon::~JogCon()
{
InputManager::SetPadModeLED(m_index, false);
}
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);
ResetTransferState();
ResetMotorConfig();
}
@@ -184,6 +199,8 @@ void JogCon::SetJogConMode(bool enabled, bool show_message)
m_jogcon_mode = enabled;
m_configuration_mode = enabled && m_configuration_mode;
InputManager::SetPadModeLED(m_index, enabled);
INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_jogcon_mode ? "JogCon" : "Digital");
if (show_message)
{
@@ -624,6 +641,8 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
static_cast<u32>(JogCon::Button::MaxCount) + 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("JogCon", "D-Pad Up"), ICON_PF_DPAD_UP, JogCon::Button::Up, GenericInputBinding::DPadUp),
@@ -645,6 +664,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
AXIS("SteeringLeft", TRANSLATE_NOOP("JogCon", "Steering Left"), ICON_PF_ANALOG_LEFT, JogCon::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft),
AXIS("SteeringRight", TRANSLATE_NOOP("JogCon", "Steering Right"), ICON_PF_ANALOG_RIGHT, JogCon::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight),
MODE_LED("ModeLED", TRANSLATE_NOOP("JogCon", "Mode LED"), ICON_PF_ANALOG_LEFT_RIGHT, 0, GenericInputBinding::ModeLED),
// clang-format on
{"Motor", TRANSLATE_NOOP("JogCon", "Vibration Motor"), ICON_PF_VIBRATION, 0u, InputBindingInfo::Type::Motor,
@@ -656,6 +676,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
#undef BUTTON
#undef AXIS
#undef MODE_LED
};
static const SettingInfo s_settings[] = {

View File

@@ -49,6 +49,8 @@ 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,6 +5,7 @@
#include "host.h"
#include "system.h"
#include "util/input_manager.h"
#include "util/state_wrapper.h"
#include "common/assert.h"
@@ -26,16 +27,26 @@ NeGcon::NeGcon(u32 index) : Controller(index)
m_axis_state[static_cast<u8>(Axis::Steering)] = 0x80;
}
NeGcon::~NeGcon() = default;
NeGcon::~NeGcon()
{
InputManager::SetPadModeLED(m_index, false);
}
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)
@@ -270,6 +281,8 @@ 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),
@@ -285,10 +298,13 @@ 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,6 +87,7 @@ 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,7 +36,10 @@ NeGconRumble::NeGconRumble(u32 index) : Controller(index)
m_rumble_config.fill(0xFF);
}
NeGconRumble::~NeGconRumble() = default;
NeGconRumble::~NeGconRumble()
{
InputManager::SetPadModeLED(m_index, false);
}
ControllerType NeGconRumble::GetType() const
{
@@ -235,6 +238,8 @@ void NeGconRumble::SetAnalogMode(bool enabled, bool show_message)
if (m_analog_mode == enabled)
return;
InputManager::SetPadModeLED(m_index, enabled);
INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
if (show_message)
{
@@ -725,6 +730,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}
// clang-format off
BUTTON("Up", TRANSLATE_NOOP("NeGconRumble", "D-Pad Up"), ICON_PF_DPAD_UP, NeGconRumble::Button::Up, GenericInputBinding::DPadUp),
@@ -744,6 +751,8 @@ 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),
// clang-format on
#undef MOTOR

View File

@@ -29,6 +29,8 @@ 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);
@@ -120,6 +122,8 @@ 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);
}

View File

@@ -29,6 +29,13 @@
<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

@@ -88,6 +88,19 @@ struct PadVibrationBinding
}
};
struct PadModeLEDBinding
{
struct ModeLED
{
InputBindingKey binding;
InputSource* source;
bool enabled;
};
ModeLED led;
u32 pad_index = 0;
};
struct MacroButton
{
u16 pad_index; ///< Pad index this macro button is for.
@@ -179,6 +192,9 @@ 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>;
/// 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)>;
@@ -188,6 +204,7 @@ struct ALIGN_TO_CACHE_LINE State
{
BindingMap binding_map;
VibrationBindingArray pad_vibration_array;
ModeLEDBindingArray pad_mode_led_array;
std::vector<MacroButton> macro_buttons;
std::vector<std::pair<u32, PointerMoveCallback>> pointer_move_callbacks;
std::recursive_mutex mutex;
@@ -951,6 +968,8 @@ 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 = {};
led_binding.pad_index = pad_index;
for (const Controller::ControllerBindingInfo& bi : cinfo.bindings)
{
@@ -1026,6 +1045,14 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
}
break;
case InputBindingInfo::Type::ModeLED:
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;
case InputBindingInfo::Type::Pointer:
case InputBindingInfo::Type::Device:
// handled in device
@@ -1541,6 +1568,7 @@ void InputManager::SetDefaultSourceConfig(SettingsInterface& si)
si.SetBoolValue("InputSources", "SDL", true);
si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
si.SetBoolValue("InputSources", "SDLPS5PlayerLED", false);
si.SetBoolValue("InputSources", "SDLPS5MicMuteLEDForAnalogMode", false);
si.SetBoolValue("InputSources", "XInput", false);
si.SetBoolValue("InputSources", "RawInput", false);
}
@@ -1762,6 +1790,7 @@ 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)
@@ -1783,6 +1812,52 @@ 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
// ------------------------------------------------------------------------
@@ -2101,6 +2176,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.macro_buttons.clear();
s_state.pointer_move_callbacks.clear();

View File

@@ -53,6 +53,7 @@ enum class InputSubclass : u32
ControllerHat = 2,
ControllerMotor = 3,
ControllerHaptic = 4,
ControllerModeLED = 5,
SensorAccelerometer = 0,
};
@@ -339,6 +340,9 @@ 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 vibration updates to input sources.
/// Intensity is normalized from 0 to 1.
void SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensity, float small_motor_intensity);

View File

@@ -20,6 +20,11 @@ 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

@@ -72,6 +72,9 @@ 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);
/// Creates a force-feedback device from this source.
virtual std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) = 0;

View File

@@ -287,6 +287,7 @@ void SDLInputSource::LoadSettings(const SettingsInterface& si)
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");
@@ -312,6 +313,7 @@ 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");
@@ -337,6 +339,92 @@ u32 SDLInputSource::ParseRGBForPlayerId(std::string_view str, u32 player_id)
return color;
}
bool SDLInputSource::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
};
const u16 gamepad_vendor = SDL_GetGamepadVendor(gp);
const u16 gamepad_product = SDL_GetGamepadProduct(gp);
bool supported = false;
for (auto& supported_controller : supported_controllers)
{
supported |= (supported_controller.vendor == gamepad_vendor && supported_controller.product == gamepad_product);
if (supported)
break;
}
return supported;
}
void SDLInputSource::UpdateModeLEDState(InputBindingKey key, bool enabled)
{
if (key.source_subtype != InputSubclass::ControllerModeLED)
return;
auto it = GetControllerDataForPlayerId(key.source_index);
if (it == m_controllers.end())
return;
it->mode_led = enabled;
SendModeLEDUpdate(&(*it));
}
void SDLInputSource::SendModeLEDUpdate(ControllerData* cd)
{
if (cd->has_mode_led && m_controller_ps5_mic_mute_led_for_analog_mode)
EnablePS5MicMuteLED(cd->gamepad, cd->mode_led);
}
void SDLInputSource::EnablePS5MicMuteLED(SDL_Gamepad* gp, bool enabled)
{
// https://github.com/libsdl-org/SDL/blob/1aba421bd301fa663e5bc83dc8af97caf6a6968a/src/joystick/hidapi/SDL_hidapi_ps5.c#L169
typedef struct
{
Uint8 ucEnableBits1; // 0
Uint8 ucEnableBits2; // 1
Uint8 ucRumbleRight; // 2
Uint8 ucRumbleLeft; // 3
Uint8 ucHeadphoneVolume; // 4
Uint8 ucSpeakerVolume; // 5
Uint8 ucMicrophoneVolume; // 6
Uint8 ucAudioEnableBits; // 7
Uint8 ucMicLightMode; // 8
Uint8 ucAudioMuteBits; // 9
Uint8 rgucRightTriggerEffect[11]; // 10
Uint8 rgucLeftTriggerEffect[11]; // 21
Uint8 rgucUnknown1[6]; // 32
Uint8 ucEnableBits3; // 38
Uint8 rgucUnknown2[2]; // 39
Uint8 ucLedAnim; // 41
Uint8 ucLedBrightness; // 42
Uint8 ucPadLights; // 43
Uint8 ucLedRed; // 44
Uint8 ucLedGreen; // 45
Uint8 ucLedBlue; // 46
} DS5EffectsState_t;
DS5EffectsState_t effects;
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)
if (!SDL_SendGamepadEffect(gp, &effects, sizeof(effects)))
ERROR_LOG("Error {} Mic Mute LED: {}", (enabled ? "enabling" : "disabling"), SDL_GetError());
}
std::span<const SettingInfo> SDLInputSource::GetAdvancedSettingsInfo()
{
return s_sdl_advanced_settings_info;
@@ -569,6 +657,12 @@ std::optional<InputBindingKey> SDLInputSource::ParseKeyString(std::string_view d
}
}
}
else if (binding.starts_with("ModeLED"))
{
key.source_subtype = InputSubclass::ControllerModeLED;
key.data = 0;
return key;
}
else
{
// must be a button
@@ -973,6 +1067,8 @@ 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);
cd.has_led = (gamepad && SDL_GetBooleanProperty(properties, SDL_PROP_GAMEPAD_CAP_RGB_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]);
@@ -1268,6 +1364,9 @@ bool SDLInputSource::GetGenericBindingMapping(std::string_view device, GenericIn
mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("SDL-{}/Haptic", pid));
}
if (it->has_mode_led)
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/ModeLED", pid));
return true;
}
else

View File

@@ -38,6 +38,8 @@ public:
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
void UpdateModeLEDState(InputBindingKey key, bool enabled) override;
bool ContainsDevice(std::string_view device) const override;
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;
TinyString ConvertKeyToString(InputBindingKey key) override;
@@ -52,6 +54,9 @@ public:
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 std::span<const SettingInfo> GetAdvancedSettingsInfo();
static bool IsHandledInputEvent(const SDL_Event* ev);
@@ -72,6 +77,8 @@ private:
float last_touch_y;
bool use_gamepad_rumble : 1;
bool has_led : 1;
bool mode_led : 1;
bool has_mode_led : 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;
@@ -102,6 +109,7 @@ private:
bool HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev);
bool HandleJoystickHatEvent(const SDL_JoyHatEvent* ev);
void SendRumbleUpdate(ControllerData* cd);
void SendModeLEDUpdate(ControllerData* cd);
ControllerDataVector m_controllers;
@@ -117,6 +125,7 @@ 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;