diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt
index 7aa4f3e348..5f3f37f75d 100644
--- a/pcsx2-qt/CMakeLists.txt
+++ b/pcsx2-qt/CMakeLists.txt
@@ -11,6 +11,8 @@ target_sources(pcsx2-qt PRIVATE
AutoUpdaterDialog.cpp
AutoUpdaterDialog.h
AutoUpdaterDialog.ui
+ ColorPickerButton.cpp
+ ColorPickerButton.h
CoverDownloadDialog.cpp
CoverDownloadDialog.h
CoverDownloadDialog.ui
@@ -50,6 +52,7 @@ target_sources(pcsx2-qt PRIVATE
Settings/ControllerBindingWidget_DualShock2.ui
Settings/ControllerBindingWidgets.cpp
Settings/ControllerBindingWidgets.h
+ Settings/ControllerLEDSettingsDialog.ui
Settings/ControllerGlobalSettingsWidget.cpp
Settings/ControllerGlobalSettingsWidget.h
Settings/ControllerGlobalSettingsWidget.ui
diff --git a/pcsx2-qt/ColorPickerButton.cpp b/pcsx2-qt/ColorPickerButton.cpp
new file mode 100644
index 0000000000..470e878970
--- /dev/null
+++ b/pcsx2-qt/ColorPickerButton.cpp
@@ -0,0 +1,66 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+#include "ColorPickerButton.h"
+#include "QtUtils.h"
+
+#include
+
+ColorPickerButton::ColorPickerButton(QWidget* parent)
+ : QPushButton(parent)
+{
+ connect(this, &QPushButton::clicked, this, &ColorPickerButton::onClicked);
+ updateBackgroundColor();
+}
+
+u32 ColorPickerButton::color()
+{
+ return m_color;
+}
+
+void ColorPickerButton::setColor(u32 rgb)
+{
+ if (m_color == rgb)
+ return;
+
+ m_color = rgb;
+ updateBackgroundColor();
+}
+
+void ColorPickerButton::updateBackgroundColor()
+{
+ setStyleSheet(QStringLiteral("background-color: #%1;").arg(static_cast(m_color), 8, 16, QChar('0')));
+}
+
+void ColorPickerButton::onClicked()
+{
+ const u32 red = (m_color >> 16) & 0xff;
+ const u32 green = (m_color >> 8) & 0xff;
+ const u32 blue = m_color & 0xff;
+
+ const QColor initial(QColor::fromRgb(red, green, blue));
+ const QColor selected(QColorDialog::getColor(initial, QtUtils::GetRootWidget(this), tr("Select LED Color")));
+
+ // QColorDialog returns Invalid on cancel, and apparently initial == Invalid is true...
+ if (!selected.isValid() || initial == selected)
+ return;
+
+ const u32 new_rgb =
+ (static_cast(selected.red()) << 16) | (static_cast(selected.green()) << 8) | static_cast(selected.blue());
+ m_color = new_rgb;
+ updateBackgroundColor();
+ emit colorChanged(new_rgb);
+}
diff --git a/pcsx2-qt/ColorPickerButton.h b/pcsx2-qt/ColorPickerButton.h
new file mode 100644
index 0000000000..53427d5521
--- /dev/null
+++ b/pcsx2-qt/ColorPickerButton.h
@@ -0,0 +1,42 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#include "common/Pcsx2Defs.h"
+#include
+
+class ColorPickerButton : public QPushButton
+{
+ Q_OBJECT
+
+public:
+ ColorPickerButton(QWidget* parent);
+
+Q_SIGNALS:
+ void colorChanged(quint32 new_color);
+
+public Q_SLOTS:
+ quint32 color();
+ void setColor(quint32 rgb);
+
+private Q_SLOTS:
+ void onClicked();
+
+private:
+ void updateBackgroundColor();
+
+ u32 m_color = 0;
+};
diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp
index f122c2a91f..53f079a917 100644
--- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp
@@ -22,6 +22,10 @@
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
+#ifdef SDL_BUILD
+#include "pcsx2/Frontend/SDLInputSource.h"
+#endif
+
ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog)
: QWidget(parent)
, m_dialog(dialog)
@@ -30,8 +34,16 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
SettingsInterface* sif = dialog->getProfileSettingsInterface();
+#ifdef SDL_BUILD
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false);
+ connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled);
+ connect(m_ui.ledSettings, &QToolButton::clicked, this, &ControllerGlobalSettingsWidget::ledSettingsClicked);
+#else
+ m_ui.enableSDLSource->setEnabled(false);
+ m_ui.ledSettings->setEnabled(false);
+#endif
+
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort1, "Pad", "MultitapPort1", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort2, "Pad", "MultitapPort2", false);
@@ -66,12 +78,13 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
m_ui.profileSettings = nullptr;
}
- connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled);
for (QCheckBox* cb : {m_ui.multitapPort1, m_ui.multitapPort2})
connect(cb, &QCheckBox::stateChanged, this, [this]() { emit bindingSetupChanged(); });
- connect(m_ui.pointerXScale, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(value)); });
- connect(m_ui.pointerYScale, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(value)); });
+ connect(m_ui.pointerXScale, &QSlider::valueChanged, this,
+ [this](int value) { m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(value)); });
+ connect(m_ui.pointerYScale, &QSlider::valueChanged, this,
+ [this](int value) { m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(value)); });
m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerXScale->value()));
m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerYScale->value()));
@@ -106,4 +119,40 @@ void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled()
{
const bool enabled = m_ui.enableSDLSource->isChecked();
m_ui.enableSDLEnhancedMode->setEnabled(enabled);
+ m_ui.ledSettings->setEnabled(enabled);
+}
+
+void ControllerGlobalSettingsWidget::ledSettingsClicked()
+{
+ ControllerLEDSettingsDialog dialog(this, m_dialog);
+ dialog.exec();
+}
+
+ControllerLEDSettingsDialog::ControllerLEDSettingsDialog(QWidget* parent, ControllerSettingsDialog* dialog)
+ : QDialog(parent)
+ , m_dialog(dialog)
+{
+ m_ui.setupUi(this);
+
+ linkButton(m_ui.SDL0LED, 0);
+ linkButton(m_ui.SDL1LED, 1);
+ linkButton(m_ui.SDL2LED, 2);
+ linkButton(m_ui.SDL3LED, 3);
+
+ connect(m_ui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &QDialog::accept);
+}
+
+ControllerLEDSettingsDialog::~ControllerLEDSettingsDialog() = default;
+
+void ControllerLEDSettingsDialog::linkButton(ColorPickerButton* button, u32 player_id)
+{
+#ifdef SDL_BUILD
+ std::string key(fmt::format("Player{}LED", player_id));
+ const u32 current_value = SDLInputSource::ParseRGBForPlayerId(m_dialog->getStringValue("SDLExtra", key.c_str(), ""), player_id);
+ button->setColor(current_value);
+
+ connect(button, &ColorPickerButton::colorChanged, this, [this, player_id, key = std::move(key)](u32 new_rgb) {
+ m_dialog->setStringValue("SDLExtra", key.c_str(), fmt::format("{:06X}", new_rgb).c_str());
+ });
+#endif
}
diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h
index 476971585a..e748c523cd 100644
--- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h
+++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h
@@ -20,7 +20,10 @@
#include
#include
+#include "ColorPickerButton.h"
+
#include "ui_ControllerGlobalSettingsWidget.h"
+#include "ui_ControllerLEDSettingsDialog.h"
class ControllerSettingsDialog;
@@ -38,9 +41,26 @@ public:
Q_SIGNALS:
void bindingSetupChanged();
-private:
+private Q_SLOTS:
void updateSDLOptionsEnabled();
+ void ledSettingsClicked();
+private:
Ui::ControllerGlobalSettingsWidget m_ui;
ControllerSettingsDialog* m_dialog;
};
+
+class ControllerLEDSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ ControllerLEDSettingsDialog(QWidget* parent, ControllerSettingsDialog* dialog);
+ ~ControllerLEDSettingsDialog();
+
+private:
+ void linkButton(ColorPickerButton* button, u32 player_id);
+
+ Ui::ControllerLEDSettingsDialog m_ui;
+ ControllerSettingsDialog* m_dialog;
+};
diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui
index 434863a563..e01f940672 100644
--- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui
+++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui
@@ -50,11 +50,27 @@
-
-
-
- DualShock 4 / DualSense Enhanced Mode
-
-
+
+
-
+
+
+ DualShock 4 / DualSense Enhanced Mode
+
+
+
+ -
+
+
+ Controller LED Settings
+
+
+
+ ..
+
+
+
+
+
diff --git a/pcsx2-qt/Settings/ControllerLEDSettingsDialog.ui b/pcsx2-qt/Settings/ControllerLEDSettingsDialog.ui
new file mode 100644
index 0000000000..4fd71b4331
--- /dev/null
+++ b/pcsx2-qt/Settings/ControllerLEDSettingsDialog.ui
@@ -0,0 +1,83 @@
+
+
+ ControllerLEDSettingsDialog
+
+
+
+ 0
+ 0
+ 501
+ 108
+
+
+
+ Controller LED Settings
+
+
+ -
+
+
+ SDL-0 LED
+
+
+
-
+
+
+
+
+
+ -
+
+
+ SDL-1 LED
+
+
+
-
+
+
+
+
+
+ -
+
+
+ SDL-2 LED
+
+
+
-
+
+
+
+
+
+ -
+
+
+ SDL-3 LED
+
+
+
-
+
+
+
+
+
+ -
+
+
+ QDialogButtonBox::Close
+
+
+
+
+
+
+
+ ColorPickerButton
+ QPushButton
+
+
+
+
+
+
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj
index dee3c01bc6..51a4441383 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj
+++ b/pcsx2-qt/pcsx2-qt.vcxproj
@@ -46,6 +46,7 @@
$(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories)
$(SolutionDir)3rdparty\demangler\include;%(AdditionalIncludeDirectories)
$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories);
+ $(SolutionDir)3rdparty\sdl2\include;$(SolutionDir)3rdparty\sdl2\SDL\include;%(AdditionalIncludeDirectories)
$(ProjectDir);$(SolutionDir)pcsx2;%(AdditionalIncludeDirectories)
%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Models
@@ -135,6 +136,7 @@
+
@@ -211,6 +213,7 @@
+
@@ -292,6 +295,7 @@
+
@@ -414,6 +418,9 @@
Document
+
+ Document
+
@@ -430,4 +437,4 @@
-
\ No newline at end of file
+
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters
index 5d392fc6a3..15d9889b58 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj.filters
+++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters
@@ -27,9 +27,7 @@
-
- Resources
-
+
@@ -313,6 +311,10 @@
moc
+
+
+ moc
+
@@ -459,6 +461,7 @@
Debugger\Models
+
@@ -577,6 +580,9 @@
Debugger
+
+ Settings
+
diff --git a/pcsx2-qt/resources/icons/black/svg/lightbulb-line.svg b/pcsx2-qt/resources/icons/black/svg/lightbulb-line.svg
new file mode 100644
index 0000000000..9370abc1a6
--- /dev/null
+++ b/pcsx2-qt/resources/icons/black/svg/lightbulb-line.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pcsx2-qt/resources/icons/white/svg/lightbulb-line.svg b/pcsx2-qt/resources/icons/white/svg/lightbulb-line.svg
new file mode 100644
index 0000000000..454dc0fe79
--- /dev/null
+++ b/pcsx2-qt/resources/icons/white/svg/lightbulb-line.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pcsx2-qt/resources/resources.qrc b/pcsx2-qt/resources/resources.qrc
index 38947f69f5..676395d341 100644
--- a/pcsx2-qt/resources/resources.qrc
+++ b/pcsx2-qt/resources/resources.qrc
@@ -37,6 +37,7 @@
icons/black/svg/image-fill.svg
icons/black/svg/keyboard-line.svg
icons/black/svg/layout-grid-line.svg
+ icons/black/svg/lightbulb-line.svg
icons/black/svg/list-check.svg
icons/black/svg/login-box-line.svg
icons/black/svg/pause-line.svg
@@ -96,6 +97,7 @@
icons/white/svg/image-fill.svg
icons/white/svg/keyboard-line.svg
icons/white/svg/layout-grid-line.svg
+ icons/white/svg/lightbulb-line.svg
icons/white/svg/list-check.svg
icons/white/svg/login-box-line.svg
icons/white/svg/pause-line.svg
diff --git a/pcsx2/Frontend/SDLInputSource.cpp b/pcsx2/Frontend/SDLInputSource.cpp
index 0f687df728..b1889ae852 100644
--- a/pcsx2/Frontend/SDLInputSource.cpp
+++ b/pcsx2/Frontend/SDLInputSource.cpp
@@ -98,6 +98,18 @@ static constexpr const char* s_sdl_hat_direction_names[] = {
// clang-format on
};
+static constexpr const char* s_sdl_default_led_colors[] = {
+ "0000ff", // SDL-0
+ "ff0000", // SDL-1
+ "00ff00", // SDL-2
+ "ffff00", // SDL-3
+};
+
+static void SetControllerRGBLED(SDL_GameController* gc, u32 color)
+{
+ SDL_GameControllerSetLED(gc, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+}
+
SDLInputSource::SDLInputSource() = default;
SDLInputSource::~SDLInputSource()
@@ -159,6 +171,39 @@ void SDLInputSource::LoadSettings(SettingsInterface& si)
{
m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
m_sdl_hints = si.GetKeyValueList("SDLHints");
+
+ for (u32 i = 0; i < MAX_LED_COLORS; i++)
+ {
+ const u32 color = GetRGBForPlayerId(si, i);
+ if (m_led_colors[i] == color)
+ continue;
+
+ m_led_colors[i] = color;
+
+ const auto it = GetControllerDataForPlayerId(i);
+ if (it == m_controllers.end() || !it->game_controller || !SDL_GameControllerHasLED(it->game_controller))
+ continue;
+
+ SetControllerRGBLED(it->game_controller, color);
+ }
+}
+
+u32 SDLInputSource::GetRGBForPlayerId(SettingsInterface& si, u32 player_id)
+{
+ return ParseRGBForPlayerId(
+ si.GetStringValue("SDLExtra", fmt::format("Player{}LED", player_id).c_str(), s_sdl_default_led_colors[player_id]),
+ player_id);
+}
+
+u32 SDLInputSource::ParseRGBForPlayerId(const std::string_view& str, u32 player_id)
+{
+ if (player_id >= MAX_LED_COLORS)
+ return 0;
+
+ const u32 default_color = StringUtil::FromChars(s_sdl_default_led_colors[player_id], 16).value_or(0);
+ const u32 color = StringUtil::FromChars(str, 16).value_or(default_color);
+
+ return color;
}
void SDLInputSource::SetHints()
@@ -617,6 +662,11 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
if (!cd.haptic && !cd.use_game_controller_rumble)
Console.Warning("(SDLInputSource) Rumble is not supported on '%s'", name);
+ if (player_id >= 0 && static_cast(player_id) < MAX_LED_COLORS && gcontroller && SDL_GameControllerHasLED(gcontroller))
+ {
+ SetControllerRGBLED(gcontroller, m_led_colors[player_id]);
+ }
+
m_controllers.push_back(std::move(cd));
InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name);
@@ -668,7 +718,7 @@ bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, ev->button));
const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ?
s_sdl_generic_binding_button_mapping[ev->button] :
- GenericInputBinding::Unknown;
+ GenericInputBinding::Unknown;
InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key);
return true;
}
diff --git a/pcsx2/Frontend/SDLInputSource.h b/pcsx2/Frontend/SDLInputSource.h
index 02ef309292..e04ca70759 100644
--- a/pcsx2/Frontend/SDLInputSource.h
+++ b/pcsx2/Frontend/SDLInputSource.h
@@ -26,6 +26,8 @@ class SettingsInterface;
class SDLInputSource final : public InputSource
{
public:
+ static constexpr u32 MAX_LED_COLORS = 4;
+
SDLInputSource();
~SDLInputSource();
@@ -48,6 +50,9 @@ public:
SDL_Joystick* GetJoystickForDevice(const std::string_view& device);
+ static u32 GetRGBForPlayerId(SettingsInterface& si, u32 player_id);
+ static u32 ParseRGBForPlayerId(const std::string_view& str, u32 player_id);
+
private:
struct ControllerData
{
@@ -92,5 +97,6 @@ private:
bool m_sdl_subsystem_initialized = false;
bool m_controller_enhanced_mode = false;
+ std::array m_led_colors{};
std::vector> m_sdl_hints;
};