diff --git a/.github/workflows/scripts/linux/build-dependencies-qt.sh b/.github/workflows/scripts/linux/build-dependencies-qt.sh index 2af1c264eb..2dfbc9c824 100755 --- a/.github/workflows/scripts/linux/build-dependencies-qt.sh +++ b/.github/workflows/scripts/linux/build-dependencies-qt.sh @@ -19,7 +19,7 @@ LIBJPEG=9f LIBPNG=1.6.45 LIBWEBP=1.5.0 LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29 -SDL=SDL2-2.30.12 +SDL=SDL3-3.2.6 QT=6.8.2 ZSTD=1.5.7 @@ -37,7 +37,7 @@ fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE. 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-$LIBPNG.tar.xz 7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz 0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz -ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea $SDL.tar.gz +096a0b843dd1124afda41c24bd05034af75af37e9a1b9d205cc0a70193b27e1a $SDL.tar.gz eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz 012043ce6d411e6e8a91fdc4e05e6bedcfa10fcb1347d3c33908f7fdd10dfe05 qtbase-everywhere-src-$QT.tar.xz d2a1bbb84707b8a0aec29227b170be00f04383fbf2361943596d09e7e443c8e1 qtimageformats-everywhere-src-$QT.tar.xz diff --git a/.github/workflows/scripts/linux/flatpak/modules/20-sdl2.json b/.github/workflows/scripts/linux/flatpak/modules/20-sdl3.json similarity index 71% rename from .github/workflows/scripts/linux/flatpak/modules/20-sdl2.json rename to .github/workflows/scripts/linux/flatpak/modules/20-sdl3.json index d8f107bd1d..077c00109c 100644 --- a/.github/workflows/scripts/linux/flatpak/modules/20-sdl2.json +++ b/.github/workflows/scripts/linux/flatpak/modules/20-sdl3.json @@ -1,5 +1,5 @@ { - "name": "sdl2", + "name": "sdl3", "buildsystem": "cmake-ninja", "builddir": true, "config-opts": [ @@ -14,8 +14,8 @@ "sources": [ { "type": "archive", - "url": "https://libsdl.org/release/SDL2-2.30.12.tar.gz", - "sha256": "ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea" + "url": "https://libsdl.org/release/SDL3-3.2.6.tar.gz", + "sha256": "096a0b843dd1124afda41c24bd05034af75af37e9a1b9d205cc0a70193b27e1a" } ], "cleanup": [ diff --git a/.github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json b/.github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json index f37615e883..ead3002a7b 100644 --- a/.github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json +++ b/.github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json @@ -26,7 +26,7 @@ ], "modules": [ "modules/10-libpcap.json", - "modules/20-sdl2.json", + "modules/20-sdl3.json", "modules/21-libbacktrace.json", "modules/22-shaderc.json", { diff --git a/.github/workflows/scripts/macos/build-dependencies-universal.sh b/.github/workflows/scripts/macos/build-dependencies-universal.sh index 7eed78a232..c6d3d34a5e 100755 --- a/.github/workflows/scripts/macos/build-dependencies-universal.sh +++ b/.github/workflows/scripts/macos/build-dependencies-universal.sh @@ -40,7 +40,7 @@ fi FREETYPE=2.13.3 HARFBUZZ=10.0.1 -SDL=SDL2-2.30.12 +SDL=SDL3-3.2.6 ZSTD=1.5.7 LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29 LIBPNG=1.6.45 @@ -76,7 +76,7 @@ CMAKE_ARCH_UNIVERSAL=-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" cat > SHASUMS < SHASUMS < $(DepsLibDir);%(AdditionalLibraryDirectories) - %(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL2.lib;zlib.lib;zstd.lib + %(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL3.lib;zlib.lib;zstd.lib @@ -15,7 +15,7 @@ - + diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index ad4fcd9c20..6eab3217cc 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -174,6 +174,14 @@ std::unique_ptr Host::CreateHostProgressCallback() return ProgressCallback::CreateNullProgressCallback(); } +void Host::ReportInfoAsync(const std::string_view title, const std::string_view message) +{ + if (!title.empty() && !message.empty()) + INFO_LOG("ReportInfoAsync: {}: {}", title, message); + else if (!message.empty()) + INFO_LOG("ReportInfoAsync: {}", message); +} + void Host::ReportErrorAsync(const std::string_view title, const std::string_view message) { if (!title.empty() && !message.empty()) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 659e3a3216..bdac5bcce8 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -1221,6 +1221,11 @@ void MainWindow::cancelGameListRefresh() m_game_list_widget->cancelRefresh(); } +void MainWindow::reportInfo(const QString& title, const QString& message) +{ + QMessageBox::information(this, title, message); +} + void MainWindow::reportError(const QString& title, const QString& message) { QMessageBox::critical(this, title, message); diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 9dd42a8aa1..7a8f3b2747 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -109,6 +109,7 @@ public Q_SLOTS: void checkForUpdates(bool display_message, bool force_check); void refreshGameList(bool invalidate_cache); void cancelGameListRefresh(); + void reportInfo(const QString& title, const QString& message); void reportError(const QString& title, const QString& message); bool confirmMessage(const QString& title, const QString& message); void onStatusMessage(const QString& message); diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 2375c70d9f..0893122bae 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -1612,6 +1612,18 @@ bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url return true; } +void Host::ReportInfoAsync(const std::string_view title, const std::string_view message) +{ + if (!title.empty() && !message.empty()) + INFO_LOG("ReportInfoAsync: {}: {}", title, message); + else if (!message.empty()) + INFO_LOG("ReportInfoAsync: {}", message); + + QMetaObject::invokeMethod(g_main_window, "reportInfo", Qt::QueuedConnection, + Q_ARG(const QString&, title.empty() ? QString() : QString::fromUtf8(title.data(), title.size())), + Q_ARG(const QString&, message.empty() ? QString() : QString::fromUtf8(message.data(), message.size()))); +} + void Host::ReportErrorAsync(const std::string_view title, const std::string_view message) { if (!title.empty() && !message.empty()) diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp index c67db6770a..8b1b3e10cf 100644 --- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp @@ -19,7 +19,7 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, SettingsInterface* sif = dialog->getProfileSettingsInterface(); ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLSource, "InputSources", "SDL", true); - ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false); + ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", true); connect(m_ui.enableSDLSource, &QCheckBox::checkStateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled); connect(m_ui.ledSettings, &QToolButton::clicked, this, &ControllerGlobalSettingsWidget::ledSettingsClicked); @@ -146,7 +146,7 @@ ControllerLEDSettingsDialog::ControllerLEDSettingsDialog(QWidget* parent, Contro SettingsInterface* sif = dialog->getProfileSettingsInterface(); - ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLPS5PlayerLED, "InputSources", "SDLPS5PlayerLED", false); + ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLPS5PlayerLED, "InputSources", "SDLPS5PlayerLED", true); connect(m_ui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &QDialog::accept); } diff --git a/pcsx2-qt/Settings/InputBindingDialog.cpp b/pcsx2-qt/Settings/InputBindingDialog.cpp index d10373acae..56d4c3690f 100644 --- a/pcsx2-qt/Settings/InputBindingDialog.cpp +++ b/pcsx2-qt/Settings/InputBindingDialog.cpp @@ -16,13 +16,14 @@ #include InputBindingDialog::InputBindingDialog(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name, - std::string key_name, std::vector bindings, QWidget* parent) + std::string key_name, std::vector bindings_settings, std::vector bindings_ui, QWidget* parent) : QDialog(parent) , m_sif(sif) , m_bind_type(bind_type) , m_section_name(std::move(section_name)) , m_key_name(std::move(key_name)) - , m_bindings(std::move(bindings)) + , m_bindings_settings(std::move(bindings_settings)) + , m_bindings_ui(std::move(bindings_ui)) { m_ui.setupUi(this); m_ui.title->setText(tr("Bindings for %1 %2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name))); @@ -32,6 +33,8 @@ InputBindingDialog::InputBindingDialog(SettingsInterface* sif, InputBindingInfo: connect(m_ui.removeBinding, &QPushButton::clicked, this, &InputBindingDialog::onRemoveBindingButtonClicked); connect(m_ui.clearBindings, &QPushButton::clicked, this, &InputBindingDialog::onClearBindingsButtonClicked); connect(m_ui.buttonBox, &QDialogButtonBox::rejected, [this]() { done(0); }); + connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &InputBindingDialog::onInputDeviceConnected); + connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this, &InputBindingDialog::onInputDeviceDisconnected); updateList(); // Only show the sensitivity controls for binds where it's applicable. @@ -210,11 +213,17 @@ void InputBindingDialog::addNewBinding() const std::string new_binding(InputManager::ConvertInputBindingKeysToString(m_bind_type, m_new_bindings.data(), m_new_bindings.size())); if (!new_binding.empty()) { - if (std::find(m_bindings.begin(), m_bindings.end(), new_binding) != m_bindings.end()) + if (std::find(m_bindings_settings.begin(), m_bindings_settings.end(), new_binding) != m_bindings_settings.end()) return; - m_ui.bindingList->addItem(QString::fromStdString(new_binding)); - m_bindings.push_back(std::move(new_binding)); + m_bindings_settings.push_back(std::move(new_binding)); + + SmallString new_binding_temp{std::string_view{new_binding}}; + InputManager::PrettifyInputBinding(new_binding_temp, false); + std::string new_binding_ui{new_binding_temp}; + + m_bindings_ui.push_back(new_binding_ui); + m_ui.bindingList->addItem(QString::fromStdString(new_binding_ui)); saveListToSettings(); } } @@ -230,17 +239,19 @@ void InputBindingDialog::onAddBindingButtonClicked() void InputBindingDialog::onRemoveBindingButtonClicked() { const int row = m_ui.bindingList->currentRow(); - if (row < 0 || static_cast(row) >= m_bindings.size()) + if (row < 0 || static_cast(row) >= m_bindings_ui.size()) return; - m_bindings.erase(m_bindings.begin() + row); + m_bindings_settings.erase(m_bindings_settings.begin() + row); + m_bindings_ui.erase(m_bindings_ui.begin() + row); delete m_ui.bindingList->takeItem(row); saveListToSettings(); } void InputBindingDialog::onClearBindingsButtonClicked() { - m_bindings.clear(); + m_bindings_settings.clear(); + m_bindings_ui.clear(); m_ui.bindingList->clear(); saveListToSettings(); } @@ -248,7 +259,7 @@ void InputBindingDialog::onClearBindingsButtonClicked() void InputBindingDialog::updateList() { m_ui.bindingList->clear(); - for (const std::string& binding : m_bindings) + for (const std::string& binding : m_bindings_ui) m_ui.bindingList->addItem(QString::fromStdString(binding)); } @@ -256,8 +267,8 @@ void InputBindingDialog::saveListToSettings() { if (m_sif) { - if (!m_bindings.empty()) - m_sif->SetStringList(m_section_name.c_str(), m_key_name.c_str(), m_bindings); + if (!m_bindings_settings.empty()) + m_sif->SetStringList(m_section_name.c_str(), m_key_name.c_str(), m_bindings_settings); else m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str()); m_sif->Save(); @@ -265,8 +276,8 @@ void InputBindingDialog::saveListToSettings() } else { - if (!m_bindings.empty()) - Host::SetBaseStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings); + if (!m_bindings_settings.empty()) + Host::SetBaseStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings_settings); else Host::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str()); Host::CommitBaseSettingChanges(); @@ -337,6 +348,16 @@ void InputBindingDialog::onDeadzoneChanged(int value) m_ui.deadzoneValue->setText(tr("%1%").arg(value)); } +void InputBindingDialog::onInputDeviceConnected(const QString& identifier, const QString& device_name) +{ + ReloadBindNames(); +} + +void InputBindingDialog::onInputDeviceDisconnected(const QString& identifier) +{ + ReloadBindNames(); +} + void InputBindingDialog::hookInputManager() { InputManager::SetHook([this](InputBindingKey key, float value) { @@ -349,3 +370,17 @@ void InputBindingDialog::unhookInputManager() { InputManager::RemoveHook(); } + +void InputBindingDialog::ReloadBindNames() +{ + m_bindings_ui.clear(); + m_bindings_ui.reserve(m_bindings_settings.size()); + for (int i = 0; i < m_bindings_settings.size(); i++) + { + SmallString binding{std::string_view{m_bindings_settings[i]}}; + InputManager::PrettifyInputBinding(binding, false); + m_bindings_ui.push_back(std::string{binding}); + } + + updateList(); +} diff --git a/pcsx2-qt/Settings/InputBindingDialog.h b/pcsx2-qt/Settings/InputBindingDialog.h index 5c278f0751..dd4be13fa0 100644 --- a/pcsx2-qt/Settings/InputBindingDialog.h +++ b/pcsx2-qt/Settings/InputBindingDialog.h @@ -22,7 +22,7 @@ class InputBindingDialog : public QDialog public: InputBindingDialog(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name, std::string key_name, - std::vector bindings, QWidget* parent); + std::vector bindings_settings, std::vector bindings_ui, QWidget* parent); ~InputBindingDialog(); protected Q_SLOTS: @@ -35,6 +35,9 @@ protected Q_SLOTS: void onSensitivityChanged(int value); void onDeadzoneChanged(int value); + void onInputDeviceConnected(const QString& identifier, const QString& device_name); + void onInputDeviceDisconnected(const QString& identifier); + protected: enum : u32 { @@ -55,13 +58,16 @@ protected: void hookInputManager(); void unhookInputManager(); + void ReloadBindNames(); + Ui::InputBindingDialog m_ui; SettingsInterface* m_sif; InputBindingInfo::Type m_bind_type; std::string m_section_name; std::string m_key_name; - std::vector m_bindings; + std::vector m_bindings_settings; + std::vector m_bindings_ui; std::vector m_new_bindings; std::vector>> m_value_ranges; diff --git a/pcsx2-qt/Settings/InputBindingWidget.cpp b/pcsx2-qt/Settings/InputBindingWidget.cpp index 482fdff99c..41e41e88b2 100644 --- a/pcsx2-qt/Settings/InputBindingWidget.cpp +++ b/pcsx2-qt/Settings/InputBindingWidget.cpp @@ -23,6 +23,8 @@ InputBindingWidget::InputBindingWidget(QWidget* parent) : QPushButton(parent) { connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked); + connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &InputBindingWidget::onInputDeviceConnected); + connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this, &InputBindingWidget::onInputDeviceDisconnected); } InputBindingWidget::InputBindingWidget( @@ -33,6 +35,8 @@ InputBindingWidget::InputBindingWidget( setMaximumWidth(225); connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked); + connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &InputBindingWidget::onInputDeviceConnected); + connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this, &InputBindingWidget::onInputDeviceDisconnected); initialize(sif, bind_type, std::move(section_name), std::move(key_name)); } @@ -62,20 +66,20 @@ void InputBindingWidget::updateText() const QString binding_tip(tr("\n\nLeft click to assign a new button\nShift + left click for additional bindings")); const QString binding_clear_tip(tr("\nRight click to clear binding")); - if (m_bindings.empty()) + if (m_bindings_ui.empty()) { setText(QString()); setToolTip(tr("No bindings registered") + binding_tip); } - else if (m_bindings.size() > 1) + else if (m_bindings_ui.size() > 1) { - setText(tr("%n bindings", "", static_cast(m_bindings.size()))); + setText(tr("%n bindings", "", static_cast(m_bindings_ui.size()))); // keep the full thing for the tooltip std::stringstream ss; bool first = true; - for (const std::string& binding : m_bindings) + for (const std::string& binding : m_bindings_ui) { if (first) first = false; @@ -87,7 +91,7 @@ void InputBindingWidget::updateText() } else { - QString binding_text(QString::fromStdString(m_bindings[0])); + QString binding_text(QString::fromStdString(m_bindings_ui[0])); setToolTip(binding_text + binding_tip + binding_clear_tip); // fix up accelerators, and if it's too long, ellipsise it @@ -232,13 +236,12 @@ void InputBindingWidget::setNewBinding() } } - m_bindings.clear(); - m_bindings.push_back(std::move(new_binding)); + m_bindings_ui.clear(); + m_bindings_ui.push_back(std::move(new_binding)); } void InputBindingWidget::clearBinding() { - m_bindings.clear(); if (m_sif) { m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str()); @@ -256,14 +259,24 @@ void InputBindingWidget::clearBinding() void InputBindingWidget::reloadBinding() { - m_bindings = m_sif ? m_sif->GetStringList(m_section_name.c_str(), m_key_name.c_str()) : - Host::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str()); + m_bindings_settings = m_sif ? m_sif->GetStringList(m_section_name.c_str(), m_key_name.c_str()) : + Host::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str()); + + m_bindings_ui.clear(); + m_bindings_ui.reserve(m_bindings_settings.size()); + for (int i = 0; i < m_bindings_settings.size(); i++) + { + SmallString binding{std::string_view{m_bindings_settings[i]}}; + InputManager::PrettifyInputBinding(binding, false); + m_bindings_ui.push_back(std::string{binding}); + } + updateText(); } void InputBindingWidget::onClicked() { - if (m_bindings.size() > 1) + if (m_bindings_ui.size() > 1) { openDialog(); return; @@ -376,6 +389,16 @@ void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float val } } +void InputBindingWidget::onInputDeviceConnected(const QString& identifier, const QString& device_name) +{ + reloadBinding(); +} + +void InputBindingWidget::onInputDeviceDisconnected(const QString& identifier) +{ + reloadBinding(); +} + void InputBindingWidget::hookInputManager() { InputManager::SetHook([this](InputBindingKey key, float value) { @@ -391,7 +414,7 @@ void InputBindingWidget::unhookInputManager() void InputBindingWidget::openDialog() { - InputBindingDialog binding_dialog(m_sif, m_bind_type, m_section_name, m_key_name, m_bindings, QtUtils::GetRootWidget(this)); + InputBindingDialog binding_dialog(m_sif, m_bind_type, m_section_name, m_key_name, m_bindings_settings, m_bindings_ui, QtUtils::GetRootWidget(this)); binding_dialog.exec(); reloadBinding(); } @@ -422,6 +445,12 @@ void InputVibrationBindingWidget::setKey(ControllerSettingsWindow* dialog, std:: 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()); + + SmallString binding{std::string_view{m_binding}}; + + if (InputManager::PrettifyInputBinding(binding, false)) + m_binding = binding; + setText(QString::fromStdString(m_binding)); } @@ -440,12 +469,22 @@ void InputVibrationBindingWidget::onClicked() 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(m_dialog->getVibrationMotors()); - if (!current.isEmpty() && input_options.indexOf(current) < 0) + QStringList input_setting_options(m_dialog->getVibrationMotors()); + QStringList input_ui_options; + input_ui_options.reserve(input_setting_options.count()); + + for (QString motor : input_setting_options) { - input_options.append(current); + SmallStringBase motor_ui(motor.toStdString()); + InputManager::PrettifyInputBinding(motor_ui, false); + input_ui_options.push_back(QString(motor_ui)); } - else if (input_options.isEmpty()) + + if (!current.isEmpty() && input_ui_options.indexOf(current) < 0) + { + input_setting_options.append(current); + } + else if (input_setting_options.isEmpty()) { QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"), tr("No devices with vibration motors were detected.")); return; @@ -457,16 +496,25 @@ void InputVibrationBindingWidget::onClicked() input_dialog.setInputMode(QInputDialog::TextInput); input_dialog.setOptions(QInputDialog::UseListViewForComboBoxItems); input_dialog.setComboBoxEditable(false); - input_dialog.setComboBoxItems(std::move(input_options)); + input_dialog.setComboBoxItems(std::move(input_ui_options)); input_dialog.setTextValue(current); if (input_dialog.exec() == 0) 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(); - setText(new_value); + // If a controller is unplugged, we won't have the setting string to save + // Skip saving if selected is an existing bind from an unplugged controller + const int selected = input_setting_options.indexOf(input_dialog.textValue()); + if (selected >= 0) + { + // Update config + const std::string new_setting_value(input_setting_options[selected].toStdString()); + Host::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), new_setting_value.c_str()); + Host::CommitBaseSettingChanges(); + // Update ui + const QString new_ui_value(input_dialog.textValue()); + m_binding = new_ui_value.toStdString(); + setText(new_ui_value); + } } void InputVibrationBindingWidget::mouseReleaseEvent(QMouseEvent* e) diff --git a/pcsx2-qt/Settings/InputBindingWidget.h b/pcsx2-qt/Settings/InputBindingWidget.h index 32800f714d..1f144d0cbb 100644 --- a/pcsx2-qt/Settings/InputBindingWidget.h +++ b/pcsx2-qt/Settings/InputBindingWidget.h @@ -39,6 +39,9 @@ protected Q_SLOTS: void onInputListenTimerTimeout(); void inputManagerHookCallback(InputBindingKey key, float value); + void onInputDeviceConnected(const QString& identifier, const QString& device_name); + void onInputDeviceDisconnected(const QString& identifier); + protected: enum : u32 { @@ -65,7 +68,8 @@ protected: InputBindingInfo::Type m_bind_type = InputBindingInfo::Type::Unknown; std::string m_section_name; std::string m_key_name; - std::vector m_bindings; + std::vector m_bindings_settings; + std::vector m_bindings_ui; std::vector m_new_bindings; std::vector>> m_value_ranges; QTimer* m_input_listen_timer = nullptr; diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index f77da95952..2f054c72a5 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -36,7 +36,7 @@ - %(AdditionalIncludeDirectories);$(DepsIncludeDir)\SDL2 + %(AdditionalIncludeDirectories);$(DepsIncludeDir)\SDL3 %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lzma\include diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 44ee9fd58f..9023aa900d 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -1146,7 +1146,7 @@ target_link_libraries(PCSX2_FLAGS INTERFACE discord-rpc simpleini freesurround - SDL2::SDL2 + SDL3::SDL3 ZLIB::ZLIB LZ4::LZ4 SoundTouch::SoundTouch @@ -1282,7 +1282,7 @@ function(setup_main_executable target) # Copy dependency libraries. set(DEPS_BINDIR "${CMAKE_SOURCE_DIR}/deps/bin") - set(DEPS_TO_COPY freetype.dll harfbuzz.dll libjpeg.dll libpng16.dll libsharpyuv.dll libwebp.dll lz4.dll SDL2.dll shaderc_shared.dll zlib1.dll zstd.dll) + set(DEPS_TO_COPY freetype.dll harfbuzz.dll libjpeg.dll libpng16.dll libsharpyuv.dll libwebp.dll lz4.dll SDL3.dll shaderc_shared.dll zlib1.dll zstd.dll) foreach(DEP_TO_COPY ${DEPS_TO_COPY}) install(FILES "${DEPS_BINDIR}/${DEP_TO_COPY}" DESTINATION "${CMAKE_SOURCE_DIR}/bin") endforeach() diff --git a/pcsx2/Host.cpp b/pcsx2/Host.cpp index b25c813af5..0a78d7083d 100644 --- a/pcsx2/Host.cpp +++ b/pcsx2/Host.cpp @@ -138,6 +138,15 @@ void Host::ClearTranslationCache() s_translation_string_mutex.unlock(); } +void Host::ReportFormattedInfoAsync(const std::string_view title, const char* format, ...) +{ + std::va_list ap; + va_start(ap, format); + std::string message(StringUtil::StdStringFromFormatV(format, ap)); + va_end(ap); + ReportInfoAsync(title, message); +} + void Host::ReportFormattedErrorAsync(const std::string_view title, const char* format, ...) { std::va_list ap; diff --git a/pcsx2/Host.h b/pcsx2/Host.h index 613195c532..e960a21f9a 100644 --- a/pcsx2/Host.h +++ b/pcsx2/Host.h @@ -55,6 +55,10 @@ namespace Host void RemoveKeyedOSDMessage(std::string key); void ClearOSDMessages(); + /// Displays an asynchronous error on the UI thread, i.e. doesn't block the caller. + void ReportInfoAsync(const std::string_view title, const std::string_view message); + void ReportFormattedInfoAsync(const std::string_view title, const char* format, ...); + /// Displays an asynchronous error on the UI thread, i.e. doesn't block the caller. void ReportErrorAsync(const std::string_view title, const std::string_view message); void ReportFormattedErrorAsync(const std::string_view title, const char* format, ...); diff --git a/pcsx2/Host/SDLAudioStream.cpp b/pcsx2/Host/SDLAudioStream.cpp index 4af1b7453f..b0573712f8 100644 --- a/pcsx2/Host/SDLAudioStream.cpp +++ b/pcsx2/Host/SDLAudioStream.cpp @@ -7,7 +7,7 @@ #include "common/Console.h" #include "common/Error.h" -#include +#include namespace { @@ -23,11 +23,11 @@ namespace void CloseDevice(); protected: - __fi bool IsOpen() const { return (m_device_id != 0); } + __fi bool IsOpen() const { return (m_stream != nullptr); } - static void AudioCallback(void* userdata, uint8_t* stream, int len); + static void AudioCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount); - u32 m_device_id = 0; + SDL_AudioStream* m_stream = nullptr; }; } // namespace @@ -41,7 +41,7 @@ static bool InitializeSDLAudio(Error* error) SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "PCSX2"); // May as well keep it alive until the process exits. - if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { Error::SetStringFmt(error, "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: {}", SDL_GetError()); return false; @@ -102,27 +102,24 @@ bool SDLAudioStream::OpenDevice(bool stretch_enabled, Error* error) READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>, }}; - SDL_AudioSpec spec = {}; - spec.freq = m_sample_rate; - spec.channels = m_output_channels; - spec.format = AUDIO_S16; - spec.samples = static_cast(GetBufferSizeForMS( - m_sample_rate, (m_parameters.minimal_output_latency) ? m_parameters.buffer_ms : m_parameters.output_latency_ms)); - spec.callback = AudioCallback; - spec.userdata = static_cast(this); + uint samples = GetBufferSizeForMS( + m_sample_rate, (m_parameters.minimal_output_latency) ? m_parameters.buffer_ms : m_parameters.output_latency_ms); + + SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, fmt::format("{}", samples).c_str()); + + const SDL_AudioSpec spec = {SDL_AUDIO_S16LE, m_output_channels, static_cast(m_sample_rate)}; + m_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, AudioCallback, static_cast(this)); SDL_AudioSpec obtained_spec = {}; - m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, SDL_AUDIO_ALLOW_SAMPLES_CHANGE); - if (m_device_id == 0) - { - Error::SetStringFmt(error, "SDL_OpenAudioDevice() failed: {}", SDL_GetError()); - return false; - } + int obtained_samples = 0; - DEV_LOG("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples); + if (SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &obtained_spec, &obtained_samples)) + DEV_LOG("Requested {} frame buffer, got {} frame buffer", samples, obtained_samples); + else + DEV_LOG("SDL_GetAudioDeviceFormat() failed {}", SDL_GetError()); BaseInitialize(sample_readers[static_cast(m_parameters.expansion_mode)], stretch_enabled); - SDL_PauseAudioDevice(m_device_id, 0); + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(m_stream)); return true; } @@ -132,20 +129,33 @@ void SDLAudioStream::SetPaused(bool paused) if (m_paused == paused) return; - SDL_PauseAudioDevice(m_device_id, paused ? 1 : 0); + if (paused) + SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(m_stream)); + else + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(m_stream)); + m_paused = paused; } void SDLAudioStream::CloseDevice() { - SDL_CloseAudioDevice(m_device_id); - m_device_id = 0; + SDL_DestroyAudioStream(m_stream); + m_stream = nullptr; } -void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len) +void SDLAudioStream::AudioCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount) { - SDLAudioStream* const this_ptr = static_cast(userdata); - const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_output_channels; + if (additional_amount > 0) + { + SDLAudioStream* const this_ptr = static_cast(userdata); - this_ptr->ReadFrames(reinterpret_cast(stream), num_frames); + const u32 num_frames = additional_amount / sizeof(SampleType) / this_ptr->m_output_channels; + SampleType* buffer = SDL_stack_alloc(SampleType, additional_amount / sizeof(SampleType)); + if (buffer) + { + this_ptr->ReadFrames(buffer, num_frames); + SDL_PutAudioStreamData(stream, buffer, additional_amount); + SDL_stack_free(buffer); + } + } } diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 64f319b2b5..fdbd5f6189 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -1589,7 +1589,9 @@ void FullscreenUI::DrawInputBindingButton( return; if (oneline) - InputManager::PrettifyInputBinding(value); + InputManager::PrettifyInputBinding(value, true); + else + InputManager::PrettifyInputBinding(value, false); if (show_type) { @@ -4461,11 +4463,11 @@ void FullscreenUI::DrawControllerSettingsPage() DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Enable SDL Input Source"), FSUI_CSTR("The SDL input source supports most controllers."), "InputSources", "SDL", true, true, false); DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_WIFI, "SDL DualShock 4 / DualSense Enhanced Mode"), - FSUI_CSTR("Provides vibration and LED control support over Bluetooth."), "InputSources", "SDLControllerEnhancedMode", false, + FSUI_CSTR("Provides vibration and LED control support over Bluetooth."), "InputSources", "SDLControllerEnhancedMode", true, bsi->GetBoolValue("InputSources", "SDL", true), false); DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIGHTBULB, "SDL DualSense Player LED"), - FSUI_CSTR("Enable/Disable the Player LED on DualSense controllers."), "InputSources", "SDLPS5PlayerLED", false, - bsi->GetBoolValue("InputSources", "SDLControllerEnhancedMode", true), false); + FSUI_CSTR("Enable/Disable the Player LED on DualSense controllers."), "InputSources", "SDLPS5PlayerLED", true, + bsi->GetBoolValue("InputSources", "SDLControllerEnhancedMode", true), true); #ifdef _WIN32 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "SDL Raw Input"), FSUI_CSTR("Allow SDL to use raw access to input devices."), "InputSources", "SDLRawInput", false, bsi->GetBoolValue("InputSources", "SDL", true), false); diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index 975818ae7c..41c3963017 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -489,7 +489,7 @@ bool ImGuiManager::AddIconFonts(float size) { // clang-format off static constexpr ImWchar range_fa[] = { 0xe06f,0xe06f,0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf023,0xf025,0xf028,0xf02b,0xf02b,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04b,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf05a,0xf05a,0xf05e,0xf05e,0xf063,0xf063,0xf067,0xf067,0xf06a,0xf06a,0xf06e,0xf06e,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf084,0xf091,0xf091,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c8,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf11b,0xf11c,0xf120,0xf121,0xf129,0xf12a,0xf140,0xf140,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf187,0xf188,0xf191,0xf192,0xf1b3,0xf1b3,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf21e,0xf21e,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2bd,0xf2bd,0xf2db,0xf2db,0xf2f2,0xf2f2,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf4e2,0xf4e2,0xf51f,0xf51f,0xf545,0xf545,0xf54c,0xf54c,0xf553,0xf553,0xf56d,0xf56d,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf70e,0xf70e,0xf756,0xf756,0xf780,0xf780,0xf794,0xf794,0xf815,0xf815,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 }; - static constexpr ImWchar range_pf[] = { 0x2198,0x2199,0x219e,0x21a3,0x21b0,0x21b3,0x21ba,0x21c3,0x21ce,0x21ce,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21f3,0x21f3,0x21f7,0x21f8,0x21fa,0x21fb,0x221a,0x221a,0x227a,0x227d,0x22bf,0x22c8,0x2349,0x2349,0x235a,0x235e,0x2360,0x2361,0x2364,0x2367,0x237a,0x237b,0x237d,0x237d,0x237f,0x237f,0x23b2,0x23b5,0x23cc,0x23cc,0x23f4,0x23f7,0x2427,0x243a,0x243d,0x243d,0x2443,0x2443,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2605,0x2605,0x2699,0x2699,0x278a,0x278e,0xe001,0xe001,0xff21,0xff3a,0x0,0x0 }; + static constexpr ImWchar range_pf[] = { 0x2198,0x2199,0x219e,0x21a7,0x21b0,0x21b3,0x21ba,0x21c3,0x21ce,0x21ce,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21e6,0x21e8,0x21f3,0x21f3,0x21f7,0x21f8,0x21fa,0x21fb,0x2206,0x2208,0x221a,0x221a,0x227a,0x227d,0x22bf,0x22c8,0x2349,0x2349,0x235a,0x235e,0x2360,0x2361,0x2364,0x2367,0x237a,0x237b,0x237d,0x237d,0x237f,0x237f,0x23b2,0x23b5,0x23cc,0x23cc,0x23f4,0x23f7,0x2427,0x243a,0x243d,0x243d,0x2443,0x2443,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2605,0x2605,0x2699,0x2699,0x278a,0x278e,0xe000,0xe001,0xff21,0xff3a,0x0,0x0 }; // clang-format on { diff --git a/pcsx2/Input/DInputSource.cpp b/pcsx2/Input/DInputSource.cpp index aee9ad83b4..71420b1c0e 100644 --- a/pcsx2/Input/DInputSource.cpp +++ b/pcsx2/Input/DInputSource.cpp @@ -45,6 +45,17 @@ std::string DInputSource::GetDeviceIdentifier(u32 index) return fmt::format("DInput-{}", index); } +static constexpr const char* s_dinput_axis_names[] = { + "X Axis", + "Y Axis", + "Z Axis", + "Z Rotation", + "X Rotation", + "Y Rotation", + "Slider 1", + "Slider 2", +}; + static constexpr std::array s_hat_directions = {{"Up", "Down", "Left", "Right"}}; bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock& settings_lock) @@ -150,6 +161,15 @@ void DInputSource::Shutdown() GetDeviceIdentifier(index)); m_controllers.pop_back(); } + + m_toplevel_window = nullptr; + m_dinput.reset(); + m_dinput_module.reset(); +} + +bool DInputSource::IsInitialized() +{ + return m_toplevel_window; } bool DInputSource::AddDevice(ControllerData& cd, const std::string& name) @@ -379,7 +399,7 @@ std::optional DInputSource::ParseKeyString(const std::string_vi return std::nullopt; } -TinyString DInputSource::ConvertKeyToString(InputBindingKey key) +TinyString DInputSource::ConvertKeyToString(InputBindingKey key, bool display, bool migration) { TinyString ret; @@ -387,18 +407,33 @@ TinyString DInputSource::ConvertKeyToString(InputBindingKey key) { if (key.source_subtype == InputSubclass::ControllerAxis) { - const char* modifier = (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+")); - ret.format("DInput-{}/{}Axis{}{}", u32(key.source_index), modifier, u32(key.data), (key.invert && !ShouldIgnoreInversion()) ? "~" : ""); + const char* modifier = (key.modifier == InputModifier::FullAxis ? (display ? "Full " : "Full") : (key.modifier == InputModifier::Negate ? "-" : "+")); + if (display) + { + if (key.data < std::size(s_dinput_axis_names)) + ret.format("DInput-{} {}{}{}", u32(key.source_index), modifier, s_dinput_axis_names[key.data], key.invert ? "~" : ""); + else + ret.format("DInput-{} {}Axis {}{}", u32(key.source_index), modifier, u32(key.data) + 1, key.invert ? "~" : ""); + } + else + ret.format("DInput-{}/{}Axis{}{}", u32(key.source_index), modifier, u32(key.data), (key.invert && (migration || !ShouldIgnoreInversion())) ? "~" : ""); } else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS) { + // Note, hats currently get mapped to buttons const u32 hat_num = (key.data - MAX_NUM_BUTTONS) / NUM_HAT_DIRECTIONS; const u32 hat_dir = (key.data - MAX_NUM_BUTTONS) % NUM_HAT_DIRECTIONS; - ret.format("DInput-{}/Hat{}{}", u32(key.source_index), hat_num, s_hat_directions[hat_dir]); + if (display) + ret.format("DInput-{} Hat {} {}", u32(key.source_index), hat_num + 1, s_hat_directions[hat_dir]); + else + ret.format("DInput-{}/Hat{}{}", u32(key.source_index), hat_num, s_hat_directions[hat_dir]); } else if (key.source_subtype == InputSubclass::ControllerButton) { - ret.format("DInput-{}/Button{}", u32(key.source_index), u32(key.data)); + if (display) + ret.format("DInput-{} Button {}", u32(key.source_index), u32(key.data) + 1); + else + ret.format("DInput-{}/Button{}", u32(key.source_index), u32(key.data)); } } diff --git a/pcsx2/Input/DInputSource.h b/pcsx2/Input/DInputSource.h index b210004044..9bd4f89f2f 100644 --- a/pcsx2/Input/DInputSource.h +++ b/pcsx2/Input/DInputSource.h @@ -38,6 +38,7 @@ public: void UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock) override; bool ReloadDevices() override; void Shutdown() override; + bool IsInitialized() override; void PollEvents() override; std::vector> EnumerateDevices() override; @@ -47,7 +48,7 @@ public: void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; std::optional ParseKeyString(const std::string_view device, const std::string_view binding) override; - TinyString ConvertKeyToString(InputBindingKey key) override; + TinyString ConvertKeyToString(InputBindingKey key, bool display = false, bool migration = false) override; TinyString ConvertKeyToIcon(InputBindingKey key) override; private: diff --git a/pcsx2/Input/InputManager.cpp b/pcsx2/Input/InputManager.cpp index 8929a4a90f..511afd3444 100644 --- a/pcsx2/Input/InputManager.cpp +++ b/pcsx2/Input/InputManager.cpp @@ -8,6 +8,7 @@ #include "SIO/Sio.h" #include "USB/USB.h" #include "VMManager.h" +#include "LayeredSettingsInterface.h" #include "common/Assertions.h" #include "common/Console.h" @@ -92,10 +93,15 @@ namespace InputManager static std::optional ParseHostKeyboardKey(const std::string_view source, const std::string_view sub_binding); static std::optional ParsePointerKey(const std::string_view source, const std::string_view sub_binding); + static TinyString ConvertKeyboardKeyToString(InputBindingKey key, bool display = false); + static TinyString ConvertPointerKeyToString(InputBindingKey key, bool display = false); + static bool SplitBinding(const std::string_view binding, std::string_view* source, std::string_view* sub_binding); - static void PrettifyInputBindingPart(const std::string_view binding, SmallString& ret, bool& changed); - static void AddBinding(const std::string_view binding, const InputEventHandler& handler); - static void AddBindings(const std::vector& bindings, const InputEventHandler& handler); + static void PrettifyInputBindingPart(const std::string_view binding, SmallString& ret, bool& changed, bool use_icons); + static std::shared_ptr AddBinding(const std::string_view binding, const InputEventHandler& handler); + // Will also apply SDL2-SDL3 migrations and update the provided section & key + static void AddBindings(const std::vector& bindings, const InputEventHandler& handler, + InputBindingInfo::Type binding_type, SettingsInterface& si, const char* section, const char* key); static bool ParseBindingAndGetSource(const std::string_view binding, InputBindingKey* key, InputSource** source); static bool IsAxisHandler(const InputEventHandler& handler); @@ -142,9 +148,12 @@ static const HotkeyInfo* const s_hotkey_list[] = {g_common_hotkeys, g_gs_hotkeys // Tracking host mouse movement and turning into relative events // 4 axes: pointer left/right, wheel vertical/horizontal. Last/Next/Normalized. // ------------------------------------------------------------------------ -static constexpr const std::array(InputPointerAxis::Count)> s_pointer_axis_names = { +static constexpr const std::array(InputPointerAxis::Count)> s_pointer_axis_setting_names = { {"X", "Y", "WheelX", "WheelY"}}; -static constexpr const std::array s_pointer_button_names = {{"LeftButton", "RightButton", "MiddleButton"}}; +static constexpr const std::array(InputPointerAxis::Count)> s_pointer_axis_names = { + {"X", "Y", "Wheel X", "Wheel Y"}}; +static constexpr const std::array s_pointer_button_setting_names = {{"LeftButton", "RightButton", "MiddleButton"}}; +static constexpr const std::array s_pointer_button_names = {{"Left Button", "Right Button", "Middle Button"}}; struct PointerAxisState { @@ -232,7 +241,7 @@ std::optional InputManager::ParseInputBindingKey(const std::str { for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i]) + if (s_input_sources[i]->IsInitialized()) { std::optional key = s_input_sources[i]->ParseKeyString(source, sub_binding); if (key.has_value()) @@ -252,7 +261,7 @@ bool InputManager::ParseBindingAndGetSource(const std::string_view binding, Inpu for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i]) + if (s_input_sources[i]->IsInitialized()) { std::optional parsed_key = s_input_sources[i]->ParseKeyString(source_string, sub_binding); if (parsed_key.has_value()) @@ -267,7 +276,62 @@ bool InputManager::ParseBindingAndGetSource(const std::string_view binding, Inpu return false; } -std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key) +TinyString InputManager::ConvertKeyboardKeyToString(InputBindingKey key, bool display) +{ + TinyString ret; + + if (key.source_type == InputSourceType::Keyboard) + { + const std::optional str(ConvertHostKeyboardCodeToString(key.data)); + if (str.has_value() && !str->empty()) + if (display) + // Keyboard keys arn't spaced out for display yet + ret.format("Keyboard {}", str->c_str()); + else + ret.format("Keyboard/{}", str->c_str()); + } + + return ret; +} + +TinyString InputManager::ConvertPointerKeyToString(InputBindingKey key, bool display) +{ + TinyString ret; + + if (key.source_type == InputSourceType::Pointer) + { + if (key.source_subtype == InputSubclass::PointerButton) + { + if (display) + { + if (key.data < s_pointer_button_setting_names.size()) + ret.format("Pointer-{} {}", u32{key.source_index}, s_pointer_button_names[key.data]); + else + ret.format("Pointer-{} Button{}", u32{key.source_index}, key.data + 1); + } + else + { + if (key.data < s_pointer_button_setting_names.size()) + ret.format("Pointer-{}/{}", u32{key.source_index}, s_pointer_button_setting_names[key.data]); + else + ret.format("Pointer-{}/Button{}", u32{key.source_index}, key.data); + } + } + else if (key.source_subtype == InputSubclass::PointerAxis) + { + if (display) + ret.format("Pointer-{} {}{:c}", u32{key.source_index}, s_pointer_axis_setting_names[key.data], + key.modifier == InputModifier::Negate ? '-' : '+'); + else + ret.format("Pointer-{}/{}{:c}", u32{key.source_index}, s_pointer_axis_setting_names[key.data], + key.modifier == InputModifier::Negate ? '-' : '+'); + } + } + + return ret; +} + +std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key, bool migration) { if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::Device) { @@ -294,48 +358,35 @@ std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type { if (key.source_type == InputSourceType::Keyboard) { - const std::optional str(ConvertHostKeyboardCodeToString(key.data)); - if (str.has_value() && !str->empty()) - return fmt::format("Keyboard/{}", str->c_str()); + return std::string(ConvertKeyboardKeyToString(key)); } else if (key.source_type == InputSourceType::Pointer) { - if (key.source_subtype == InputSubclass::PointerButton) - { - if (key.data < s_pointer_button_names.size()) - return fmt::format("Pointer-{}/{}", u32{key.source_index}, s_pointer_button_names[key.data]); - else - return fmt::format("Pointer-{}/Button{}", u32{key.source_index}, key.data); - } - else if (key.source_subtype == InputSubclass::PointerAxis) - { - return fmt::format("Pointer-{}/{}{:c}", u32{key.source_index}, s_pointer_axis_names[key.data], - key.modifier == InputModifier::Negate ? '-' : '+'); - } + return std::string(ConvertPointerKeyToString(key)); } else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast(key.source_type)]) { - return std::string(s_input_sources[static_cast(key.source_type)]->ConvertKeyToString(key)); + return std::string(s_input_sources[static_cast(key.source_type)]->ConvertKeyToString(key, false, migration)); } } return {}; } -std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type, const InputBindingKey* keys, size_t num_keys) +std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type, const InputBindingKey* keys, size_t num_keys, bool migration) { // can't have a chord of devices/pointers if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::Device) { // so only take the first if (num_keys > 0) - return ConvertInputBindingKeyToString(binding_type, keys[0]); + return ConvertInputBindingKeyToString(binding_type, keys[0], migration); } std::stringstream ss; for (size_t i = 0; i < num_keys; i++) { - const std::string keystr(ConvertInputBindingKeyToString(binding_type, keys[i])); + const std::string keystr(ConvertInputBindingKeyToString(binding_type, keys[i], migration)); if (keystr.empty()) return std::string(); @@ -348,7 +399,7 @@ std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type return ss.str(); } -bool InputManager::PrettifyInputBinding(SmallStringBase& binding) +bool InputManager::PrettifyInputBinding(SmallStringBase& binding, bool use_icons) { if (binding.empty()) return false; @@ -369,7 +420,7 @@ bool InputManager::PrettifyInputBinding(SmallStringBase& binding) { if (!ret.empty()) ret.append(" + "); - PrettifyInputBindingPart(part, ret, changed); + PrettifyInputBindingPart(part, ret, changed, use_icons); } } last = next + 1; @@ -381,7 +432,7 @@ bool InputManager::PrettifyInputBinding(SmallStringBase& binding) { if (!ret.empty()) ret.append(" + "); - PrettifyInputBindingPart(part, ret, changed); + PrettifyInputBindingPart(part, ret, changed, use_icons); } } @@ -391,7 +442,7 @@ bool InputManager::PrettifyInputBinding(SmallStringBase& binding) return changed; } -void InputManager::PrettifyInputBindingPart(const std::string_view binding, SmallString& ret, bool& changed) +void InputManager::PrettifyInputBindingPart(const std::string_view binding, SmallString& ret, bool& changed, bool use_icons) { std::string_view source, sub_binding; if (!SplitBinding(binding, &source, &sub_binding)) @@ -401,12 +452,30 @@ void InputManager::PrettifyInputBindingPart(const std::string_view binding, Smal if (source.starts_with("Keyboard")) { std::optional key = ParseHostKeyboardKey(source, sub_binding); - const char* icon = key.has_value() ? ConvertHostKeyboardCodeToIcon(key->data) : nullptr; - if (icon) + if (key.has_value()) { - ret.append(icon); - changed = true; - return; + if (use_icons) + { + const char* icon = ConvertHostKeyboardCodeToIcon(key->data); + if (icon) + { + ret.append(icon); + changed = true; + return; + } + else + { + ret.append(ConvertKeyboardKeyToString(key.value(), true)); + changed = true; + return; + } + } + else + { + ret.append(ConvertKeyboardKeyToString(key.value(), true)); + changed = true; + return; + } } } else if (source.starts_with("Pointer")) @@ -414,7 +483,7 @@ void InputManager::PrettifyInputBindingPart(const std::string_view binding, Smal const std::optional key = ParsePointerKey(source, sub_binding); if (key.has_value()) { - if (key->source_subtype == InputSubclass::PointerButton) + if (use_icons && key->source_subtype == InputSubclass::PointerButton) { static constexpr const char* button_icons[] = { ICON_PF_MOUSE_BUTTON_1, @@ -424,32 +493,41 @@ void InputManager::PrettifyInputBindingPart(const std::string_view binding, Smal ICON_PF_MOUSE_BUTTON_5, }; if (key->data < std::size(button_icons)) - { ret.append(button_icons[key->data]); - changed = true; - return; - } + else + ret.append(ConvertPointerKeyToString(key.value(), true)); } + else + ret.append(ConvertPointerKeyToString(key.value(), true)); + + changed = true; + return; } } else { for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { + // We call ConvertKeyToIcon/String() even on disabled sources + // This ensures consistant appearance between enabled and disabled sources if (s_input_sources[i]) { std::optional key = s_input_sources[i]->ParseKeyString(source, sub_binding); if (key.has_value()) { - const TinyString icon = s_input_sources[i]->ConvertKeyToIcon(key.value()); - if (!icon.empty()) + if (use_icons) { - ret.append(icon); - changed = true; - return; + const TinyString icon = s_input_sources[i]->ConvertKeyToIcon(key.value()); + if (!icon.empty()) + ret.append(icon); + else + ret.append(s_input_sources[i]->ConvertKeyToString(key.value(), true)); } + else + ret.append(s_input_sources[i]->ConvertKeyToString(key.value(), true)); - break; + changed = true; + return; } } } @@ -459,7 +537,7 @@ void InputManager::PrettifyInputBindingPart(const std::string_view binding, Smal } -void InputManager::AddBinding(const std::string_view binding, const InputEventHandler& handler) +std::shared_ptr InputManager::AddBinding(const std::string_view binding, const InputEventHandler& handler) { std::shared_ptr ibinding; const std::vector chord_bindings(SplitChord(binding)); @@ -493,17 +571,65 @@ void InputManager::AddBinding(const std::string_view binding, const InputEventHa } if (!ibinding) - return; + return nullptr; // plop it in the input map for all the keys for (u32 i = 0; i < ibinding->num_keys; i++) s_binding_map.emplace(ibinding->keys[i].MaskDirection(), ibinding); + + return ibinding; } -void InputManager::AddBindings(const std::vector& bindings, const InputEventHandler& handler) +void InputManager::AddBindings(const std::vector& bindings, const InputEventHandler& handler, + InputBindingInfo::Type binding_type, SettingsInterface& si, const char* section, const char* key) { + std::vector> ibindings; + + bool migrate = false; for (const std::string& binding : bindings) - AddBinding(binding, handler); + { + std::shared_ptr ibinding = AddBinding(binding, handler); + ibindings.push_back(ibinding); + + if (ibinding) + { + // Check for SDL2-3 migrations + for (u32 i = 0; i < ibinding->num_keys; i++) + { + if (ibinding->keys[i].needs_migration) + migrate = true; + } + } + } + + // Save migrations + if (migrate) + { + std::vector new_bindings; + new_bindings.reserve(bindings.size()); + + for (int i = 0; i < bindings.size(); i++) + { + if (ibindings[i]) + new_bindings.push_back(ConvertInputBindingKeysToString(binding_type, ibindings[i]->keys, ibindings[i]->num_keys, true)); + else + // Retain invalid bindings as is + new_bindings.push_back(bindings[i]); + } + + // Need to find where our binding came from + LayeredSettingsInterface& lsi = static_cast(si); + for (int i = 0; i < LayeredSettingsInterface::NUM_LAYERS; i++) + { + SettingsInterface* layer = lsi.GetLayer(static_cast(i)); + if (layer && layer->GetStringList(section, key) == bindings) + { + // Layer found, update settings + layer->SetStringList(section, key, new_bindings); + layer->Save(); + } + } + } } // ------------------------------------------------------------------------ @@ -631,14 +757,14 @@ std::optional InputManager::ParsePointerKey(const std::string_v return key; } - for (u32 i = 0; i < s_pointer_axis_names.size(); i++) + for (u32 i = 0; i < s_pointer_axis_setting_names.size(); i++) { - if (sub_binding.starts_with(s_pointer_axis_names[i])) + if (sub_binding.starts_with(s_pointer_axis_setting_names[i])) { key.source_subtype = InputSubclass::PointerAxis; key.data = i; - const std::string_view dir_part(sub_binding.substr(std::strlen(s_pointer_axis_names[i]))); + const std::string_view dir_part(sub_binding.substr(std::strlen(s_pointer_axis_setting_names[i]))); if (dir_part == "+") key.modifier = InputModifier::None; else if (dir_part == "-") @@ -650,9 +776,9 @@ std::optional InputManager::ParsePointerKey(const std::string_v } } - for (u32 i = 0; i < s_pointer_button_names.size(); i++) + for (u32 i = 0; i < s_pointer_button_setting_names.size(); i++) { - if (sub_binding == s_pointer_button_names[i]) + if (sub_binding == s_pointer_button_setting_names[i]) { key.source_subtype = InputSubclass::PointerButton; key.data = i; @@ -711,7 +837,7 @@ void InputManager::AddHotkeyBindings(SettingsInterface& si) if (bindings.empty()) continue; - AddBindings(bindings, InputButtonEventHandler{hotkey->handler}); + AddBindings(bindings, InputButtonEventHandler{hotkey->handler}, InputBindingInfo::Type::Button, si, "Hotkeys", hotkey->name); } } } @@ -753,7 +879,8 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index) AddBindings( bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index, sensitivity, deadzone](InputBindingKey key, float value) { Pad::SetControllerState(pad_index, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value)); - }}); + }}, + bi.bind_type, si, section.c_str(), bi.name); } } break; @@ -771,10 +898,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index) if (!bindings.empty()) { const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("Macro{}Deadzone", macro_button_index + 1).c_str(), 0.0f); - AddBindings(bindings, InputAxisEventHandler{[pad_index, macro_button_index, deadzone](InputBindingKey key, float value) { - const bool state = (value > deadzone); - Pad::SetMacroButtonState(key, pad_index, macro_button_index, state); - }}); + AddBindings( + bindings, InputAxisEventHandler{[pad_index, macro_button_index, deadzone](InputBindingKey key, float value) { + const bool state = (value > deadzone); + Pad::SetMacroButtonState(key, pad_index, macro_button_index, state); + }}, + InputBindingInfo::Type::Macro, si, section.c_str(), fmt::format("Macro{}", macro_button_index + 1).c_str()); } } @@ -835,9 +964,11 @@ void InputManager::AddUSBBindings(SettingsInterface& si, u32 port) { const float sensitivity = si.GetFloatValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str(), 1.0f); const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f); - AddBindings(bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index, sensitivity, deadzone](InputBindingKey key, float value) { - USB::SetDeviceBindValue(port, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value)); - }}); + AddBindings( + bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index, sensitivity, deadzone](InputBindingKey key, float value) { + USB::SetDeviceBindValue(port, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value)); + }}, + bi.bind_type, si, section.c_str(), bind_name.c_str()); } } break; @@ -1414,10 +1545,10 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind constexpr float pointer_sensitivity = 0.05f; for (u32 axis = 0; axis <= static_cast(InputPointerAxis::Y); axis++) { - s_pointer_axis_speed[axis] = si.GetFloatValue("Pad", fmt::format("Pointer{}Speed", s_pointer_axis_names[axis]).c_str(), 40.0f) / + s_pointer_axis_speed[axis] = si.GetFloatValue("Pad", fmt::format("Pointer{}Speed", s_pointer_axis_setting_names[axis]).c_str(), 40.0f) / ui_ctrl_range * pointer_sensitivity; s_pointer_axis_dead_zone[axis] = std::min( - si.GetFloatValue("Pad", fmt::format("Pointer{}DeadZone", s_pointer_axis_names[axis]).c_str(), 20.0f) / ui_ctrl_range, 1.0f); + si.GetFloatValue("Pad", fmt::format("Pointer{}DeadZone", s_pointer_axis_setting_names[axis]).c_str(), 20.0f) / ui_ctrl_range, 1.0f); s_pointer_axis_range[axis] = 1.0f - s_pointer_axis_dead_zone[axis]; } s_pointer_inertia = si.GetFloatValue("Pad", "PointerInertia", 10.0f) / ui_ctrl_range; @@ -1461,7 +1592,7 @@ bool InputManager::ReloadDevices() for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i]) + if (s_input_sources[i]->IsInitialized()) changed |= s_input_sources[i]->ReloadDevices(); } @@ -1472,11 +1603,11 @@ void InputManager::CloseSources() { for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i]) + if (s_input_sources[i]->IsInitialized()) { s_input_sources[i]->Shutdown(); - s_input_sources[i].reset(); } + s_input_sources[i].reset(); } } @@ -1484,7 +1615,7 @@ void InputManager::PollSources() { for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i]) + if (s_input_sources[i]->IsInitialized()) s_input_sources[i]->PollEvents(); } @@ -1504,7 +1635,7 @@ std::vector> InputManager::EnumerateDevices( for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i]) + if (s_input_sources[i]->IsInitialized()) { std::vector> devs(s_input_sources[i]->EnumerateDevices()); if (ret.empty()) @@ -1523,7 +1654,7 @@ std::vector InputManager::EnumerateMotors() for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i]) + if (s_input_sources[i]->IsInitialized()) { std::vector devs(s_input_sources[i]->EnumerateMotors()); if (ret.empty()) @@ -1583,7 +1714,7 @@ InputManager::GenericInputBindingMapping InputManager::GetGenericBindingMapping( { for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { - if (s_input_sources[i] && s_input_sources[i]->GetGenericBindingMapping(device, &mapping)) + if (s_input_sources[i]->IsInitialized() && s_input_sources[i]->GetGenericBindingMapping(device, &mapping)) break; } } @@ -1599,34 +1730,34 @@ bool InputManager::IsInputSourceEnabled(SettingsInterface& si, InputSourceType t template void InputManager::UpdateInputSourceState(SettingsInterface& si, std::unique_lock& settings_lock, InputSourceType type) { + if (!s_input_sources[static_cast(type)]) + { + std::unique_ptr source = std::make_unique(); + if (!source->Initialize(si, settings_lock)) + Console.Error("(InputManager) Source '%s' failed to initialize.", InputSourceToString(type)); + + s_input_sources[static_cast(type)] = std::move(source); + } + const bool enabled = IsInputSourceEnabled(si, type); if (enabled) { - if (s_input_sources[static_cast(type)]) + if (s_input_sources[static_cast(type)]->IsInitialized()) { s_input_sources[static_cast(type)]->UpdateSettings(si, settings_lock); } - else + else if (!s_input_sources[static_cast(type)]->Initialize(si, settings_lock)) { - std::unique_ptr source = std::make_unique(); - if (!source->Initialize(si, settings_lock)) - { - Console.Error("(InputManager) Source '%s' failed to initialize.", InputSourceToString(type)); - return; - } - - s_input_sources[static_cast(type)] = std::move(source); + Console.Error("(InputManager) Source '%s' failed to initialize.", InputSourceToString(type)); } } else { - if (s_input_sources[static_cast(type)]) + if (s_input_sources[static_cast(type)]->IsInitialized()) { settings_lock.unlock(); s_input_sources[static_cast(type)]->Shutdown(); settings_lock.lock(); - - s_input_sources[static_cast(type)].reset(); } } } diff --git a/pcsx2/Input/InputManager.h b/pcsx2/Input/InputManager.h index af27fafb5b..e21e0adf3c 100644 --- a/pcsx2/Input/InputManager.h +++ b/pcsx2/Input/InputManager.h @@ -63,7 +63,8 @@ union InputBindingKey InputSubclass source_subtype : 3; ///< if 1, binding is for an axis and not a button (used for controllers) InputModifier modifier : 2; u32 invert : 1; ///< if 1, value is inverted prior to being sent to the sink - u32 unused : 14; + u32 needs_migration : 1; //< Used for SDL2-3 Migration, binding should be saved to complete migration + u32 unused : 13; u32 data; }; @@ -80,6 +81,7 @@ union InputBindingKey r.bits = bits; r.modifier = InputModifier::None; r.invert = 0; + r.needs_migration = false; return r; } }; @@ -203,13 +205,13 @@ namespace InputManager std::optional ParseInputBindingKey(const std::string_view binding); /// Converts a input key to a string. - std::string ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key); + std::string ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key, bool migration = false); /// Converts a chord of binding keys to a string. - std::string ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type, const InputBindingKey* keys, size_t num_keys); + std::string ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type, const InputBindingKey* keys, size_t num_keys, bool migration = false); /// Represents a binding with icon fonts, if available. - bool PrettifyInputBinding(SmallStringBase& binding); + bool PrettifyInputBinding(SmallStringBase& binding, bool use_icons = true); /// Splits a chord into individual bindings. std::vector SplitChord(const std::string_view binding); diff --git a/pcsx2/Input/InputSource.h b/pcsx2/Input/InputSource.h index 7c8e215c26..c5ecf6bcee 100644 --- a/pcsx2/Input/InputSource.h +++ b/pcsx2/Input/InputSource.h @@ -24,11 +24,13 @@ public: virtual void UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock) = 0; virtual bool ReloadDevices() = 0; virtual void Shutdown() = 0; + virtual bool IsInitialized() = 0; virtual void PollEvents() = 0; + /// InputBinding functions can be called while uninitialized virtual std::optional ParseKeyString(const std::string_view device, const std::string_view binding) = 0; - virtual TinyString ConvertKeyToString(InputBindingKey key) = 0; + virtual TinyString ConvertKeyToString(InputBindingKey key, bool display = false, bool migration = false) = 0; virtual TinyString ConvertKeyToIcon(InputBindingKey key) = 0; /// Enumerates available devices. Returns a pair of the prefix (e.g. SDL-0) and the device name. diff --git a/pcsx2/Input/SDLInputSource.cpp b/pcsx2/Input/SDLInputSource.cpp index 7a0a8cf77e..af676045a6 100644 --- a/pcsx2/Input/SDLInputSource.cpp +++ b/pcsx2/Input/SDLInputSource.cpp @@ -17,96 +17,353 @@ #include #include +#include static constexpr const char* CONTROLLER_DB_FILENAME = "game_controller_db.txt"; +static constexpr const char* s_sdl_axis_setting_names[] = { + "LeftX", // SDL_GAMEPAD_AXIS_LEFTX + "LeftY", // SDL_GAMEPAD_AXIS_LEFTY + "RightX", // SDL_GAMEPAD_AXIS_RIGHTX + "RightY", // SDL_GAMEPAD_AXIS_RIGHTY + "LeftTrigger", // SDL_GAMEPAD_AXIS_LEFT_TRIGGER + "RightTrigger", // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +}; +static_assert(std::size(s_sdl_axis_setting_names) == SDL_GAMEPAD_AXIS_COUNT); + static constexpr const char* s_sdl_axis_names[] = { - "LeftX", // SDL_CONTROLLER_AXIS_LEFTX - "LeftY", // SDL_CONTROLLER_AXIS_LEFTY - "RightX", // SDL_CONTROLLER_AXIS_RIGHTX - "RightY", // SDL_CONTROLLER_AXIS_RIGHTY - "LeftTrigger", // SDL_CONTROLLER_AXIS_TRIGGERLEFT - "RightTrigger", // SDL_CONTROLLER_AXIS_TRIGGERRIGHT -}; -static constexpr const char* s_sdl_axis_icons[][2] = { - {ICON_PF_LEFT_ANALOG_LEFT, ICON_PF_LEFT_ANALOG_RIGHT}, // SDL_CONTROLLER_AXIS_LEFTX - {ICON_PF_LEFT_ANALOG_UP, ICON_PF_LEFT_ANALOG_DOWN}, // SDL_CONTROLLER_AXIS_LEFTY - {ICON_PF_RIGHT_ANALOG_LEFT, ICON_PF_RIGHT_ANALOG_RIGHT}, // SDL_CONTROLLER_AXIS_RIGHTX - {ICON_PF_RIGHT_ANALOG_UP, ICON_PF_RIGHT_ANALOG_DOWN}, // SDL_CONTROLLER_AXIS_RIGHTY - {nullptr, ICON_PF_LEFT_TRIGGER_PULL}, // SDL_CONTROLLER_AXIS_TRIGGERLEFT - {nullptr, ICON_PF_RIGHT_TRIGGER_PULL}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT -}; -static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { - {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_CONTROLLER_AXIS_LEFTX - {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_CONTROLLER_AXIS_LEFTY - {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_CONTROLLER_AXIS_RIGHTX - {GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // SDL_CONTROLLER_AXIS_RIGHTY - {GenericInputBinding::Unknown, GenericInputBinding::L2}, // SDL_CONTROLLER_AXIS_TRIGGERLEFT - {GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT + "Left X", // SDL_GAMEPAD_AXIS_LEFTX + "Left Y", // SDL_GAMEPAD_AXIS_LEFTY + "Right X", // SDL_GAMEPAD_AXIS_RIGHTX + "Right Y", // SDL_GAMEPAD_AXIS_RIGHTY }; +static constexpr const char* s_sdl_trigger_names[] = { + "Left Trigger", // SDL_GAMEPAD_AXIS_LEFT_TRIGGER + "Right Trigger", // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +}; +static constexpr const char* s_sdl_trigger_ps_names[] = { + "L2", // SDL_GAMEPAD_AXIS_LEFT_TRIGGER + "R2", // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +}; + +static const char* const* s_sdl_trigger_names_list[] = { + s_sdl_trigger_names, // SDL_GAMEPAD_TYPE_UNKNOWN + s_sdl_trigger_names, // SDL_GAMEPAD_TYPE_STANDARD + s_sdl_trigger_names, // SDL_GAMEPAD_TYPE_XBOX360 + s_sdl_trigger_names, // SDL_GAMEPAD_TYPE_XBOXONE + s_sdl_trigger_ps_names, // SDL_GAMEPAD_TYPE_PS3 + s_sdl_trigger_ps_names, // SDL_GAMEPAD_TYPE_PS4 + s_sdl_trigger_ps_names, // SDL_GAMEPAD_TYPE_PS5 + // Switch +}; + +static constexpr const char* s_sdl_axis_icons[][2] = { + {ICON_PF_LEFT_ANALOG_LEFT, ICON_PF_LEFT_ANALOG_RIGHT}, // SDL_GAMEPAD_AXIS_LEFTX + {ICON_PF_LEFT_ANALOG_UP, ICON_PF_LEFT_ANALOG_DOWN}, // SDL_GAMEPAD_AXIS_LEFTY + {ICON_PF_RIGHT_ANALOG_LEFT, ICON_PF_RIGHT_ANALOG_RIGHT}, // SDL_GAMEPAD_AXIS_RIGHTX + {ICON_PF_RIGHT_ANALOG_UP, ICON_PF_RIGHT_ANALOG_DOWN}, // SDL_GAMEPAD_AXIS_RIGHTY +}; + +static constexpr const char* s_sdl_trigger_icons[] = { + ICON_PF_LEFT_TRIGGER_PULL, // SDL_GAMEPAD_AXIS_LEFT_TRIGGER + ICON_PF_RIGHT_TRIGGER_PULL, // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +}; +static constexpr const char* s_sdl_trigger_ps_icons[] = { + ICON_PF_LEFT_TRIGGER_L2, // SDL_GAMEPAD_AXIS_LEFT_TRIGGER + ICON_PF_RIGHT_TRIGGER_R2, // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +}; + +static const char* const* s_sdl_trigger_icons_list[] = { + s_sdl_trigger_icons, // SDL_GAMEPAD_TYPE_UNKNOWN + s_sdl_trigger_icons, // SDL_GAMEPAD_TYPE_STANDARD + s_sdl_trigger_icons, // SDL_GAMEPAD_TYPE_XBOX360 + s_sdl_trigger_icons, // SDL_GAMEPAD_TYPE_XBOXONE + s_sdl_trigger_ps_icons, // SDL_GAMEPAD_TYPE_PS3 + s_sdl_trigger_ps_icons, // SDL_GAMEPAD_TYPE_PS4 + s_sdl_trigger_ps_icons, // SDL_GAMEPAD_TYPE_PS5 + // Switch +}; + +static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { + {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_GAMEPAD_AXIS_LEFTX + {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_GAMEPAD_AXIS_LEFTY + {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_GAMEPAD_AXIS_RIGHTX + {GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // SDL_GAMEPAD_AXIS_RIGHTY + {GenericInputBinding::Unknown, GenericInputBinding::L2}, // SDL_GAMEPAD_AXIS_LEFT_TRIGGER + {GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER +}; + +static constexpr const char* s_sdl_button_setting_names[] = { + "FaceSouth", // SDL_GAMEPAD_BUTTON_SOUTH + "FaceEast", // SDL_GAMEPAD_BUTTON_EAST + "FaceWest", // SDL_GAMEPAD_BUTTON_WEST + "FaceNorth", // SDL_GAMEPAD_BUTTON_NORTH + "Back", // SDL_GAMEPAD_BUTTON_BACK + "Guide", // SDL_GAMEPAD_BUTTON_GUIDE + "Start", // SDL_GAMEPAD_BUTTON_START + "LeftStick", // SDL_GAMEPAD_BUTTON_LEFT_STICK + "RightStick", // SDL_GAMEPAD_BUTTON_RIGHT_STICK + "LeftShoulder", // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + "RightShoulder", // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + "DPadUp", // SDL_GAMEPAD_BUTTON_DPAD_UP + "DPadDown", // SDL_GAMEPAD_BUTTON_DPAD_DOWN + "DPadLeft", // SDL_GAMEPAD_BUTTON_DPAD_LEFT + "DPadRight", // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + "Misc1", // SDL_GAMEPAD_BUTTON_MISC1 + "Paddle1", // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 + "Paddle2", // SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 + "Paddle3", // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 + "Paddle4", // SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 + "Touchpad", // SDL_GAMEPAD_BUTTON_TOUCHPAD + "Misc2", // SDL_GAMEPAD_BUTTON_MISC2 + "Misc3", // SDL_GAMEPAD_BUTTON_MISC3 + "Misc4", // SDL_GAMEPAD_BUTTON_MISC4 + "Misc5", // SDL_GAMEPAD_BUTTON_MISC5 + "Misc6", // SDL_GAMEPAD_BUTTON_MISC6 +}; +static_assert(std::size(s_sdl_button_setting_names) == SDL_GAMEPAD_BUTTON_COUNT); + +static constexpr const char* s_sdl_face_button_names[] = { + nullptr, // SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN + "A", // SDL_GAMEPAD_BUTTON_LABEL_A + "B", // SDL_GAMEPAD_BUTTON_LABEL_B + "X", // SDL_GAMEPAD_BUTTON_LABEL_X + "Y", // SDL_GAMEPAD_BUTTON_LABEL_Y + "Cross", // SDL_GAMEPAD_BUTTON_LABEL_CROSS + "Circle", // SDL_GAMEPAD_BUTTON_LABEL_CIRCLE + "Square", // SDL_GAMEPAD_BUTTON_LABEL_SQUARE + "Triangle", // SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE +}; static constexpr const char* s_sdl_button_names[] = { - "A", // SDL_CONTROLLER_BUTTON_A - "B", // SDL_CONTROLLER_BUTTON_B - "X", // SDL_CONTROLLER_BUTTON_X - "Y", // SDL_CONTROLLER_BUTTON_Y - "Back", // SDL_CONTROLLER_BUTTON_BACK - "Guide", // SDL_CONTROLLER_BUTTON_GUIDE - "Start", // SDL_CONTROLLER_BUTTON_START - "LeftStick", // SDL_CONTROLLER_BUTTON_LEFTSTICK - "RightStick", // SDL_CONTROLLER_BUTTON_RIGHTSTICK - "LeftShoulder", // SDL_CONTROLLER_BUTTON_LEFTSHOULDER - "RightShoulder", // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER - "DPadUp", // SDL_CONTROLLER_BUTTON_DPAD_UP - "DPadDown", // SDL_CONTROLLER_BUTTON_DPAD_DOWN - "DPadLeft", // SDL_CONTROLLER_BUTTON_DPAD_LEFT - "DPadRight", // SDL_CONTROLLER_BUTTON_DPAD_RIGHT - "Misc1", // SDL_CONTROLLER_BUTTON_MISC1 - "Paddle1", // SDL_CONTROLLER_BUTTON_PADDLE1 - "Paddle2", // SDL_CONTROLLER_BUTTON_PADDLE2 - "Paddle3", // SDL_CONTROLLER_BUTTON_PADDLE3 - "Paddle4", // SDL_CONTROLLER_BUTTON_PADDLE4 - "Touchpad", // SDL_CONTROLLER_BUTTON_TOUCHPAD + "Face South", // SDL_GAMEPAD_BUTTON_SOUTH + "Face East", // SDL_GAMEPAD_BUTTON_EAST + "Face West", // SDL_GAMEPAD_BUTTON_WEST + "Face North", // SDL_GAMEPAD_BUTTON_NORTH + "Back", // SDL_GAMEPAD_BUTTON_BACK + "Guide", // SDL_GAMEPAD_BUTTON_GUIDE + "Start", // SDL_GAMEPAD_BUTTON_START + "Left Stick", // SDL_GAMEPAD_BUTTON_LEFT_STICK + "Right Stick", // SDL_GAMEPAD_BUTTON_RIGHT_STICK + "Left Shoulder", // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + "Right Shoulder", // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + "D-Pad Up", // SDL_GAMEPAD_BUTTON_DPAD_UP + "D-Pad Down", // SDL_GAMEPAD_BUTTON_DPAD_DOWN + "D-Pad Left", // SDL_GAMEPAD_BUTTON_DPAD_LEFT + "D-Pad Right", // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + "Misc 1", // SDL_GAMEPAD_BUTTON_MISC1 + "Paddle 1", // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 + "Paddle 2", // SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 + "Paddle 3", // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 + "Paddle 4", // SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 + "Touchpad", // SDL_GAMEPAD_BUTTON_TOUCHPAD + "Misc 2", // SDL_GAMEPAD_BUTTON_MISC2 + "Misc 3", // SDL_GAMEPAD_BUTTON_MISC3 + "Misc 4", // SDL_GAMEPAD_BUTTON_MISC4 + "Misc 5", // SDL_GAMEPAD_BUTTON_MISC5 + "Misc 6", // SDL_GAMEPAD_BUTTON_MISC6 +}; +static constexpr const char* s_sdl_button_ps3_names[] = { + "Cross", // SDL_GAMEPAD_BUTTON_SOUTH + "Circle", // SDL_GAMEPAD_BUTTON_EAST + "Square", // SDL_GAMEPAD_BUTTON_WEST + "Triangle", // SDL_GAMEPAD_BUTTON_NORTH + "Select", // SDL_GAMEPAD_BUTTON_BACK + "PS", // SDL_GAMEPAD_BUTTON_GUIDE + "Start", // SDL_GAMEPAD_BUTTON_START + "Left Stick", // SDL_GAMEPAD_BUTTON_LEFT_STICK + "Right Stick", // SDL_GAMEPAD_BUTTON_RIGHT_STICK + "L1", // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + "R1", // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER +}; +static constexpr const char* s_sdl_button_ps4_names[] = { + "Cross", // SDL_GAMEPAD_BUTTON_SOUTH + "Circle", // SDL_GAMEPAD_BUTTON_EAST + "Square", // SDL_GAMEPAD_BUTTON_WEST + "Triangle", // SDL_GAMEPAD_BUTTON_NORTH + "Share", // SDL_GAMEPAD_BUTTON_BACK + "PS", // SDL_GAMEPAD_BUTTON_GUIDE + "Options", // SDL_GAMEPAD_BUTTON_START + "Left Stick", // SDL_GAMEPAD_BUTTON_LEFT_STICK + "Right Stick", // SDL_GAMEPAD_BUTTON_RIGHT_STICK + "L1", // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + "R1", // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER +}; +static constexpr const char* s_sdl_button_ps5_names[] = { + "Cross", // SDL_GAMEPAD_BUTTON_SOUTH + "Circle", // SDL_GAMEPAD_BUTTON_EAST + "Square", // SDL_GAMEPAD_BUTTON_WEST + "Triangle", // SDL_GAMEPAD_BUTTON_NORTH + "Create", // SDL_GAMEPAD_BUTTON_BACK + "PS", // SDL_GAMEPAD_BUTTON_GUIDE + "Options", // SDL_GAMEPAD_BUTTON_START + "Left Stick", // SDL_GAMEPAD_BUTTON_LEFT_STICK + "Right Stick", // SDL_GAMEPAD_BUTTON_RIGHT_STICK + "L1", // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + "R1", // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + nullptr, // SDL_GAMEPAD_BUTTON_DPAD_UP + nullptr, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + nullptr, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + nullptr, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + "Mute", // SDL_GAMEPAD_BUTTON_MISC1 +}; + +static constexpr const char* const* s_sdl_button_names_list[] = { + s_sdl_button_names, // SDL_GAMEPAD_TYPE_UNKNOWN + s_sdl_button_names, // SDL_GAMEPAD_TYPE_STANDARD + s_sdl_button_names, // SDL_GAMEPAD_TYPE_XBOX360 + s_sdl_button_names, // SDL_GAMEPAD_TYPE_XBOXONE + s_sdl_button_ps3_names, // SDL_GAMEPAD_TYPE_PS3 + s_sdl_button_ps4_names, // SDL_GAMEPAD_TYPE_PS4 + s_sdl_button_ps5_names, // SDL_GAMEPAD_TYPE_PS5 + // Switch +}; +static constexpr size_t s_sdl_button_namesize_list[] = { + std::size(s_sdl_button_names), // SDL_GAMEPAD_TYPE_UNKNOWN + std::size(s_sdl_button_names), // SDL_GAMEPAD_TYPE_STANDARD + std::size(s_sdl_button_names), // SDL_GAMEPAD_TYPE_XBOX360 + std::size(s_sdl_button_names), // SDL_GAMEPAD_TYPE_XBOXONE + std::size(s_sdl_button_ps3_names), // SDL_GAMEPAD_TYPE_PS3 + std::size(s_sdl_button_ps4_names), // SDL_GAMEPAD_TYPE_PS4 + std::size(s_sdl_button_ps5_names), // SDL_GAMEPAD_TYPE_PS5 + // Switch +}; + +static constexpr const char* s_sdl_face_button_icons[] = { + nullptr, // SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN + ICON_PF_BUTTON_A, // SDL_GAMEPAD_BUTTON_LABEL_A + ICON_PF_BUTTON_B, // SDL_GAMEPAD_BUTTON_LABEL_B + ICON_PF_BUTTON_X, // SDL_GAMEPAD_BUTTON_LABEL_X + ICON_PF_BUTTON_Y, // SDL_GAMEPAD_BUTTON_LABEL_Y + ICON_PF_BUTTON_CROSS, // SDL_GAMEPAD_BUTTON_LABEL_CROSS + ICON_PF_BUTTON_CIRCLE, // SDL_GAMEPAD_BUTTON_LABEL_CIRCLE + ICON_PF_BUTTON_SQUARE, // SDL_GAMEPAD_BUTTON_LABEL_SQUARE + ICON_PF_BUTTON_TRIANGLE, // SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE }; static constexpr const char* s_sdl_button_icons[] = { - ICON_PF_BUTTON_A, // SDL_CONTROLLER_BUTTON_A - ICON_PF_BUTTON_B, // SDL_CONTROLLER_BUTTON_B - ICON_PF_BUTTON_X, // SDL_CONTROLLER_BUTTON_X - ICON_PF_BUTTON_Y, // SDL_CONTROLLER_BUTTON_Y - ICON_PF_SHARE_CAPTURE, // SDL_CONTROLLER_BUTTON_BACK - ICON_PF_XBOX, // SDL_CONTROLLER_BUTTON_GUIDE - ICON_PF_BURGER_MENU, // SDL_CONTROLLER_BUTTON_START - ICON_PF_LEFT_ANALOG_CLICK, // SDL_CONTROLLER_BUTTON_LEFTSTICK - ICON_PF_RIGHT_ANALOG_CLICK, // SDL_CONTROLLER_BUTTON_RIGHTSTICK - ICON_PF_LEFT_SHOULDER_LB, // SDL_CONTROLLER_BUTTON_LEFTSHOULDER - ICON_PF_RIGHT_SHOULDER_RB, // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER - ICON_PF_XBOX_DPAD_UP, // SDL_CONTROLLER_BUTTON_DPAD_UP - ICON_PF_XBOX_DPAD_DOWN, // SDL_CONTROLLER_BUTTON_DPAD_DOWN - ICON_PF_XBOX_DPAD_LEFT, // SDL_CONTROLLER_BUTTON_DPAD_LEFT - ICON_PF_XBOX_DPAD_RIGHT, // SDL_CONTROLLER_BUTTON_DPAD_RIGHT + ICON_PF_BUTTON_DOWN_A, // SDL_GAMEPAD_BUTTON_SOUTH + ICON_PF_BUTTON_RIGHT_B, // SDL_GAMEPAD_BUTTON_EAST + ICON_PF_BUTTON_LEFT_X, // SDL_GAMEPAD_BUTTON_WEST + ICON_PF_BUTTON_UP_Y, // SDL_GAMEPAD_BUTTON_NORTH + ICON_PF_SHARE_CAPTURE, // SDL_GAMEPAD_BUTTON_BACK + ICON_PF_XBOX, // SDL_GAMEPAD_BUTTON_GUIDE + ICON_PF_BURGER_MENU, // SDL_GAMEPAD_BUTTON_START + ICON_PF_LEFT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_LEFT_STICK + ICON_PF_RIGHT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + ICON_PF_LEFT_SHOULDER_LB, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + ICON_PF_RIGHT_SHOULDER_RB, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + ICON_PF_XBOX_DPAD_UP, // SDL_GAMEPAD_BUTTON_DPAD_UP + ICON_PF_XBOX_DPAD_DOWN, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + ICON_PF_XBOX_DPAD_LEFT, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + ICON_PF_XBOX_DPAD_RIGHT, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT }; +static constexpr const char* s_sdl_button_ps3_icons[] = { + ICON_PF_BUTTON_CROSS, // SDL_GAMEPAD_BUTTON_SOUTH + ICON_PF_BUTTON_CIRCLE, // SDL_GAMEPAD_BUTTON_EAST + ICON_PF_BUTTON_SQUARE, // SDL_GAMEPAD_BUTTON_WEST + ICON_PF_BUTTON_TRIANGLE, // SDL_GAMEPAD_BUTTON_NORTH + ICON_PF_SELECT_SHARE, // SDL_GAMEPAD_BUTTON_BACK + ICON_PF_XBOX, // SDL_GAMEPAD_BUTTON_GUIDE + ICON_PF_START, // SDL_GAMEPAD_BUTTON_START + ICON_PF_LEFT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_LEFT_STICK + ICON_PF_RIGHT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + ICON_PF_LEFT_SHOULDER_L1, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + ICON_PF_RIGHT_SHOULDER_R1, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + ICON_PF_DPAD_UP, // SDL_GAMEPAD_BUTTON_DPAD_UP + ICON_PF_DPAD_DOWN, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + ICON_PF_DPAD_LEFT, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + ICON_PF_DPAD_RIGHT, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT +}; +static constexpr const char* s_sdl_button_ps4_icons[] = { + ICON_PF_BUTTON_CROSS, // SDL_GAMEPAD_BUTTON_SOUTH + ICON_PF_BUTTON_CIRCLE, // SDL_GAMEPAD_BUTTON_EAST + ICON_PF_BUTTON_SQUARE, // SDL_GAMEPAD_BUTTON_WEST + ICON_PF_BUTTON_TRIANGLE, // SDL_GAMEPAD_BUTTON_NORTH + ICON_PF_DUALSHOCK_SHARE, // SDL_GAMEPAD_BUTTON_BACK + ICON_PF_PLAYSTATION, // SDL_GAMEPAD_BUTTON_GUIDE + ICON_PF_DUALSHOCK_OPTIONS, // SDL_GAMEPAD_BUTTON_START + ICON_PF_LEFT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_LEFT_STICK + ICON_PF_RIGHT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + ICON_PF_LEFT_SHOULDER_L1, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + ICON_PF_RIGHT_SHOULDER_R1, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + ICON_PF_DPAD_UP, // SDL_GAMEPAD_BUTTON_DPAD_UP + ICON_PF_DPAD_DOWN, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + ICON_PF_DPAD_LEFT, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + ICON_PF_DPAD_RIGHT, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + nullptr, // SDL_GAMEPAD_BUTTON_MISC1 + nullptr, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 + nullptr, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 + nullptr, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 + nullptr, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 + ICON_PF_DUALSHOCK_TOUCHPAD, // SDL_GAMEPAD_BUTTON_TOUCHPAD +}; +static constexpr const char* s_sdl_button_ps5_icons[] = { + ICON_PF_BUTTON_CROSS, // SDL_GAMEPAD_BUTTON_SOUTH + ICON_PF_BUTTON_CIRCLE, // SDL_GAMEPAD_BUTTON_EAST + ICON_PF_BUTTON_SQUARE, // SDL_GAMEPAD_BUTTON_WEST + ICON_PF_BUTTON_TRIANGLE, // SDL_GAMEPAD_BUTTON_NORTH + ICON_PF_DUALSENSE_SHARE, // SDL_GAMEPAD_BUTTON_BACK + ICON_PF_PLAYSTATION, // SDL_GAMEPAD_BUTTON_GUIDE + ICON_PF_DUALSENSE_OPTIONS, // SDL_GAMEPAD_BUTTON_START + ICON_PF_LEFT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_LEFT_STICK + ICON_PF_RIGHT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + ICON_PF_LEFT_SHOULDER_L1, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + ICON_PF_RIGHT_SHOULDER_R1, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + ICON_PF_DPAD_UP, // SDL_GAMEPAD_BUTTON_DPAD_UP + ICON_PF_DPAD_DOWN, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + ICON_PF_DPAD_LEFT, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + ICON_PF_DPAD_RIGHT, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + nullptr, // SDL_GAMEPAD_BUTTON_MISC1 + nullptr, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 + nullptr, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 + nullptr, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 + nullptr, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 + ICON_PF_DUALSENSE_TOUCHPAD, // SDL_GAMEPAD_BUTTON_TOUCHPAD +}; + +static constexpr const char* const* s_sdl_button_icons_list[] = { + s_sdl_button_icons, // SDL_GAMEPAD_TYPE_UNKNOWN + s_sdl_button_icons, // SDL_GAMEPAD_TYPE_STANDARD + s_sdl_button_icons, // SDL_GAMEPAD_TYPE_XBOX360 + s_sdl_button_icons, // SDL_GAMEPAD_TYPE_XBOXONE + s_sdl_button_ps3_icons, // SDL_GAMEPAD_TYPE_PS3 + s_sdl_button_ps4_icons, // SDL_GAMEPAD_TYPE_PS4 + s_sdl_button_ps5_icons, // SDL_GAMEPAD_TYPE_PS5 + // Switch +}; +static constexpr size_t s_sdl_button_iconsize_list[] = { + std::size(s_sdl_button_icons), // SDL_GAMEPAD_TYPE_UNKNOWN + std::size(s_sdl_button_icons), // SDL_GAMEPAD_TYPE_STANDARD + std::size(s_sdl_button_icons), // SDL_GAMEPAD_TYPE_XBOX360 + std::size(s_sdl_button_icons), // SDL_GAMEPAD_TYPE_XBOXONE + std::size(s_sdl_button_ps3_icons), // SDL_GAMEPAD_TYPE_PS3 + std::size(s_sdl_button_ps4_icons), // SDL_GAMEPAD_TYPE_PS4 + std::size(s_sdl_button_ps5_icons), // SDL_GAMEPAD_TYPE_PS5 + // Switch +}; + static constexpr const GenericInputBinding s_sdl_generic_binding_button_mapping[] = { - GenericInputBinding::Cross, // SDL_CONTROLLER_BUTTON_A - GenericInputBinding::Circle, // SDL_CONTROLLER_BUTTON_B - GenericInputBinding::Square, // SDL_CONTROLLER_BUTTON_X - GenericInputBinding::Triangle, // SDL_CONTROLLER_BUTTON_Y - GenericInputBinding::Select, // SDL_CONTROLLER_BUTTON_BACK - GenericInputBinding::System, // SDL_CONTROLLER_BUTTON_GUIDE - GenericInputBinding::Start, // SDL_CONTROLLER_BUTTON_START - GenericInputBinding::L3, // SDL_CONTROLLER_BUTTON_LEFTSTICK - GenericInputBinding::R3, // SDL_CONTROLLER_BUTTON_RIGHTSTICK - GenericInputBinding::L1, // SDL_CONTROLLER_BUTTON_LEFTSHOULDER - GenericInputBinding::R1, // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER - GenericInputBinding::DPadUp, // SDL_CONTROLLER_BUTTON_DPAD_UP - GenericInputBinding::DPadDown, // SDL_CONTROLLER_BUTTON_DPAD_DOWN - GenericInputBinding::DPadLeft, // SDL_CONTROLLER_BUTTON_DPAD_LEFT - GenericInputBinding::DPadRight, // SDL_CONTROLLER_BUTTON_DPAD_RIGHT - GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_MISC1 - GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE1 - GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE2 - GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE3 - GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE4 - GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_TOUCHPAD + GenericInputBinding::Cross, // SDL_GAMEPAD_BUTTON_SOUTH + GenericInputBinding::Circle, // SDL_GAMEPAD_BUTTON_EAST + GenericInputBinding::Square, // SDL_GAMEPAD_BUTTON_WEST + GenericInputBinding::Triangle, // SDL_GAMEPAD_BUTTON_NORTH + GenericInputBinding::Select, // SDL_GAMEPAD_BUTTON_BACK + GenericInputBinding::System, // SDL_GAMEPAD_BUTTON_GUIDE + GenericInputBinding::Start, // SDL_GAMEPAD_BUTTON_START + GenericInputBinding::L3, // SDL_GAMEPAD_BUTTON_LEFT_STICK + GenericInputBinding::R3, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + GenericInputBinding::L1, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + GenericInputBinding::R1, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + GenericInputBinding::DPadUp, // SDL_GAMEPAD_BUTTON_DPAD_UP + GenericInputBinding::DPadDown, // SDL_GAMEPAD_BUTTON_DPAD_DOWN + GenericInputBinding::DPadLeft, // SDL_GAMEPAD_BUTTON_DPAD_LEFT + GenericInputBinding::DPadRight, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_MISC1 + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_TOUCHPAD }; static constexpr const char* s_sdl_hat_direction_names[] = { @@ -125,9 +382,9 @@ static constexpr const char* s_sdl_default_led_colors[] = { "808000", // SDL-3 }; -static void SetControllerRGBLED(SDL_GameController* gc, u32 color) +static void SetGamepadRGBLED(SDL_Gamepad* pad, u32 color) { - SDL_GameControllerSetLED(gc, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); + SDL_SetGamepadLED(pad, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); } static void SDLLogCallback(void* userdata, int category, SDL_LogPriority priority, const char* message) @@ -155,11 +412,16 @@ bool SDLInputSource::Initialize(SettingsInterface& si, std::unique_lock& settings_lock) { - const bool old_controller_enhanced_mode = m_controller_enhanced_mode; - const bool old_controller_ps5_player_led = m_controller_ps5_player_led; - const bool old_controller_raw_mode = m_controller_raw_mode; + const bool old_enable_enhanced_reports = m_enable_enhanced_reports; + const bool old_enable_ps5_player_leds = m_enable_ps5_player_leds; + const bool old_use_raw_input = m_use_raw_input; #ifdef __APPLE__ const bool old_enable_iokit_driver = m_enable_iokit_driver; @@ -175,9 +437,9 @@ void SDLInputSource::UpdateSettings(SettingsInterface& si, std::unique_lockgame_controller || !SDL_GameControllerHasLED(it->game_controller)) + if (it == m_controllers.end() || !it->gamepad) continue; - SetControllerRGBLED(it->game_controller, color); + const SDL_PropertiesID props = SDL_GetGamepadProperties(it->gamepad); + if (props == 0) + { + ERROR_LOG("SDLInputSource: SDL_GetGamepadProperties() failed"); + continue; + } + + if (!SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN, false)) + continue; + + SetGamepadRGBLED(it->gamepad, color); } m_sdl_hints = si.GetKeyValueList("SDLHints"); - m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false); - m_controller_ps5_player_led = si.GetBoolValue("InputSources", "SDLPS5PlayerLED", false); - m_controller_raw_mode = si.GetBoolValue("InputSources", "SDLRawInput", false); + m_enable_enhanced_reports = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", true); + m_enable_ps5_player_leds = si.GetBoolValue("InputSources", "SDLPS5PlayerLED", true); + m_use_raw_input = si.GetBoolValue("InputSources", "SDLRawInput", false); #ifdef __APPLE__ m_enable_iokit_driver = si.GetBoolValue("InputSources", "SDLIOKitDriver", true); @@ -272,10 +544,9 @@ void SDLInputSource::SetHints() Console.Error(fmt::format("SDLInputSource: Controller DB not found, it should be named '{}'", CONTROLLER_DB_FILENAME)); } - SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, m_controller_raw_mode ? "1" : "0"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, m_controller_enhanced_mode ? "1" : "0"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, m_controller_enhanced_mode ? "1" : "0"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, m_controller_ps5_player_led ? "1" : "0"); + SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, m_use_raw_input ? "1" : "0"); + SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, m_enable_enhanced_reports ? "auto" : "0"); // PS4/PS5 Rumble + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, m_enable_ps5_player_leds ? "1" : "0"); // Enable Wii U Pro Controller support SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); #ifndef _WIN32 @@ -296,22 +567,32 @@ void SDLInputSource::SetHints() bool SDLInputSource::InitializeSubsystem() { - if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0) + if (!SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC)) { - Console.Error("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed"); + Console.Error("SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC) failed"); return false; } - SDL_LogSetOutputFunction(SDLLogCallback, nullptr); + SDL_SetLogOutputFunction(SDLLogCallback, nullptr); #ifdef PCSX2_DEVBUILD - SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); + SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE); #else - SDL_LogSetAllPriority(SDL_LOG_PRIORITY_INFO); + SDL_SetLogPriorities(SDL_LOG_PRIORITY_INFO); #endif // we should open the controllers as the connected events come in, so no need to do any more here m_sdl_subsystem_initialized = true; - Console.WriteLn(Color_StrongGreen, fmt::format("SDLInputSource: {} controller mappings are loaded.", SDL_GameControllerNumMappings())); + + int count; + char** mappings = SDL_GetGamepadMappings(&count); + if (mappings != nullptr) + { + SDL_free(mappings); + Console.WriteLnFmt(Color_StrongGreen, "SDLInputSource: {} gamepad mappings are loaded.", count); + } + else + Console.Error("SDL_GetGamepadMappings() failed {}", SDL_GetError()); + return true; } @@ -322,7 +603,7 @@ void SDLInputSource::ShutdownSubsystem() if (m_sdl_subsystem_initialized) { - SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC); m_sdl_subsystem_initialized = false; } } @@ -347,7 +628,7 @@ std::vector> SDLInputSource::EnumerateDevice { std::string id(StringUtil::StdStringFromFormat("SDL-%d", cd.player_id)); - const char* name = cd.game_controller ? SDL_GameControllerName(cd.game_controller) : SDL_JoystickName(cd.joystick); + const char* name = cd.gamepad ? SDL_GetGamepadName(cd.gamepad) : SDL_GetJoystickName(cd.joystick); if (name) ret.emplace_back(std::move(id), name); else @@ -370,6 +651,119 @@ std::optional SDLInputSource::ParseKeyString(const std::string_ key.source_type = InputSourceType::SDL; key.source_index = static_cast(player_id.value()); + // SDL2-SDL3 migrations + static constexpr const char* sdl_button_legacy_names[] = { + "A", // SDL_CONTROLLER_BUTTON_A + "B", // SDL_CONTROLLER_BUTTON_B + "X", // SDL_CONTROLLER_BUTTON_X + "Y", // SDL_CONTROLLER_BUTTON_Y + }; + + for (u32 i = 0; i < std::size(sdl_button_legacy_names); i++) + { + if (binding == sdl_button_legacy_names[i]) + { + key.source_subtype = InputSubclass::ControllerButton; + key.data = i; + + // SDL2 would map A/B/X/Y based on the button's label + // We need to convert this to a positional binding for SDL3 + static constexpr SDL_GamepadButton face_button_pos[] = { + SDL_GAMEPAD_BUTTON_SOUTH, + SDL_GAMEPAD_BUTTON_EAST, + SDL_GAMEPAD_BUTTON_WEST, + SDL_GAMEPAD_BUTTON_NORTH, + }; + + // This migrations needs to inspect the controller + { + std::lock_guard lock(m_controllers_key_mutex); + + auto it = GetControllerDataForPlayerId(key.source_index); + if (it != m_controllers.end() && it->gamepad) + { + static bool shown_prompt = false; + for (u32 pos = 0; pos < std::size(face_button_pos); pos++) + { + // A/B/X/Y are equal to 1/2/3/4 in SDL_GamepadButtonLabel + // PS controllers have positional A/B/X/Y, so don't need adjusting + // Controllers with unknown labels are assumed to have positional A/B/X/Y + const SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabel(it->gamepad, face_button_pos[pos]); + if (key.data == (label - 1)) + { + if (key.data != pos) + { + if (!shown_prompt) + { + shown_prompt = true; + Host::ReportInfoAsync(TRANSLATE("SDLInputSource", "SDL3 Migration"), + TRANSLATE("SDLInputSource", "As part of our upgrade to SDL3, we've had to migrate your binds\n" + "Your controller did not match the Xbox layout and may need rebinding\n" + "Please verify your controller settings and amend if required")); + } + key.data = pos; + } + break; + } + } + + key.needs_migration = true; + return key; + } + else if (std::find(m_gamepads_needing_migration.begin(), m_gamepads_needing_migration.end(), key.source_index) == + m_gamepads_needing_migration.end()) + { + // flag the device to migrate later + m_gamepads_needing_migration.push_back(key.source_index); + return std::nullopt; + } + } + } + } + + if (binding.starts_with("+Axis") || binding.starts_with("-Axis")) + { + const std::string_view axis_name(binding.substr(1)); + + std::string_view end; + if (auto value = StringUtil::FromChars(axis_name.substr(4), 10, &end)) + { + key.source_subtype = InputSubclass::ControllerAxis; + key.data = *value - 6 + std::size(s_sdl_axis_setting_names); + key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None; + key.invert = (end == "~"); + + key.needs_migration = true; + return key; + } + } + else if (binding.starts_with("FullAxis")) + { + std::string_view end; + if (auto value = StringUtil::FromChars(binding.substr(8), 10, &end)) + { + key.source_subtype = InputSubclass::ControllerAxis; + key.data = *value - 6 + std::size(s_sdl_axis_setting_names); + key.modifier = InputModifier::FullAxis; + key.invert = (end == "~"); + + key.needs_migration = true; + return key; + } + } + else if (binding.starts_with("Button")) + { + if (auto value = StringUtil::FromChars(binding.substr(6))) + { + key.source_subtype = InputSubclass::ControllerButton; + key.data = *value - 21 + std::size(s_sdl_button_setting_names); + + key.needs_migration = true; + return key; + } + } + // End Migrations + if (binding.ends_with("Motor")) { key.source_subtype = InputSubclass::ControllerMotor; @@ -399,21 +793,21 @@ std::optional SDLInputSource::ParseKeyString(const std::string_ // likely an axis const std::string_view axis_name(binding.substr(1)); - if (axis_name.starts_with("Axis")) + if (axis_name.starts_with("JoyAxis")) { std::string_view end; - if (auto value = StringUtil::FromChars(axis_name.substr(4), 10, &end)) + if (auto value = StringUtil::FromChars(axis_name.substr(7), 10, &end)) { key.source_subtype = InputSubclass::ControllerAxis; - key.data = *value; + key.data = *value + std::size(s_sdl_axis_setting_names); key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None; key.invert = (end == "~"); return key; } } - for (u32 i = 0; i < std::size(s_sdl_axis_names); i++) + for (u32 i = 0; i < std::size(s_sdl_axis_setting_names); i++) { - if (axis_name == s_sdl_axis_names[i]) + if (axis_name == s_sdl_axis_setting_names[i]) { // found an axis! key.source_subtype = InputSubclass::ControllerAxis; @@ -423,13 +817,13 @@ std::optional SDLInputSource::ParseKeyString(const std::string_ } } } - else if (binding.starts_with("FullAxis")) + else if (binding.starts_with("FullJoyAxis")) { std::string_view end; - if (auto value = StringUtil::FromChars(binding.substr(8), 10, &end)) + if (auto value = StringUtil::FromChars(binding.substr(11), 10, &end)) { key.source_subtype = InputSubclass::ControllerAxis; - key.data = *value; + key.data = *value + std::size(s_sdl_axis_setting_names); key.modifier = InputModifier::FullAxis; key.invert = (end == "~"); return key; @@ -454,18 +848,18 @@ std::optional SDLInputSource::ParseKeyString(const std::string_ else { // must be a button - if (binding.starts_with("Button")) + if (binding.starts_with("JoyButton")) { - if (auto value = StringUtil::FromChars(binding.substr(6))) + if (auto value = StringUtil::FromChars(binding.substr(9))) { key.source_subtype = InputSubclass::ControllerButton; - key.data = *value; + key.data = *value + std::size(s_sdl_button_setting_names); return key; } } - for (u32 i = 0; i < std::size(s_sdl_button_names); i++) + for (u32 i = 0; i < std::size(s_sdl_button_setting_names); i++) { - if (binding == s_sdl_button_names[i]) + if (binding == s_sdl_button_setting_names[i]) { key.source_subtype = InputSubclass::ControllerButton; key.data = i; @@ -478,7 +872,7 @@ std::optional SDLInputSource::ParseKeyString(const std::string_ return std::nullopt; } -TinyString SDLInputSource::ConvertKeyToString(InputBindingKey key) +TinyString SDLInputSource::ConvertKeyToString(InputBindingKey key, bool display, bool migration) { TinyString ret; @@ -486,32 +880,102 @@ TinyString SDLInputSource::ConvertKeyToString(InputBindingKey key) { if (key.source_subtype == InputSubclass::ControllerAxis) { - const char* modifier = (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+")); - if (key.data < std::size(s_sdl_axis_names)) - ret.format("SDL-{}/{}{}", static_cast(key.source_index), modifier, s_sdl_axis_names[key.data]); + const char* modifier = (key.modifier == InputModifier::FullAxis ? (display ? "Full " : "Full") : (key.modifier == InputModifier::Negate ? "-" : "+")); + if (display) + { + std::lock_guard lock(m_controllers_key_mutex); + + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + auto it = GetControllerDataForPlayerId(key.source_index); + if (it != m_controllers.end()) + type = SDL_GetRealGamepadType(it->gamepad); + + if (key.data < std::size(s_sdl_axis_names)) + { + ret.format("SDL-{} {}{}", static_cast(key.source_index), modifier, s_sdl_axis_names[key.data]); + } + else if (key.data - std::size(s_sdl_axis_names) < std::size(s_sdl_trigger_names)) + { + const u32 trigger_index = key.data - std::size(s_sdl_axis_names); + + if (type < std::size(s_sdl_trigger_names_list)) + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_trigger_names_list[type][trigger_index]); + else + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_trigger_names[trigger_index]); + } + else + ret.format("SDL-{} {}Axis {}{}", static_cast(key.source_index), modifier, key.data - std::size(s_sdl_axis_setting_names) + 1, key.invert ? "~" : ""); + } else - ret.format("SDL-{}/{}Axis{}{}", static_cast(key.source_index), modifier, key.data, (key.invert && !ShouldIgnoreInversion()) ? "~" : ""); + { + if (key.data < std::size(s_sdl_axis_setting_names)) + ret.format("SDL-{}/{}{}", static_cast(key.source_index), modifier, s_sdl_axis_setting_names[key.data]); + else + ret.format("SDL-{}/{}JoyAxis{}{}", static_cast(key.source_index), modifier, key.data - std::size(s_sdl_axis_setting_names), (key.invert && (migration || !ShouldIgnoreInversion())) ? "~" : ""); + } } else if (key.source_subtype == InputSubclass::ControllerButton) { - if (key.data < std::size(s_sdl_button_names)) - ret.format("SDL-{}/{}", static_cast(key.source_index), s_sdl_button_names[key.data]); + if (display) + { + std::lock_guard lock(m_controllers_key_mutex); + + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + auto it = GetControllerDataForPlayerId(key.source_index); + if (it != m_controllers.end()) + type = SDL_GetRealGamepadType(it->gamepad); + + if (type > SDL_GAMEPAD_TYPE_STANDARD && type < std::size(s_sdl_button_names_list) && + key.data < s_sdl_button_namesize_list[type] && s_sdl_button_names_list[type][key.data] != nullptr) + { + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_button_names_list[type][key.data]); + } + else if (key.data < 4) + { + SDL_GamepadButtonLabel label = SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN; + if (it != m_controllers.end() && it->gamepad) + label = SDL_GetGamepadButtonLabel(it->gamepad, static_cast(key.data)); + + if (label > SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN && label < std::size(s_sdl_face_button_names)) + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_face_button_names[label]); + else + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_button_names[key.data]); + } + else if (key.data < std::size(s_sdl_button_names)) + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_button_names[key.data]); + else + ret.format("SDL-{} Button {}", static_cast(key.source_index), key.data - std::size(s_sdl_button_setting_names) + 1); + } else - ret.format("SDL-{}/Button{}", static_cast(key.source_index), key.data); + { + if (key.data < std::size(s_sdl_button_setting_names)) + ret.format("SDL-{}/{}", static_cast(key.source_index), s_sdl_button_setting_names[key.data]); + else + ret.format("SDL-{}/JoyButton{}", static_cast(key.source_index), key.data - std::size(s_sdl_button_setting_names)); + } } else if (key.source_subtype == InputSubclass::ControllerHat) { const u32 hat_index = key.data / static_cast(std::size(s_sdl_hat_direction_names)); const u32 hat_direction = key.data % static_cast(std::size(s_sdl_hat_direction_names)); - ret.format("SDL-{}/Hat{}{}", static_cast(key.source_index), hat_index, s_sdl_hat_direction_names[hat_direction]); + if (display) + ret.format("SDL-{} Hat {} {}", static_cast(key.source_index), hat_index + 1, s_sdl_hat_direction_names[hat_direction]); + else + ret.format("SDL-{}/Hat{}{}", static_cast(key.source_index), hat_index, s_sdl_hat_direction_names[hat_direction]); } else if (key.source_subtype == InputSubclass::ControllerMotor) { - ret.format("SDL-{}/{}Motor", static_cast(key.source_index), key.data ? "Large" : "Small"); + if (display) + ret.format("SDL-{} {} Motor", static_cast(key.source_index), key.data ? "Large" : "Small"); + else + ret.format("SDL-{}/{}Motor", static_cast(key.source_index), key.data ? "Large" : "Small"); } else if (key.source_subtype == InputSubclass::ControllerHaptic) { - ret.format("SDL-{}/Haptic", static_cast(key.source_index)); + if (display) + ret.format("SDL-{} Haptic", static_cast(key.source_index)); + else + ret.format("SDL-{}/Haptic", static_cast(key.source_index)); } } @@ -524,17 +988,52 @@ TinyString SDLInputSource::ConvertKeyToIcon(InputBindingKey key) if (key.source_type == InputSourceType::SDL) { + std::lock_guard lock(m_controllers_key_mutex); + + SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; + auto it = GetControllerDataForPlayerId(key.source_index); + if (it != m_controllers.end()) + type = SDL_GetRealGamepadType(it->gamepad); + if (key.source_subtype == InputSubclass::ControllerAxis) { - if (key.data < std::size(s_sdl_axis_icons) && key.modifier != InputModifier::FullAxis) + if (key.modifier != InputModifier::FullAxis) { - ret.format("SDL-{} {}", static_cast(key.source_index), - s_sdl_axis_icons[key.data][key.modifier == InputModifier::None]); + if (key.data < std::size(s_sdl_axis_icons)) + { + ret.format("SDL-{} {}", static_cast(key.source_index), + s_sdl_axis_icons[key.data][key.modifier == InputModifier::None]); + } + else if (key.data - std::size(s_sdl_axis_icons) < std::size(s_sdl_trigger_icons)) + { + const u32 trigger_index = key.data - std::size(s_sdl_axis_icons); + + if (type < std::size(s_sdl_trigger_icons_list)) + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_trigger_icons_list[type][trigger_index]); + else + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_trigger_icons[trigger_index]); + } } } else if (key.source_subtype == InputSubclass::ControllerButton) { - if (key.data < std::size(s_sdl_button_icons)) + if (type > SDL_GAMEPAD_TYPE_STANDARD && type < std::size(s_sdl_button_icons_list) && + key.data < s_sdl_button_iconsize_list[type] && s_sdl_button_icons_list[type][key.data] != nullptr) + { + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_button_icons_list[type][key.data]); + } + else if (key.data < 4) + { + SDL_GamepadButtonLabel label = SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN; + if (it != m_controllers.end() && it->gamepad) + label = SDL_GetGamepadButtonLabel(it->gamepad, static_cast(key.data)); + + if (label > SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN && label < std::size(s_sdl_face_button_icons)) + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_face_button_icons[label]); + else + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_button_icons[key.data]); + } + else if (key.data < std::size(s_sdl_button_icons)) ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_button_icons[key.data]); } } @@ -546,24 +1045,24 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event) { switch (event->type) { - case SDL_CONTROLLERDEVICEADDED: + case SDL_EVENT_GAMEPAD_ADDED: { - Console.WriteLn("SDLInputSource: Controller %d inserted", event->cdevice.which); + Console.WriteLn("SDLInputSource: Gamepad %d inserted", event->cdevice.which); OpenDevice(event->cdevice.which, true); return true; } - case SDL_CONTROLLERDEVICEREMOVED: + case SDL_EVENT_GAMEPAD_REMOVED: { - Console.WriteLn("SDLInputSource: Controller %d removed", event->cdevice.which); + Console.WriteLn("SDLInputSource: Gamepad %d removed", event->cdevice.which); CloseDevice(event->cdevice.which); return true; } - case SDL_JOYDEVICEADDED: + case SDL_EVENT_JOYSTICK_ADDED: { - // Let game controller handle.. well.. game controllers. - if (SDL_IsGameController(event->jdevice.which)) + // Let gamepad handle.. well.. gamepads. + if (SDL_IsGamepad(event->jdevice.which)) return false; Console.WriteLn("SDLInputSource: Joystick %d inserted", event->jdevice.which); @@ -572,9 +1071,9 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event) } break; - case SDL_JOYDEVICEREMOVED: + case SDL_EVENT_JOYSTICK_REMOVED: { - if (auto it = GetControllerDataForJoystickId(event->cdevice.which); it != m_controllers.end() && it->game_controller) + if (auto it = GetControllerDataForJoystickId(event->cdevice.which); it != m_controllers.end() && it->gamepad) return false; Console.WriteLn("SDLInputSource: Joystick %d removed", event->jdevice.which); @@ -582,21 +1081,21 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event) return true; } - case SDL_CONTROLLERAXISMOTION: - return HandleControllerAxisEvent(&event->caxis); + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + return HandleGamepadAxisEvent(&event->gaxis); - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - return HandleControllerButtonEvent(&event->cbutton); + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + return HandleGamepadButtonEvent(&event->gbutton); - case SDL_JOYAXISMOTION: + case SDL_EVENT_JOYSTICK_AXIS_MOTION: return HandleJoystickAxisEvent(&event->jaxis); - case SDL_JOYBUTTONDOWN: - case SDL_JOYBUTTONUP: + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: + case SDL_EVENT_JOYSTICK_BUTTON_UP: return HandleJoystickButtonEvent(&event->jbutton); - case SDL_JOYHATMOTION: + case SDL_EVENT_JOYSTICK_HAT_MOTION: return HandleJoystickHatEvent(&event->jhat); default: @@ -647,39 +1146,39 @@ int SDLInputSource::GetFreePlayerId() const return 0; } -bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) +bool SDLInputSource::OpenDevice(int index, bool is_gamepad) { - SDL_GameController* gcontroller; + SDL_Gamepad* gamepad; SDL_Joystick* joystick; - if (is_gamecontroller) + if (is_gamepad) { - gcontroller = SDL_GameControllerOpen(index); - joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr; + gamepad = SDL_OpenGamepad(index); + joystick = gamepad ? SDL_GetGamepadJoystick(gamepad) : nullptr; } else { - gcontroller = nullptr; - joystick = SDL_JoystickOpen(index); + gamepad = nullptr; + joystick = SDL_OpenJoystick(index); } - if (!gcontroller && !joystick) + if (!gamepad && !joystick) { ERROR_LOG("SDLInputSource: Failed to open controller {}", index); return false; } - const int joystick_id = SDL_JoystickInstanceID(joystick); - int player_id = gcontroller ? SDL_GameControllerGetPlayerIndex(gcontroller) : SDL_JoystickGetPlayerIndex(joystick); + const SDL_JoystickID joystick_id = SDL_GetJoystickID(joystick); + int player_id = gamepad ? SDL_GetGamepadPlayerIndex(gamepad) : SDL_GetJoystickPlayerIndex(joystick); for (auto it = m_controllers.begin(); it != m_controllers.end(); ++it) { if (it->joystick_id == joystick_id) { ERROR_LOG("SDLInputSource: Controller {}, instance {}, player {} already connected, ignoring.", index, joystick_id, player_id); - if (gcontroller) - SDL_GameControllerClose(gcontroller); + if (gamepad) + SDL_CloseGamepad(gamepad); else - SDL_JoystickClose(joystick); + SDL_CloseJoystick(joystick); return false; } @@ -694,65 +1193,73 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) player_id = free_player_id; } - const char* name = gcontroller ? SDL_GameControllerName(gcontroller) : SDL_JoystickName(joystick); + const char* name = gamepad ? SDL_GetGamepadName(gamepad) : SDL_GetJoystickName(joystick); if (!name) name = "Unknown Device"; - INFO_LOG("SDLInputSource: Opened {} {} (instance id {}, player id {}): {}", is_gamecontroller ? "game controller" : "joystick", + INFO_LOG("SDLInputSource: Opened {} {} (instance id {}, player id {}): {}", is_gamepad ? "gamepad" : "joystick", index, joystick_id, player_id, name); ControllerData cd = {}; cd.player_id = player_id; cd.joystick_id = joystick_id; cd.haptic_left_right_effect = -1; - cd.game_controller = gcontroller; + cd.gamepad = gamepad; cd.joystick = joystick; - if (gcontroller) + if (gamepad) { - const int num_axes = SDL_JoystickNumAxes(joystick); - const int num_buttons = SDL_JoystickNumButtons(joystick); - cd.joy_axis_used_in_gc.resize(num_axes, false); - cd.joy_button_used_in_gc.resize(num_buttons, false); - auto mark_bind = [&](SDL_GameControllerButtonBind bind) { - if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS && bind.value.axis < num_axes) - cd.joy_axis_used_in_gc[bind.value.axis] = true; - if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && bind.value.button < num_buttons) - cd.joy_button_used_in_gc[bind.value.button] = true; - }; - for (size_t i = 0; i < std::size(s_sdl_axis_names); i++) - mark_bind(SDL_GameControllerGetBindForAxis(gcontroller, static_cast(i))); - for (size_t i = 0; i < std::size(s_sdl_button_names); i++) - mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast(i))); + int binding_count; + SDL_GamepadBinding** bindings = SDL_GetGamepadBindings(gamepad, &binding_count); + if (bindings) + { + const int num_axes = SDL_GetNumJoystickAxes(joystick); + const int num_buttons = SDL_GetNumJoystickButtons(joystick); + cd.joy_axis_used_in_pad.resize(num_axes, false); + cd.joy_button_used_in_pad.resize(num_buttons, false); + auto mark_bind = [&](SDL_GamepadBinding* bind) { + if (bind->input_type == SDL_GAMEPAD_BINDTYPE_AXIS && bind->input.axis.axis < num_axes) + cd.joy_axis_used_in_pad[bind->input.axis.axis] = true; + if (bind->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON && bind->input.button < num_buttons) + cd.joy_button_used_in_pad[bind->input.button] = true; + }; - INFO_LOG("SDLInputSource: Controller {} has {} axes and {} buttons", player_id, num_axes, num_buttons); + for (int i = 0; i < binding_count; i++) + mark_bind(bindings[i]); + + SDL_free(bindings); + + INFO_LOG("SDLInputSource: Gamepad {} has {} axes and {} buttons", player_id, num_axes, num_buttons); + } + else + ERROR_LOG("SDLInputSource: Failed to get gamepad bindings {}", SDL_GetError()); } else { - // GC doesn't have the concept of hats, so we only need to do this for joysticks. - const int num_hats = SDL_JoystickNumHats(joystick); + // Gamepad doesn't have the concept of hats, so we only need to do this for joysticks. + const int num_hats = SDL_GetNumJoystickHats(joystick); if (num_hats > 0) - cd.last_hat_state.resize(static_cast(num_hats), u8(0)); + cd.last_hat_state.resize(static_cast(num_hats), u8{0}); INFO_LOG("SDLInputSource: Joystick {} has {} axes, {} buttons and {} hats", player_id, - SDL_JoystickNumAxes(joystick), SDL_JoystickNumButtons(joystick), num_hats); + SDL_GetNumJoystickAxes(joystick), SDL_GetNumJoystickButtons(joystick), num_hats); } - cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0); - if (cd.use_game_controller_rumble) + cd.use_gamepad_rumble = (gamepad && SDL_RumbleGamepad(gamepad, 0, 0, 0)); + if (cd.use_gamepad_rumble) { - INFO_LOG("SDLInputSource: Rumble is supported on '{}' via gamecontroller", name); + INFO_LOG("SDLInputSource: Rumble is supported on '{}' via gamepad", name); } else { - SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick); + SDL_Haptic* haptic = SDL_OpenHapticFromJoystick(joystick); if (haptic) { SDL_HapticEffect ef = {}; ef.leftright.type = SDL_HAPTIC_LEFTRIGHT; ef.leftright.length = 1000; - int ef_id = SDL_HapticNewEffect(haptic, &ef); + int ef_id = SDL_CreateHapticEffect(haptic, &ef); if (ef_id >= 0) { cd.haptic = haptic; @@ -761,14 +1268,14 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) else { ERROR_LOG("SDLInputSource: Failed to create haptic left/right effect: {}", SDL_GetError()); - if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0) + if (SDL_HapticRumbleSupported(haptic) && SDL_InitHapticRumble(haptic)) { cd.haptic = haptic; } else { ERROR_LOG("SDLInputSource: No haptic rumble supported: {}", SDL_GetError()); - SDL_HapticClose(haptic); + SDL_CloseHaptic(haptic); } } } @@ -777,15 +1284,44 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) INFO_LOG("SDLInputSource: Rumble is supported on '{}' via haptic", name); } - if (!cd.haptic && !cd.use_game_controller_rumble) + if (!cd.haptic && !cd.use_gamepad_rumble) WARNING_LOG("SDLInputSource: Rumble is not supported on '{}'", name); - if (player_id >= 0 && static_cast(player_id) < MAX_LED_COLORS && gcontroller && SDL_GameControllerHasLED(gcontroller)) + if (gamepad) { - SetControllerRGBLED(gcontroller, m_led_colors[player_id]); + const SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad); + bool hasLED = false; + if (props == 0) + ERROR_LOG("SDLInputSource: SDL_GetGamepadProperties() failed"); + else + hasLED = SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN, false); + + if (player_id >= 0 && static_cast(player_id) < MAX_LED_COLORS && hasLED) + { + SetGamepadRGBLED(gamepad, m_led_colors[player_id]); + } } - m_controllers.push_back(std::move(cd)); + { + std::unique_lock lock(m_controllers_key_mutex); + m_controllers.push_back(std::move(cd)); + + if (gamepad) + { + // Perform SDL2-SDL3 migrations that require inspecting the gamepad + auto idx = std::find(m_gamepads_needing_migration.begin(), m_gamepads_needing_migration.end(), player_id); + if (idx != m_gamepads_needing_migration.end()) + { + m_gamepads_needing_migration.erase(idx); + + // ParseKeyString will need the lock when migrating + // unlock here so we don't deadlock reloading binds + lock.unlock(); + // Reload bindings to perform migration + VMManager::ReloadInputBindings(true); + } + } + } InputManager::OnInputDeviceConnected(fmt::format("SDL-{}", player_id), name); return true; @@ -797,19 +1333,23 @@ bool SDLInputSource::CloseDevice(int joystick_index) if (it == m_controllers.end()) return false; - InputManager::OnInputDeviceDisconnected( - {InputBindingKey{.source_type = InputSourceType::SDL, .source_index = static_cast(it->player_id)}}, - fmt::format("SDL-{}", it->player_id)); + { + std::lock_guard lock(m_controllers_key_mutex); + InputManager::OnInputDeviceDisconnected( + {InputBindingKey{.source_type = InputSourceType::SDL, .source_index = static_cast(it->player_id)}}, + fmt::format("SDL-{}", it->player_id)); - if (it->haptic) - SDL_HapticClose(it->haptic); + if (it->haptic) + SDL_CloseHaptic(it->haptic); - if (it->game_controller) - SDL_GameControllerClose(it->game_controller); - else - SDL_JoystickClose(it->joystick); + if (it->gamepad) + SDL_CloseGamepad(it->gamepad); + else + SDL_CloseJoystick(it->joystick); + + m_controllers.erase(it); + } - m_controllers.erase(it); return true; } @@ -818,7 +1358,7 @@ static float NormalizeS16(s16 value) return static_cast(value) / (value < 0 ? 32768.0f : 32767.0f); } -bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev) +bool SDLInputSource::HandleGamepadAxisEvent(const SDL_GamepadAxisEvent* ev) { auto it = GetControllerDataForJoystickId(ev->which); if (it == m_controllers.end()) @@ -829,7 +1369,7 @@ bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev return true; } -bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev) +bool SDLInputSource::HandleGamepadButtonEvent(const SDL_GamepadButtonEvent* ev) { auto it = GetControllerDataForJoystickId(ev->which); if (it == m_controllers.end()) @@ -839,7 +1379,7 @@ bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ? s_sdl_generic_binding_button_mapping[ev->button] : GenericInputBinding::Unknown; - InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key); + InputManager::InvokeEvents(key, static_cast(ev->down), generic_key); return true; } @@ -848,9 +1388,9 @@ bool SDLInputSource::HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev) auto it = GetControllerDataForJoystickId(ev->which); if (it == m_controllers.end()) return false; - if (ev->axis < it->joy_axis_used_in_gc.size() && it->joy_axis_used_in_gc[ev->axis]) - return false; // Will get handled by GC event - const u32 axis = ev->axis + std::size(s_sdl_axis_names); // Ensure we don't conflict with GC axes + if (ev->axis < it->joy_axis_used_in_pad.size() && it->joy_axis_used_in_pad[ev->axis]) + return false; // Will get handled by Gamepad event + const u32 axis = ev->axis + std::size(s_sdl_axis_setting_names); // Ensure we don't conflict with Gamepad axes const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, axis)); InputManager::InvokeEvents(key, NormalizeS16(ev->value)); return true; @@ -861,11 +1401,11 @@ bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev) auto it = GetControllerDataForJoystickId(ev->which); if (it == m_controllers.end()) return false; - if (ev->button < it->joy_button_used_in_gc.size() && it->joy_button_used_in_gc[ev->button]) - return false; // Will get handled by GC event - const u32 button = ev->button + std::size(s_sdl_button_names); // Ensure we don't conflict with GC buttons + if (ev->button < it->joy_button_used_in_pad.size() && it->joy_button_used_in_pad[ev->button]) + return false; // Will get handled by Gamepad event + const u32 button = ev->button + std::size(s_sdl_button_setting_names); // Ensure we don't conflict with Gamepad buttons const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, button)); - InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f); + InputManager::InvokeEvents(key, static_cast(ev->down)); return true; } @@ -904,7 +1444,7 @@ std::vector SDLInputSource::EnumerateMotors() { key.source_index = cd.player_id; - if (cd.use_game_controller_rumble || cd.haptic_left_right_effect) + if (cd.use_gamepad_rumble || cd.haptic_left_right_effect) { // two motors key.source_subtype = InputSubclass::ControllerMotor; @@ -938,7 +1478,7 @@ bool SDLInputSource::GetGenericBindingMapping(const std::string_view device, Inp if (it == m_controllers.end()) return false; - if (it->game_controller) + if (it->gamepad) { // assume all buttons are present. const s32 pid = player_id.value(); @@ -947,19 +1487,19 @@ bool SDLInputSource::GetGenericBindingMapping(const std::string_view device, Inp const GenericInputBinding negative = s_sdl_generic_binding_axis_mapping[i][0]; const GenericInputBinding positive = s_sdl_generic_binding_axis_mapping[i][1]; if (negative != GenericInputBinding::Unknown) - mapping->emplace_back(negative, fmt::format("SDL-{}/-{}", pid, s_sdl_axis_names[i])); + mapping->emplace_back(negative, fmt::format("SDL-{}/-{}", pid, s_sdl_axis_setting_names[i])); if (positive != GenericInputBinding::Unknown) - mapping->emplace_back(positive, fmt::format("SDL-{}/+{}", pid, s_sdl_axis_names[i])); + mapping->emplace_back(positive, fmt::format("SDL-{}/+{}", pid, s_sdl_axis_setting_names[i])); } for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++) { const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i]; if (binding != GenericInputBinding::Unknown) - mapping->emplace_back(binding, fmt::format("SDL-{}/{}", pid, s_sdl_button_names[i])); + mapping->emplace_back(binding, fmt::format("SDL-{}/{}", pid, s_sdl_button_setting_names[i])); } - if (it->use_game_controller_rumble || it->haptic_left_right_effect) + 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)); @@ -1017,9 +1557,9 @@ void SDLInputSource::SendRumbleUpdate(ControllerData* cd) // we'll update before this duration is elapsed static constexpr u32 DURATION = 65535; // SDL_MAX_RUMBLE_DURATION_MS - if (cd->use_game_controller_rumble) + if (cd->use_gamepad_rumble) { - SDL_GameControllerRumble(cd->game_controller, cd->rumble_intensity[0], cd->rumble_intensity[1], DURATION); + SDL_RumbleGamepad(cd->gamepad, cd->rumble_intensity[0], cd->rumble_intensity[1], DURATION); return; } @@ -1032,20 +1572,20 @@ void SDLInputSource::SendRumbleUpdate(ControllerData* cd) ef.leftright.large_magnitude = cd->rumble_intensity[0]; ef.leftright.small_magnitude = cd->rumble_intensity[1]; ef.leftright.length = DURATION; - SDL_HapticUpdateEffect(cd->haptic, cd->haptic_left_right_effect, &ef); - SDL_HapticRunEffect(cd->haptic, cd->haptic_left_right_effect, SDL_HAPTIC_INFINITY); + SDL_UpdateHapticEffect(cd->haptic, cd->haptic_left_right_effect, &ef); + SDL_RunHapticEffect(cd->haptic, cd->haptic_left_right_effect, SDL_HAPTIC_INFINITY); } else { - SDL_HapticStopEffect(cd->haptic, cd->haptic_left_right_effect); + SDL_StopHapticEffect(cd->haptic, cd->haptic_left_right_effect); } } else { const float strength = static_cast(std::max(cd->rumble_intensity[0], cd->rumble_intensity[1])) * (1.0f / 65535.0f); if (strength > 0.0f) - SDL_HapticRumblePlay(cd->haptic, strength, DURATION); + SDL_PlayHapticRumble(cd->haptic, strength, DURATION); else - SDL_HapticRumbleStop(cd->haptic); + SDL_StopHapticRumble(cd->haptic); } } diff --git a/pcsx2/Input/SDLInputSource.h b/pcsx2/Input/SDLInputSource.h index 5593c88918..e9f67b6d42 100644 --- a/pcsx2/Input/SDLInputSource.h +++ b/pcsx2/Input/SDLInputSource.h @@ -5,7 +5,7 @@ #include "Input/InputSource.h" -#include +#include #include #include @@ -26,6 +26,7 @@ public: void UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock) override; bool ReloadDevices() override; void Shutdown() override; + bool IsInitialized() override; void PollEvents() override; std::vector> EnumerateDevices() override; @@ -35,7 +36,7 @@ public: void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; std::optional ParseKeyString(const std::string_view device, const std::string_view binding) override; - TinyString ConvertKeyToString(InputBindingKey key) override; + TinyString ConvertKeyToString(InputBindingKey key, bool display = false, bool migration = false) override; TinyString ConvertKeyToIcon(InputBindingKey key) override; bool ProcessSDLEvent(const SDL_Event* event); @@ -50,17 +51,17 @@ private: struct ControllerData { SDL_Haptic* haptic; - SDL_GameController* game_controller; + SDL_Gamepad* gamepad; SDL_Joystick* joystick; u16 rumble_intensity[2]; int haptic_left_right_effect; - int joystick_id; + SDL_JoystickID joystick_id; int player_id; - bool use_game_controller_rumble; + bool use_gamepad_rumble; - // Used to disable Joystick controls that are used in GameController inputs so we don't get double events - std::vector joy_button_used_in_gc; - std::vector joy_axis_used_in_gc; + // Used to disable Joystick controls that are used in Gamepad inputs so we don't get double events + std::vector joy_button_used_in_pad; + std::vector joy_axis_used_in_pad; // Track last hat state so we can send "unpressed" events. std::vector last_hat_state; @@ -77,10 +78,10 @@ private: ControllerDataVector::iterator GetControllerDataForPlayerId(int id); int GetFreePlayerId() const; - bool OpenDevice(int index, bool is_gamecontroller); + bool OpenDevice(int index, bool is_gamepad); bool CloseDevice(int joystick_index); - bool HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev); - bool HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev); + bool HandleGamepadAxisEvent(const SDL_GamepadAxisEvent* ev); + bool HandleGamepadButtonEvent(const SDL_GamepadButtonEvent* ev); bool HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev); bool HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev); bool HandleJoystickHatEvent(const SDL_JoyHatEvent* ev); @@ -88,13 +89,22 @@ private: ControllerDataVector m_controllers; + // ConvertKeyToString and ConvertKeyToIcon can inspect the + // currently connected gamepad to provide matching labels + // ParseKeyString can also inspect the gamepad for migrations + // Those functions can be called on the main thread, while + // gamepad addition/removal is done on the CPU thread + std::mutex m_controllers_key_mutex; + + std::vector m_gamepads_needing_migration; + std::array m_led_colors{}; std::vector> m_sdl_hints; bool m_sdl_subsystem_initialized = false; - bool m_controller_enhanced_mode = false; - bool m_controller_raw_mode = false; - bool m_controller_ps5_player_led = false; + bool m_enable_enhanced_reports = false; + bool m_use_raw_input = false; + bool m_enable_ps5_player_leds = false; #ifdef __APPLE__ bool m_enable_iokit_driver = false; diff --git a/pcsx2/Input/XInputSource.cpp b/pcsx2/Input/XInputSource.cpp index 138776f04f..ab12829f98 100644 --- a/pcsx2/Input/XInputSource.cpp +++ b/pcsx2/Input/XInputSource.cpp @@ -14,7 +14,7 @@ #include -static const char* s_axis_names[XInputSource::NUM_AXES] = { +static const char* s_axis_setting_names[XInputSource::NUM_AXES] = { "LeftX", // AXIS_LEFTX "LeftY", // AXIS_LEFTY "RightX", // AXIS_RIGHTX @@ -22,6 +22,14 @@ static const char* s_axis_names[XInputSource::NUM_AXES] = { "LeftTrigger", // AXIS_TRIGGERLEFT "RightTrigger", // AXIS_TRIGGERRIGHT }; +static const char* s_axis_names[XInputSource::NUM_AXES] = { + "Left X", // AXIS_LEFTX + "Left Y", // AXIS_LEFTY + "Right X", // AXIS_RIGHTX + "Right Y", // AXIS_RIGHTY + "Left Trigger", // AXIS_TRIGGERLEFT + "Right Trigger", // AXIS_TRIGGERRIGHT +}; static constexpr const char* s_axis_icons[][2] = { {ICON_PF_LEFT_ANALOG_LEFT, ICON_PF_LEFT_ANALOG_RIGHT}, // AXIS_LEFTX {ICON_PF_LEFT_ANALOG_UP, ICON_PF_LEFT_ANALOG_DOWN}, // AXIS_LEFTY @@ -39,7 +47,7 @@ static const GenericInputBinding s_xinput_generic_binding_axis_mapping[][2] = { {GenericInputBinding::Unknown, GenericInputBinding::R2}, // AXIS_TRIGGERRIGHT }; -static const char* s_button_names[XInputSource::NUM_BUTTONS] = { +static const char* s_button_setting_names[XInputSource::NUM_BUTTONS] = { "DPadUp", // XINPUT_GAMEPAD_DPAD_UP "DPadDown", // XINPUT_GAMEPAD_DPAD_DOWN "DPadLeft", // XINPUT_GAMEPAD_DPAD_LEFT @@ -56,6 +64,24 @@ static const char* s_button_names[XInputSource::NUM_BUTTONS] = { "Y", // XINPUT_GAMEPAD_Y "Guide", // XINPUT_GAMEPAD_GUIDE }; +static const char* s_button_names[XInputSource::NUM_BUTTONS] = { + "D-Pad Up", // XINPUT_GAMEPAD_DPAD_UP + "D-Pad Down", // XINPUT_GAMEPAD_DPAD_DOWN + "D-Pad Left", // XINPUT_GAMEPAD_DPAD_LEFT + "D-Pad Right", // XINPUT_GAMEPAD_DPAD_RIGHT + "Start", // XINPUT_GAMEPAD_START + "Back", // XINPUT_GAMEPAD_BACK + "Left Stick", // XINPUT_GAMEPAD_LEFT_THUMB + "Right Stick", // XINPUT_GAMEPAD_RIGHT_THUMB + "Left Shoulder", // XINPUT_GAMEPAD_LEFT_SHOULDER + "Right Shoulder", // XINPUT_GAMEPAD_RIGHT_SHOULDER + "A", // XINPUT_GAMEPAD_A + "B", // XINPUT_GAMEPAD_B + "X", // XINPUT_GAMEPAD_X + "Y", // XINPUT_GAMEPAD_Y + "Guide", // XINPUT_GAMEPAD_GUIDE +}; + static const u16 s_button_masks[XInputSource::NUM_BUTTONS] = { XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT, XINPUT_GAMEPAD_START, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, @@ -133,6 +159,8 @@ bool XInputSource::Initialize(SettingsInterface& si, std::unique_lock XInputSource::ParseKeyString(const std::string_vi { // likely an axis const std::string_view axis_name(binding.substr(1)); - for (u32 i = 0; i < std::size(s_axis_names); i++) + for (u32 i = 0; i < std::size(s_axis_setting_names); i++) { - if (axis_name == s_axis_names[i]) + if (axis_name == s_axis_setting_names[i]) { // found an axis! key.source_subtype = InputSubclass::ControllerAxis; @@ -302,9 +335,9 @@ std::optional XInputSource::ParseKeyString(const std::string_vi else { // must be a button - for (u32 i = 0; i < std::size(s_button_names); i++) + for (u32 i = 0; i < std::size(s_button_setting_names); i++) { - if (binding == s_button_names[i]) + if (binding == s_button_setting_names[i]) { key.source_subtype = InputSubclass::ControllerButton; key.data = i; @@ -317,24 +350,33 @@ std::optional XInputSource::ParseKeyString(const std::string_vi return std::nullopt; } -TinyString XInputSource::ConvertKeyToString(InputBindingKey key) +TinyString XInputSource::ConvertKeyToString(InputBindingKey key, bool display, bool migration) { TinyString ret; if (key.source_type == InputSourceType::XInput) { - if (key.source_subtype == InputSubclass::ControllerAxis && key.data < std::size(s_axis_names)) + if (key.source_subtype == InputSubclass::ControllerAxis && key.data < std::size(s_axis_setting_names)) { const char modifier = key.modifier == InputModifier::Negate ? '-' : '+'; - ret.format("XInput-{}/{}{}", static_cast(key.source_index), modifier, s_axis_names[key.data]); + if (display) + ret.format("XInput-{} {}{}", static_cast(key.source_index), modifier, s_axis_names[key.data]); + else + ret.format("XInput-{}/{}{}", static_cast(key.source_index), modifier, s_axis_setting_names[key.data]); } - else if (key.source_subtype == InputSubclass::ControllerButton && key.data < std::size(s_button_names)) + else if (key.source_subtype == InputSubclass::ControllerButton && key.data < std::size(s_button_setting_names)) { - ret.format("XInput-{}/{}", static_cast(key.source_index), s_button_names[key.data]); + if (display) + ret.format("XInput-{} {}", static_cast(key.source_index), s_button_names[key.data]); + else + ret.format("XInput-{}/{}", static_cast(key.source_index), s_button_setting_names[key.data]); } else if (key.source_subtype == InputSubclass::ControllerMotor) { - ret.format("XInput-{}/{}Motor", static_cast(key.source_index), key.data ? "Large" : "Small"); + if (display) + ret.format("XInput-{} {} Motor", static_cast(key.source_index), key.data ? "Large" : "Small"); + else + ret.format("XInput-{}/{}Motor", static_cast(key.source_index), key.data ? "Large" : "Small"); } } @@ -404,16 +446,16 @@ bool XInputSource::GetGenericBindingMapping(const std::string_view device, Input const GenericInputBinding negative = s_xinput_generic_binding_axis_mapping[i][0]; const GenericInputBinding positive = s_xinput_generic_binding_axis_mapping[i][1]; if (negative != GenericInputBinding::Unknown) - mapping->emplace_back(negative, fmt::format("XInput-{}/-{}", pid, s_axis_names[i])); + mapping->emplace_back(negative, fmt::format("XInput-{}/-{}", pid, s_axis_setting_names[i])); if (positive != GenericInputBinding::Unknown) - mapping->emplace_back(positive, fmt::format("XInput-{}/+{}", pid, s_axis_names[i])); + mapping->emplace_back(positive, fmt::format("XInput-{}/+{}", pid, s_axis_setting_names[i])); } for (u32 i = 0; i < std::size(s_xinput_generic_binding_button_mapping); i++) { const GenericInputBinding binding = s_xinput_generic_binding_button_mapping[i]; if (binding != GenericInputBinding::Unknown) - mapping->emplace_back(binding, fmt::format("XInput-{}/{}", pid, s_button_names[i])); + mapping->emplace_back(binding, fmt::format("XInput-{}/{}", pid, s_button_setting_names[i])); } if (m_controllers[pid].has_small_motor) diff --git a/pcsx2/Input/XInputSource.h b/pcsx2/Input/XInputSource.h index 7ec20548c0..668dd3b9af 100644 --- a/pcsx2/Input/XInputSource.h +++ b/pcsx2/Input/XInputSource.h @@ -75,6 +75,7 @@ public: void UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock) override; bool ReloadDevices() override; void Shutdown() override; + bool IsInitialized() override; void PollEvents() override; std::vector> EnumerateDevices() override; @@ -84,7 +85,7 @@ public: void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; std::optional ParseKeyString(const std::string_view device, const std::string_view binding) override; - TinyString ConvertKeyToString(InputBindingKey key) override; + TinyString ConvertKeyToString(InputBindingKey key, bool display = false, bool migration = false) override; TinyString ConvertKeyToIcon(InputBindingKey key) override; private: diff --git a/pcsx2/SIO/Pad/Pad.cpp b/pcsx2/SIO/Pad/Pad.cpp index a4fc84ba0b..30818bda81 100644 --- a/pcsx2/SIO/Pad/Pad.cpp +++ b/pcsx2/SIO/Pad/Pad.cpp @@ -167,8 +167,8 @@ void Pad::SetDefaultControllerConfig(SettingsInterface& si) InputManager::InputSourceToString(static_cast(i)), InputManager::GetInputSourceDefaultEnabled(static_cast(i))); } - si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", false); - si.SetBoolValue("InputSources", "SDLPS5PlayerLED", false); + si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", true); + si.SetBoolValue("InputSources", "SDLPS5PlayerLED", true); si.SetBoolValue("Pad", "MultitapPort1", false); si.SetBoolValue("Pad", "MultitapPort2", false); si.SetFloatValue("Pad", "PointerXScale", 8.0f); diff --git a/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp b/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp index 642a201f86..d997046421 100644 --- a/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp +++ b/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp @@ -27,7 +27,7 @@ namespace usb_pad { DestroyEffects(); - SDL_HapticClose(m_haptic); + SDL_CloseHaptic(m_haptic); m_haptic = nullptr; } } @@ -45,7 +45,7 @@ namespace usb_pad return nullptr; } - SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick); + SDL_Haptic* haptic = SDL_OpenHapticFromJoystick(joystick); if (!haptic) { Console.Error(fmt::format("Haptic is not supported on {}.", device)); @@ -77,16 +77,16 @@ namespace usb_pad // - Simagic Alpha Mini: Does NOT implement infinite durations (stops after some time, seeking hard numbers) constexpr u32 length = SDL_HAPTIC_INFINITY; - const unsigned int supported = SDL_HapticQuery(m_haptic); + const unsigned int supported = SDL_GetHapticFeatures(m_haptic); if (supported & SDL_HAPTIC_CONSTANT) { m_constant_effect.type = SDL_HAPTIC_CONSTANT; m_constant_effect.constant.direction.type = SDL_HAPTIC_STEERING_AXIS; m_constant_effect.constant.length = length; - m_constant_effect_id = SDL_HapticNewEffect(m_haptic, &m_constant_effect); + m_constant_effect_id = SDL_CreateHapticEffect(m_haptic, &m_constant_effect); if (m_constant_effect_id < 0) - Console.Error("SDL_HapticNewEffect() for constant failed: %s", SDL_GetError()); + Console.Error("SDL_CreateHapticEffect() for constant failed: %s", SDL_GetError()); } else { @@ -99,9 +99,9 @@ namespace usb_pad m_spring_effect.condition.direction.type = SDL_HAPTIC_STEERING_AXIS; m_spring_effect.condition.length = length; - m_spring_effect_id = SDL_HapticNewEffect(m_haptic, &m_spring_effect); + m_spring_effect_id = SDL_CreateHapticEffect(m_haptic, &m_spring_effect); if (m_spring_effect_id < 0) - Console.Error("SDL_HapticNewEffect() for spring failed: %s", SDL_GetError()); + Console.Error("SDL_CreateHapticEffect() for spring failed: %s", SDL_GetError()); } else { @@ -114,9 +114,9 @@ namespace usb_pad m_damper_effect.condition.direction.type = SDL_HAPTIC_STEERING_AXIS; m_damper_effect.condition.length = length; - m_damper_effect_id = SDL_HapticNewEffect(m_haptic, &m_damper_effect); + m_damper_effect_id = SDL_CreateHapticEffect(m_haptic, &m_damper_effect); if (m_damper_effect_id < 0) - Console.Error("SDL_HapticNewEffect() for damper failed: %s", SDL_GetError()); + Console.Error("SDL_CreateHapticEffect() for damper failed: %s", SDL_GetError()); } else { @@ -129,9 +129,9 @@ namespace usb_pad m_friction_effect.condition.direction.type = SDL_HAPTIC_STEERING_AXIS; m_friction_effect.condition.length = length; - m_friction_effect_id = SDL_HapticNewEffect(m_haptic, &m_friction_effect); + m_friction_effect_id = SDL_CreateHapticEffect(m_haptic, &m_friction_effect); if (m_friction_effect_id < 0) - Console.Error("SDL_HapticNewEffect() for friction failed: %s", SDL_GetError()); + Console.Error("SDL_CreateHapticEffect() for friction failed: %s", SDL_GetError()); } else { @@ -149,10 +149,10 @@ namespace usb_pad { if (m_friction_effect_running) { - SDL_HapticStopEffect(m_haptic, m_friction_effect_id); + SDL_StopHapticEffect(m_haptic, m_friction_effect_id); m_friction_effect_running = false; } - SDL_HapticDestroyEffect(m_haptic, m_friction_effect_id); + SDL_DestroyHapticEffect(m_haptic, m_friction_effect_id); m_friction_effect_id = -1; } @@ -160,10 +160,10 @@ namespace usb_pad { if (m_damper_effect_running) { - SDL_HapticStopEffect(m_haptic, m_damper_effect_id); + SDL_StopHapticEffect(m_haptic, m_damper_effect_id); m_damper_effect_running = false; } - SDL_HapticDestroyEffect(m_haptic, m_damper_effect_id); + SDL_DestroyHapticEffect(m_haptic, m_damper_effect_id); m_damper_effect_id = -1; } @@ -171,10 +171,10 @@ namespace usb_pad { if (m_spring_effect_running) { - SDL_HapticStopEffect(m_haptic, m_spring_effect_id); + SDL_StopHapticEffect(m_haptic, m_spring_effect_id); m_spring_effect_running = false; } - SDL_HapticDestroyEffect(m_haptic, m_spring_effect_id); + SDL_DestroyHapticEffect(m_haptic, m_spring_effect_id); m_spring_effect_id = -1; } @@ -182,10 +182,10 @@ namespace usb_pad { if (m_constant_effect_running) { - SDL_HapticStopEffect(m_haptic, m_constant_effect_id); + SDL_StopHapticEffect(m_haptic, m_constant_effect_id); m_constant_effect_running = false; } - SDL_HapticDestroyEffect(m_haptic, m_constant_effect_id); + SDL_DestroyHapticEffect(m_haptic, m_constant_effect_id); m_constant_effect_id = -1; } } @@ -199,8 +199,8 @@ namespace usb_pad if (m_constant_effect.constant.level != new_level) { m_constant_effect.constant.level = new_level; - if (SDL_HapticUpdateEffect(m_haptic, m_constant_effect_id, &m_constant_effect) != 0) - Console.Warning("SDL_HapticUpdateEffect() for constant failed: %s", SDL_GetError()); + if (!SDL_UpdateHapticEffect(m_haptic, m_constant_effect_id, &m_constant_effect)) + Console.Warning("SDL_UpdateHapticEffect() for constant failed: %s", SDL_GetError()); } // Avoid re-running already-running effects by default. Re-running a running effect @@ -217,10 +217,10 @@ namespace usb_pad // This is the reason for use_ffb_dropout_workaround. if (!m_constant_effect_running || use_ffb_dropout_workaround) { - if (SDL_HapticRunEffect(m_haptic, m_constant_effect_id, SDL_HAPTIC_INFINITY) == 0) + if (SDL_RunHapticEffect(m_haptic, m_constant_effect_id, SDL_HAPTIC_INFINITY)) m_constant_effect_running = true; else - Console.Error("SDL_HapticRunEffect() for constant failed: %s", SDL_GetError()); + Console.Error("SDL_RunHapticEffect() for constant failed: %s", SDL_GetError()); } } @@ -248,15 +248,15 @@ namespace usb_pad m_spring_effect.condition.deadband[0] = ClampU16(ff.u.condition.deadband); m_spring_effect.condition.center[0] = ClampS16(ff.u.condition.center); - if (SDL_HapticUpdateEffect(m_haptic, m_spring_effect_id, &m_spring_effect) != 0) - Console.Warning("SDL_HapticUpdateEffect() for spring failed: %s", SDL_GetError()); + if (!SDL_UpdateHapticEffect(m_haptic, m_spring_effect_id, &m_spring_effect)) + Console.Warning("SDL_UpdateHapticEffect() for spring failed: %s", SDL_GetError()); if (!m_spring_effect_running) { - if (SDL_HapticRunEffect(m_haptic, m_spring_effect_id, SDL_HAPTIC_INFINITY) == 0) + if (SDL_RunHapticEffect(m_haptic, m_spring_effect_id, SDL_HAPTIC_INFINITY)) m_spring_effect_running = true; else - Console.Error("SDL_HapticRunEffect() for spring failed: %s", SDL_GetError()); + Console.Error("SDL_RunHapticEffect() for spring failed: %s", SDL_GetError()); } } @@ -272,15 +272,15 @@ namespace usb_pad m_damper_effect.condition.deadband[0] = ClampU16(ff.u.condition.deadband); m_damper_effect.condition.center[0] = ClampS16(ff.u.condition.center); - if (SDL_HapticUpdateEffect(m_haptic, m_damper_effect_id, &m_damper_effect) != 0) - Console.Warning("SDL_HapticUpdateEffect() for damper failed: %s", SDL_GetError()); + if (!SDL_UpdateHapticEffect(m_haptic, m_damper_effect_id, &m_damper_effect)) + Console.Warning("SDL_UpdateHapticEffect() for damper failed: %s", SDL_GetError()); if (!m_damper_effect_running) { - if (SDL_HapticRunEffect(m_haptic, m_damper_effect_id, SDL_HAPTIC_INFINITY) == 0) + if (SDL_RunHapticEffect(m_haptic, m_damper_effect_id, SDL_HAPTIC_INFINITY)) m_damper_effect_running = true; else - Console.Error("SDL_HapticRunEffect() for damper failed: %s", SDL_GetError()); + Console.Error("SDL_RunHapticEffect() for damper failed: %s", SDL_GetError()); } } @@ -296,12 +296,12 @@ namespace usb_pad m_friction_effect.condition.deadband[0] = ClampU16(ff.u.condition.deadband); m_friction_effect.condition.center[0] = ClampS16(ff.u.condition.center); - if (SDL_HapticUpdateEffect(m_haptic, m_friction_effect_id, &m_friction_effect) != 0) + if (!SDL_UpdateHapticEffect(m_haptic, m_friction_effect_id, &m_friction_effect)) { - if (!m_friction_effect_running && SDL_HapticRunEffect(m_haptic, m_friction_effect_id, SDL_HAPTIC_INFINITY) == 0) + if (!m_friction_effect_running && SDL_RunHapticEffect(m_haptic, m_friction_effect_id, SDL_HAPTIC_INFINITY)) m_friction_effect_running = true; else - Console.Error("SDL_HapticUpdateEffect() for friction failed: %s", SDL_GetError()); + Console.Error("SDL_UpdateHapticEffect() for friction failed: %s", SDL_GetError()); } } @@ -309,8 +309,8 @@ namespace usb_pad { if (m_autocenter_supported) { - if (SDL_HapticSetAutocenter(m_haptic, value) != 0) - Console.Warning("SDL_HapticSetAutocenter() failed: %s", SDL_GetError()); + if (!SDL_SetHapticAutocenter(m_haptic, value)) + Console.Warning("SDL_SetHapticAutocenter() failed: %s", SDL_GetError()); } } @@ -322,7 +322,7 @@ namespace usb_pad { if (m_constant_effect_running) { - SDL_HapticStopEffect(m_haptic, m_constant_effect_id); + SDL_StopHapticEffect(m_haptic, m_constant_effect_id); m_constant_effect_running = false; } } @@ -332,7 +332,7 @@ namespace usb_pad { if (m_spring_effect_running) { - SDL_HapticStopEffect(m_haptic, m_spring_effect_id); + SDL_StopHapticEffect(m_haptic, m_spring_effect_id); m_spring_effect_running = false; } } @@ -342,7 +342,7 @@ namespace usb_pad { if (m_damper_effect_running) { - SDL_HapticStopEffect(m_haptic, m_damper_effect_id); + SDL_StopHapticEffect(m_haptic, m_damper_effect_id); m_damper_effect_running = false; } } @@ -352,7 +352,7 @@ namespace usb_pad { if (m_friction_effect_running) { - SDL_HapticStopEffect(m_haptic, m_friction_effect_id); + SDL_StopHapticEffect(m_haptic, m_friction_effect_id); m_friction_effect_running = false; } } diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 72e3bf8535..5bdb555442 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -624,10 +624,10 @@ void VMManager::ReloadInputSources() LoadInputBindings(*si, lock); } -void VMManager::ReloadInputBindings() +void VMManager::ReloadInputBindings(bool force) { // skip loading bindings if we're not running, since it'll get done on startup anyway - if (!HasValidVM()) + if (!force && !HasValidVM()) return; FPControlRegisterBackup fpcr_backup(FPControlRegister::GetDefault()); diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index 11af8cd519..44497ed606 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -114,7 +114,8 @@ namespace VMManager void ReloadInputSources(); /// Reloads input bindings. - void ReloadInputBindings(); + /// Can be forced to load even when there is not an active virtual machine. + void ReloadInputBindings(bool force = false); /// Returns the save state filename for the given game serial/crc. std::string GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot); diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index 1d2efe083b..4535dbb8a8 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -31,7 +31,7 @@ - %(AdditionalIncludeDirectories);$(DepsIncludeDir)\SDL2 + %(AdditionalIncludeDirectories);$(DepsIncludeDir)\SDL3 %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\winwil\include diff --git a/tests/ctest/core/CMakeLists.txt b/tests/ctest/core/CMakeLists.txt index 1f4d523cdf..5f01feaf10 100644 --- a/tests/ctest/core/CMakeLists.txt +++ b/tests/ctest/core/CMakeLists.txt @@ -60,16 +60,16 @@ else() target_sources(core_test PRIVATE ${multi_isa_sources}) endif() -if(WIN32 AND TARGET SDL2::SDL2) - # Copy SDL2 DLL to binary directory. +if(WIN32 AND TARGET SDL3::SDL3) + # Copy SDL3 DLL to binary directory. if(CMAKE_BUILD_TYPE STREQUAL "Debug") - get_property(SDL2_DLL_PATH TARGET SDL2::SDL2 PROPERTY IMPORTED_LOCATION_DEBUG) + get_property(SDL3_DLL_PATH TARGET SDL3::SDL3 PROPERTY IMPORTED_LOCATION_DEBUG) else() - get_property(SDL2_DLL_PATH TARGET SDL2::SDL2 PROPERTY IMPORTED_LOCATION_RELEASE) + get_property(SDL3_DLL_PATH TARGET SDL3::SDL3 PROPERTY IMPORTED_LOCATION_RELEASE) endif() - if(SDL2_DLL_PATH) + if(SDL3_DLL_PATH) add_custom_command(TARGET core_test POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "$" - COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${SDL2_DLL_PATH}" "$") + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${SDL3_DLL_PATH}" "$") endif() endif() diff --git a/tests/ctest/core/StubHost.cpp b/tests/ctest/core/StubHost.cpp index 7eee7ef840..01d1574058 100644 --- a/tests/ctest/core/StubHost.cpp +++ b/tests/ctest/core/StubHost.cpp @@ -39,6 +39,10 @@ std::unique_ptr Host::CreateHostProgressCallback() return ProgressCallback::CreateNullProgressCallback(); } +void Host::ReportInfoAsync(const std::string_view title, const std::string_view message) +{ +} + void Host::ReportErrorAsync(const std::string_view title, const std::string_view message) { }