SDLInputSource: Use persistent identifiers when mapping

Allows player numbers to change without invalidating bindings.
This commit is contained in:
Stenzek
2026-09-26 01:43:06 +10:00
parent 7495b76245
commit edd41dff15
7 changed files with 203 additions and 79 deletions
+8 -2
View File
@@ -3988,12 +3988,18 @@ void FullscreenUI::DrawControllerSettingsPage()
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_GEAR, "Enable SDL Input Source"),
FSUI_VSTR("The SDL input source supports most controllers."), "InputSources", "SDL", true, true,
false);
const bool sdl_enabled = bsi->GetBoolValue("InputSources", "SDL", true);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_WIFI, "SDL DualShock 4 / DualSense Enhanced Mode"),
FSUI_VSTR("Provides vibration and LED control support over Bluetooth."), "InputSources",
"SDLControllerEnhancedMode", false, bsi->GetBoolValue("InputSources", "SDL", true), false);
"SDLControllerEnhancedMode", false, sdl_enabled, 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", "SDL", true), false);
"SDLPS5PlayerLED", false, sdl_enabled, false);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_PLUG, "SDL Persistent Device Identifiers"),
FSUI_VSTR("Saves supported controllers by serial or device identity so bindings survive port "
"and connection-order changes when possible."),
"InputSources", "SDLUsePersistentDeviceIdentifiers", true, sdl_enabled, false);
#ifdef _WIN32
DrawToggleSetting(
bsi, FSUI_ICONVSTR(ICON_FA_GEAR, "Enable XInput Input Source"),
+2
View File
@@ -688,6 +688,7 @@ 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 DualShock 4 / DualSense Enhanced Mode");
TRANSLATE_NOOP("FullscreenUI", "SDL Persistent Device Identifiers");
TRANSLATE_NOOP("FullscreenUI", "Safe Mode");
TRANSLATE_NOOP("FullscreenUI", "Samples 16-bit direct-color textures at native resolution when upscaling. This can fix filtering of FMVs/backgrounds in some games, but may reduce the quality of render-to-texture effects.");
TRANSLATE_NOOP("FullscreenUI", "Save");
@@ -703,6 +704,7 @@ TRANSLATE_NOOP("FullscreenUI", "Save as Serial File Names");
TRANSLATE_NOOP("FullscreenUI", "Save state deleted.");
TRANSLATE_NOOP("FullscreenUI", "Saved {}");
TRANSLATE_NOOP("FullscreenUI", "Saves state periodically so you can rewind any mistakes while playing.");
TRANSLATE_NOOP("FullscreenUI", "Saves supported controllers by serial or device identity so bindings survive port and connection-order changes when possible.");
TRANSLATE_NOOP("FullscreenUI", "Scaled Interlacing");
TRANSLATE_NOOP("FullscreenUI", "Scales line skipping in interlaced rendering to the internal resolution, making it less noticeable. Usually safe to enable.");
TRANSLATE_NOOP("FullscreenUI", "Scaling");
@@ -60,6 +60,8 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
if (!remove_sources)
{
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLSource, "InputSources", "SDL", true);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(
sif, m_ui.useSDLPersistentDeviceIdentifiers, "InputSources", "SDLUsePersistentDeviceIdentifiers", true);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLEnhancedMode, "InputSources",
"SDLControllerEnhancedMode", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableTouchPadAsPointer, "InputSources",
@@ -114,6 +116,7 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
m_ui.sdlGridLayout = nullptr;
m_ui.ledSettings = nullptr;
m_ui.enableSDLSource = nullptr;
m_ui.useSDLPersistentDeviceIdentifiers = nullptr;
m_ui.enableSDLEnhancedMode = nullptr;
m_ui.sdlHelpText = nullptr;
m_ui.enableTouchPadAsPointer = nullptr;
@@ -190,6 +193,8 @@ void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled()
const bool enabled = m_ui.enableSDLSource->isChecked();
if (m_ui.enableSDLEnhancedMode)
m_ui.enableSDLEnhancedMode->setEnabled(enabled);
if (m_ui.useSDLPersistentDeviceIdentifiers)
m_ui.useSDLPersistentDeviceIdentifiers->setEnabled(enabled);
if (m_ui.enableTouchPadAsPointer)
m_ui.enableTouchPadAsPointer->setEnabled(enabled);
if (m_ui.ledSettings)
@@ -95,6 +95,16 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="useSDLPersistentDeviceIdentifiers">
<property name="toolTip">
<string>Saves supported controllers by serial or device identity so bindings survive port and connection-order changes when possible.</string>
</property>
<property name="text">
<string>Use Persistent Device Identifiers</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+2
View File
@@ -48,6 +48,8 @@ class Error;
X(SDL_GetJoystickName) \
X(SDL_GetJoystickPlayerIndex) \
X(SDL_GetJoystickGUID) \
X(SDL_GetJoystickSerial) \
X(SDL_GetJoystickPath) \
X(SDL_GetJoystickProperties) \
X(SDL_GetJoystickID) \
X(SDL_GetJoystickAxis) \
+172 -76
View File
@@ -24,6 +24,7 @@
#include "IconsPromptFont.h"
#include "fmt/format.h"
#include <cctype>
#include <cmath>
#include <mutex>
@@ -293,6 +294,54 @@ static const char* GetButtonIcon(u8 type, u32 button)
return str;
}
static bool IsDecimalString(std::string_view str)
{
return !str.empty() && std::ranges::all_of(str, [](char ch) { return std::isdigit(static_cast<unsigned char>(ch)); });
}
static bool IsSDLDeviceIdentifier(std::string_view device)
{
return device.starts_with("SDL-");
}
static std::string GetPersistentIdentifier(SDL_Joystick* joystick, const SDL_GUID& joystick_guid, const char* guid)
{
if (const char* serial = g_dyn_sdl.SDL_GetJoystickSerial(joystick); serial && serial[0] != '\0')
{
// Serials can contain any byte. If it does contain something not displayable/savable, hex-encode them.
// Otherwise, save the serial as-is. e.g. dualshock/dualsense serials are mac address-like.
const size_t serial_length = std::strlen(serial);
if (std::all_of(serial, serial + serial_length, [](char ch) {
return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || (ch == '-'));
}))
{
return fmt::format("{}-{}", guid, std::string_view(serial, serial_length));
}
else
{
return fmt::format("SB-{}-{}", guid, StringUtil::EncodeHex(serial, serial_length));
}
}
#ifdef _WIN32
const char* path_ptr = g_dyn_sdl.SDL_GetJoystickPath(joystick);
if (!path_ptr || path_ptr[0] == '\0')
return {};
// SDL doesn't expose the owning joystick backend. GameInput tags the internal GUID with 'g' and uses the
// 32-byte APP_LOCAL_DEVICE_ID, hex encoded, as its path.
const std::string_view path(path_ptr);
if (joystick_guid.data[14] == 'g')
{
const std::optional<std::vector<u8>> app_local_device_id = StringUtil::DecodeHex(path);
if (app_local_device_id.has_value() && app_local_device_id->size() == 32)
return fmt::format("GameInput-{}", StringUtil::EncodeHex<u8>(app_local_device_id.value()));
}
#endif
return {};
}
SDLInputSource::SDLInputSource() = default;
SDLInputSource::~SDLInputSource()
@@ -363,6 +412,15 @@ void SDLInputSource::LoadSettings(const SettingsInterface& si)
}
}
// We need to hold the write lock when changing the persistent-device-identifiers setting, because
// changing the value changes what gets returned by ConvertKeyToString().
if (const bool use_persistent_device_identifiers =
si.GetBoolValue("InputSources", "SDLUsePersistentDeviceIdentifiers", true);
use_persistent_device_identifiers != m_use_persistent_device_identifiers)
{
const auto lock = InputManager::GetSourcesWriteLock();
m_use_persistent_device_identifiers = use_persistent_device_identifiers;
}
m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
m_controller_ps5_player_led = si.GetBoolValue("InputSources", "SDLPS5PlayerLED", false);
m_controller_touchpad_as_pointer = si.GetBoolValue("InputSources", "SDLTouchpadAsPointer", false);
@@ -388,6 +446,7 @@ void InputSource::CopySDLSourceSettings(SettingsInterface* dest_si, const Settin
for (u32 i = 0; i < SDLInputSource::MAX_LED_COLORS; i++)
dest_si->CopyStringValue(src_si, "SDLExtra", TinyString::from_format("Player{}LED", i).c_str());
dest_si->CopyBoolValue(src_si, "InputSources", "SDLUsePersistentDeviceIdentifiers");
dest_si->CopyBoolValue(src_si, "InputSources", "SDLControllerEnhancedMode");
dest_si->CopyBoolValue(src_si, "InputSources", "SDLPS5PlayerLED");
dest_si->CopyBoolValue(src_si, "InputSources", "SDLTouchpadAsPointer");
@@ -627,16 +686,27 @@ InputManager::DeviceList SDLInputSource::EnumerateDevices()
bool SDLInputSource::ContainsDevice(std::string_view device) const
{
return device.starts_with("SDL-");
return IsSDLDeviceIdentifier(device);
}
std::optional<InputBindingKey> SDLInputSource::ParseKeyString(std::string_view device, std::string_view binding)
{
if (!device.starts_with("SDL-") || binding.empty())
if (!IsSDLDeviceIdentifier(device) || binding.empty())
return std::nullopt;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4));
if (!player_id.has_value() || player_id.value() < 0)
std::optional<int> player_id;
const std::string_view identifier = device.substr(4);
if (IsDecimalString(identifier))
{
player_id = StringUtil::FromChars<int>(identifier);
}
else
{
const auto it = ResolveDevice(device);
if (it != m_controllers.end())
player_id = it->player_id;
}
if (!player_id.has_value())
return std::nullopt;
InputBindingKey key = {};
@@ -785,18 +855,27 @@ SmallString SDLInputSource::ConvertKeyToString(InputBindingKey key)
{
if (key.source_type == InputSourceType::SDL)
{
// This function is used to convert the hooked events to keys for saving to file.
// If persistent identifiers are not enabled, we don't want to use them.
const auto it = m_use_persistent_device_identifiers ?
GetControllerDataForPlayerId(static_cast<int>(key.source_index)) :
m_controllers.end();
const TinyString device_number = TinyString::from_format("{}", static_cast<u32>(key.source_index));
const std::string_view device = (it != m_controllers.end() && !it->persistent_identifier.empty()) ?
std::string_view(it->persistent_identifier) :
device_number.view();
if (key.source_subtype == InputSubclass::ControllerAxis)
{
const char* modifier =
(key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
if (key.data < static_cast<u32>(s_axis_info.size()))
{
return SmallString::from_format("SDL-{}/{}{}{}", static_cast<u32>(key.source_index), modifier,
s_axis_info[key.data].name, key.invert ? "~" : "");
return SmallString::from_format("SDL-{}/{}{}{}", device, modifier, s_axis_info[key.data].name,
key.invert ? "~" : "");
}
else
{
return SmallString::from_format("SDL-{}/{}Axis{}{}", static_cast<u32>(key.source_index), modifier,
return SmallString::from_format("SDL-{}/{}Axis{}{}", device, modifier,
key.data - static_cast<u32>(s_axis_info.size()), key.invert ? "~" : "");
}
}
@@ -804,34 +883,30 @@ SmallString SDLInputSource::ConvertKeyToString(InputBindingKey key)
{
if (key.data < static_cast<u32>(s_button_info.size()))
{
return SmallString::from_format("SDL-{}/{}", static_cast<u32>(key.source_index), s_button_info[key.data].name);
return SmallString::from_format("SDL-{}/{}", device, s_button_info[key.data].name);
}
else
{
return SmallString::from_format("SDL-{}/Button{}", static_cast<u32>(key.source_index),
key.data - static_cast<u32>(s_button_info.size()));
return SmallString::from_format("SDL-{}/Button{}", device, key.data - static_cast<u32>(s_button_info.size()));
}
}
else if (key.source_subtype == InputSubclass::ControllerHat)
{
const u32 hat_index = key.data / static_cast<u32>(std::size(s_sdl_hat_direction_names));
const u32 hat_direction = key.data % static_cast<u32>(std::size(s_sdl_hat_direction_names));
return SmallString::from_format("SDL-{}/Hat{}{}", static_cast<u32>(key.source_index), hat_index,
s_sdl_hat_direction_names[hat_direction]);
return SmallString::from_format("SDL-{}/Hat{}{}", device, hat_index, s_sdl_hat_direction_names[hat_direction]);
}
else if (key.source_subtype == InputSubclass::ControllerMotor)
{
return SmallString::from_format("SDL-{}/{}Motor", static_cast<u32>(key.source_index),
(key.data == MOTOR_INDEX_SMALL) ? "Small" : "Large");
return SmallString::from_format("SDL-{}/{}Motor", device, (key.data == MOTOR_INDEX_SMALL) ? "Small" : "Large");
}
else if (key.source_subtype == InputSubclass::ControllerHaptic)
{
return SmallString::from_format("SDL-{}/Haptic", static_cast<u32>(key.source_index));
return SmallString::from_format("SDL-{}/Haptic", device);
}
else if (key.source_subtype == InputSubclass::ControllerLED)
{
return SmallString::from_format("SDL-{}/{}", static_cast<u32>(key.source_index),
(key.data != 0) ? "MuteLED" : "RGBLED");
return SmallString::from_format("SDL-{}/{}", device, (key.data != 0) ? "MuteLED" : "RGBLED");
}
else if (key.source_subtype == InputSubclass::ControllerSensor)
{
@@ -839,8 +914,8 @@ SmallString SDLInputSource::ConvertKeyToString(InputBindingKey key)
{
const char* modifier =
(key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
return SmallString::from_format("SDL-{}/{}{}{}", static_cast<u32>(key.source_index), modifier,
s_sdl_sensor_names[key.data], key.invert ? "~" : "");
return SmallString::from_format("SDL-{}/{}{}{}", device, modifier, s_sdl_sensor_names[key.data],
key.invert ? "~" : "");
}
}
}
@@ -863,12 +938,14 @@ SmallString SDLInputSource::ConvertKeyToDisplayString(InputBindingKey key, bool
const char* icon = (key.modifier == InputModifier::None) ? ai.icon_positive : ai.icon_negative;
if (icon && allow_icon)
{
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0} {1}"), static_cast<u32>(key.source_index), mapper(icon));
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0} {1}"),
static_cast<u32>(key.source_index), mapper(icon));
}
else
{
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"), static_cast<u32>(key.source_index),
Host::TranslateToStringView("SDLInputSource", ai.name));
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"),
static_cast<u32>(key.source_index),
Host::TranslateToStringView("SDLInputSource", ai.name));
}
}
}
@@ -881,7 +958,8 @@ SmallString SDLInputSource::ConvertKeyToDisplayString(InputBindingKey key, bool
GetButtonIcon((it != m_controllers.end()) ? it->gamepad_type : SDL_GAMEPAD_TYPE_UNKNOWN, key.data);
if (icon && allow_icon)
{
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0} {1}"), static_cast<u32>(key.source_index), mapper(icon));
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0} {1}"),
static_cast<u32>(key.source_index), mapper(icon));
}
else
{
@@ -895,33 +973,36 @@ SmallString SDLInputSource::ConvertKeyToDisplayString(InputBindingKey key, bool
{
const u32 hat_index = key.data / static_cast<u32>(std::size(s_sdl_hat_direction_names));
const u32 hat_direction = key.data % static_cast<u32>(std::size(s_sdl_hat_direction_names));
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/Hat{1}{2}"), static_cast<u32>(key.source_index), hat_index,
Host::TranslateToStringView("SDLInputSource", s_sdl_hat_direction_names[hat_direction]));
return SmallString::from_format(
TRANSLATE_FS("SDLInputSource", "SDL-{0}/Hat{1}{2}"), static_cast<u32>(key.source_index), hat_index,
Host::TranslateToStringView("SDLInputSource", s_sdl_hat_direction_names[hat_direction]));
}
else if (key.source_subtype == InputSubclass::ControllerMotor)
{
if (allow_icon)
{
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"), static_cast<u32>(key.source_index),
(key.data == MOTOR_INDEX_SMALL) ? ICON_PF_VIBRATION : ICON_PF_VIBRATION_L);
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"),
static_cast<u32>(key.source_index),
(key.data == MOTOR_INDEX_SMALL) ? ICON_PF_VIBRATION : ICON_PF_VIBRATION_L);
}
else
{
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"), static_cast<u32>(key.source_index),
(key.data == MOTOR_INDEX_SMALL) ? TRANSLATE_SV("SDLInputSource", "SmallMotor") :
TRANSLATE_SV("SDLInputSource", "LargeMotor"));
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"),
static_cast<u32>(key.source_index),
(key.data == MOTOR_INDEX_SMALL) ? TRANSLATE_SV("SDLInputSource", "SmallMotor") :
TRANSLATE_SV("SDLInputSource", "LargeMotor"));
}
}
else if (key.source_subtype == InputSubclass::ControllerHaptic)
{
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"), static_cast<u32>(key.source_index),
TRANSLATE_SV("SDLInputSource", "Haptic"));
TRANSLATE_SV("SDLInputSource", "Haptic"));
}
else if (key.source_subtype == InputSubclass::ControllerLED)
{
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}"), static_cast<u32>(key.source_index),
(key.data != 0) ? TRANSLATE_SV("SDLInputSource", "MuteLED") :
TRANSLATE_SV("SDLInputSource", "RGBLED"));
(key.data != 0) ? TRANSLATE_SV("SDLInputSource", "MuteLED") :
TRANSLATE_SV("SDLInputSource", "RGBLED"));
}
else if (key.source_subtype == InputSubclass::ControllerSensor)
{
@@ -929,8 +1010,9 @@ SmallString SDLInputSource::ConvertKeyToDisplayString(InputBindingKey key, bool
{
const char* modifier =
(key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
return SmallString::from_format(TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}{2}{3}"), static_cast<u32>(key.source_index), modifier,
Host::TranslateToStringView("SDLInputSource", s_sdl_sensor_names[key.data]), key.invert ? "~" : "");
return SmallString::from_format(
TRANSLATE_FS("SDLInputSource", "SDL-{0}/{1}{2}{3}"), static_cast<u32>(key.source_index), modifier,
Host::TranslateToStringView("SDLInputSource", s_sdl_sensor_names[key.data]), key.invert ? "~" : "");
}
}
}
@@ -1056,32 +1138,30 @@ bool SDLInputSource::PollEvents()
return topology_changed;
}
SDL_Joystick* SDLInputSource::GetJoystickForDevice(std::string_view device)
{
if (!device.starts_with("SDL-"))
return nullptr;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4));
if (!player_id.has_value() || player_id.value() < 0)
return nullptr;
auto it = GetControllerDataForPlayerId(player_id.value());
if (it == m_controllers.end())
return nullptr;
return it->joystick;
}
SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForJoystickId(SDL_JoystickID id)
{
return std::find_if(m_controllers.begin(), m_controllers.end(),
[id](const ControllerData& cd) { return cd.joystick_id == id; });
return std::ranges::find_if(m_controllers, [id](const ControllerData& cd) { return cd.joystick_id == id; });
}
SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForPlayerId(int id)
{
return std::find_if(m_controllers.begin(), m_controllers.end(),
[id](const ControllerData& cd) { return cd.player_id == id; });
return std::ranges::find_if(m_controllers, [id](const ControllerData& cd) { return cd.player_id == id; });
}
SDLInputSource::ControllerDataVector::iterator SDLInputSource::ResolveDevice(std::string_view device)
{
if (!IsSDLDeviceIdentifier(device))
return m_controllers.end();
const std::string_view identifier = device.substr(4);
if (IsDecimalString(identifier))
{
const std::optional<int> player_id = StringUtil::FromChars<int>(identifier);
return player_id.has_value() ? GetControllerDataForPlayerId(player_id.value()) : m_controllers.end();
}
return std::ranges::find_if(
m_controllers, [identifier](const ControllerData& cd) { return cd.persistent_identifier == identifier; });
}
int SDLInputSource::GetFreePlayerId() const
@@ -1135,7 +1215,8 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
}
char guid[33];
g_dyn_sdl.SDL_GUIDToString(g_dyn_sdl.SDL_GetJoystickGUID(joystick), guid, sizeof(guid));
const SDL_GUID joystick_guid = g_dyn_sdl.SDL_GetJoystickGUID(joystick);
g_dyn_sdl.SDL_GUIDToString(joystick_guid, guid, sizeof(guid));
const char* name = gamepad ? g_dyn_sdl.SDL_GetGamepadName(gamepad) : g_dyn_sdl.SDL_GetJoystickName(joystick);
if (!name)
@@ -1153,6 +1234,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
cd.haptic_left_right_effect = -1;
cd.gamepad = gamepad;
cd.joystick = joystick;
cd.persistent_identifier = GetPersistentIdentifier(joystick, joystick_guid, guid);
cd.last_touch_x = 0.0f;
cd.last_touch_y = 0.0f;
cd.gamepad_type =
@@ -1296,6 +1378,23 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
if (gamepad)
gamepad_button_type = GetGamepadButtonType(cd.gamepad_type);
// Identifier should be unique, but if it isn't, we fall back to the port-based identifier.
if (!cd.persistent_identifier.empty())
{
if (std::ranges::any_of(m_controllers, [&cd](const ControllerData& ocd) {
return (cd.persistent_identifier == ocd.persistent_identifier);
}))
{
WARNING_LOG("More than one device has the same persistent identifier {}. Ignoring the second device '{}'.",
cd.persistent_identifier, name);
cd.persistent_identifier = {};
}
else
{
INFO_LOG("Persistent identifier for {} is {}", name, cd.persistent_identifier);
}
}
// Hold the write lock for as little time as possible.
{
const auto lock = InputManager::GetSourcesWriteLock();
@@ -1625,49 +1724,45 @@ u32 SDLInputSource::GetPollableDeviceCount() const
bool SDLInputSource::GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping)
{
if (!device.starts_with("SDL-"))
return false;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4));
if (!player_id.has_value() || player_id.value() < 0)
return false;
ControllerDataVector::iterator it = GetControllerDataForPlayerId(player_id.value());
const auto it = ResolveDevice(device);
if (it == m_controllers.end())
return false;
if (it->gamepad)
{
// assume all buttons are present.
const s32 pid = player_id.value();
const TinyString device_number = TinyString::from_format("{}", static_cast<u32>(it->player_id));
const std::string_view save_device = (m_use_persistent_device_identifiers && !it->persistent_identifier.empty()) ?
std::string_view(it->persistent_identifier) :
device_number.view();
for (const AxisInfo& ai : s_axis_info)
{
if (ai.generic_binding_negative != GenericInputBinding::Unknown)
mapping->emplace_back(ai.generic_binding_negative, fmt::format("SDL-{}/-{}", pid, ai.name));
mapping->emplace_back(ai.generic_binding_negative, fmt::format("SDL-{}/-{}", save_device, ai.name));
if (ai.generic_binding_positive != GenericInputBinding::Unknown)
mapping->emplace_back(ai.generic_binding_positive, fmt::format("SDL-{}/+{}", pid, ai.name));
mapping->emplace_back(ai.generic_binding_positive, fmt::format("SDL-{}/+{}", save_device, ai.name));
}
for (const ButtonInfo& bi : s_button_info)
{
if (bi.generic_binding != GenericInputBinding::Unknown)
mapping->emplace_back(bi.generic_binding, fmt::format("SDL-{}/{}", pid, bi.name));
mapping->emplace_back(bi.generic_binding, fmt::format("SDL-{}/{}", save_device, bi.name));
}
if (it->use_gamepad_rumble || it->haptic_left_right_effect)
{
mapping->emplace_back(GenericInputBinding::SmallMotor, fmt::format("SDL-{}/SmallMotor", pid));
mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("SDL-{}/LargeMotor", pid));
mapping->emplace_back(GenericInputBinding::SmallMotor, fmt::format("SDL-{}/SmallMotor", save_device));
mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("SDL-{}/LargeMotor", save_device));
}
else
{
mapping->emplace_back(GenericInputBinding::SmallMotor, fmt::format("SDL-{}/Haptic", pid));
mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("SDL-{}/Haptic", pid));
mapping->emplace_back(GenericInputBinding::SmallMotor, fmt::format("SDL-{}/Haptic", save_device));
mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("SDL-{}/Haptic", save_device));
}
if (it->has_mode_led)
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/MuteLED", pid));
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/MuteLED", save_device));
else if (it->has_led)
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/RGBLED", pid));
mapping->emplace_back(GenericInputBinding::ModeLED, fmt::format("SDL-{}/RGBLED", save_device));
return true;
}
@@ -1758,13 +1853,14 @@ std::unique_ptr<InputSource> InputSource::CreateSDLSource()
std::unique_ptr<ForceFeedbackDevice> SDLInputSource::CreateForceFeedbackDevice(std::string_view device, Error* error)
{
SDL_Joystick* joystick = GetJoystickForDevice(device);
if (!joystick)
const auto it = ResolveDevice(device);
if (it == m_controllers.end())
{
Error::SetStringFmt(error, "No SDL_Joystick for {}", device);
return nullptr;
}
SDL_Joystick* const joystick = it->joystick;
SDL_Haptic* haptic = g_dyn_sdl.SDL_OpenHapticFromJoystick(joystick);
if (!haptic)
{
+4 -1
View File
@@ -78,6 +78,8 @@ private:
bool has_mode_led : 1;
bool has_accel : 1;
std::string persistent_identifier;
// 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;
std::vector<bool> joy_axis_used_in_gc;
@@ -94,9 +96,9 @@ private:
void LoadSettings(const SettingsInterface& si);
void SetHints();
SDL_Joystick* GetJoystickForDevice(std::string_view device);
ControllerDataVector::iterator GetControllerDataForJoystickId(SDL_JoystickID id);
ControllerDataVector::iterator GetControllerDataForPlayerId(int id);
ControllerDataVector::iterator ResolveDevice(std::string_view device);
int GetFreePlayerId() const;
bool OpenDevice(int index, bool is_gamecontroller);
@@ -119,6 +121,7 @@ private:
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_use_persistent_device_identifiers = false;
bool m_controller_touchpad_as_pointer = false;
union