mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 22:06:59 +01:00
launcher: configure and preview DualSense lightbar color (#912)
launcher: configure and preview controller lightbar color Add an optional lightbar color to launcher settings and pass it to the emulator with --controller-color. Preview changes on RGB gamepads, handle controller hotplug events, and restore the saved color when settings dialogs close. Release launcher preview handles when starting a game. Co-authored-by: m66401970-eng <m66401970@gmail.com>
This commit is contained in:
@@ -47,6 +47,10 @@ const std::string& GetAudioInputDevice() {
|
|||||||
return g_config->audio_input_device;
|
return g_config->audio_input_device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::optional<ControllerColor>& GetControllerColor() {
|
||||||
|
return g_config->controller_color;
|
||||||
|
}
|
||||||
|
|
||||||
PresentMode GetPresentMode() {
|
PresentMode GetPresentMode() {
|
||||||
return g_config->present_mode;
|
return g_config->present_mode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ enum class LogDirection { Silent, Console, File };
|
|||||||
enum class PresentMode { Fifo, Mailbox, Immediate };
|
enum class PresentMode { Fifo, Mailbox, Immediate };
|
||||||
|
|
||||||
using Keymap = std::vector<std::string>;
|
using Keymap = std::vector<std::string>;
|
||||||
|
using ControllerColor = std::array<uint8_t, 3>;
|
||||||
|
|
||||||
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||||
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||||
@@ -44,6 +47,7 @@ struct ConfigOptions {
|
|||||||
std::string user_name = "Kyty";
|
std::string user_name = "Kyty";
|
||||||
int32_t user_id = DEFAULT_USER_ID;
|
int32_t user_id = DEFAULT_USER_ID;
|
||||||
std::string audio_input_device;
|
std::string audio_input_device;
|
||||||
|
std::optional<ControllerColor> controller_color;
|
||||||
PresentMode present_mode = PresentMode::Mailbox;
|
PresentMode present_mode = PresentMode::Mailbox;
|
||||||
int32_t gpu_index = -1;
|
int32_t gpu_index = -1;
|
||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
@@ -81,6 +85,7 @@ uint32_t GetScreenHeight();
|
|||||||
const std::string& GetUserName();
|
const std::string& GetUserName();
|
||||||
int32_t GetUserId();
|
int32_t GetUserId();
|
||||||
const std::string& GetAudioInputDevice();
|
const std::string& GetAudioInputDevice();
|
||||||
|
const std::optional<ControllerColor>& GetControllerColor();
|
||||||
PresentMode GetPresentMode();
|
PresentMode GetPresentMode();
|
||||||
int32_t GetGpuIndex();
|
int32_t GetGpuIndex();
|
||||||
bool FullscreenEnabled();
|
bool FullscreenEnabled();
|
||||||
|
|||||||
@@ -72,6 +72,27 @@
|
|||||||
<property name="toolTip"><string>Language</string></property>
|
<property name="toolTip"><string>Language</string></property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_controller_color">
|
||||||
|
<property name="text"><string>DualSense lightbar:</string></property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="controllerColorLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="button_controller_color">
|
||||||
|
<property name="toolTip"><string>Choose and preview a fixed LED color. Bluetooth DualSense may take several seconds to update.</string></property>
|
||||||
|
<property name="text"><string>Custom</string></property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="button_controller_color_reset">
|
||||||
|
<property name="toolTip"><string>Let the game control the lightbar</string></property>
|
||||||
|
<property name="text"><string>Default</string></property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QChar>
|
#include <QChar>
|
||||||
|
#include <QColor>
|
||||||
#include <QMetaEnum>
|
#include <QMetaEnum>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -88,6 +89,7 @@ public:
|
|||||||
QString user_name = "Kyty";
|
QString user_name = "Kyty";
|
||||||
int user_id = Config::DEFAULT_USER_ID;
|
int user_id = Config::DEFAULT_USER_ID;
|
||||||
QString audio_input_device;
|
QString audio_input_device;
|
||||||
|
QString controller_color;
|
||||||
PresentMode present_mode = PresentMode::Mailbox;
|
PresentMode present_mode = PresentMode::Mailbox;
|
||||||
int gpu_index = -1;
|
int gpu_index = -1;
|
||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
@@ -119,6 +121,7 @@ public:
|
|||||||
user_name = other.user_name;
|
user_name = other.user_name;
|
||||||
user_id = other.user_id;
|
user_id = other.user_id;
|
||||||
audio_input_device = other.audio_input_device;
|
audio_input_device = other.audio_input_device;
|
||||||
|
controller_color = other.controller_color;
|
||||||
present_mode = other.present_mode;
|
present_mode = other.present_mode;
|
||||||
gpu_index = other.gpu_index;
|
gpu_index = other.gpu_index;
|
||||||
fullscreen_enabled = other.fullscreen_enabled;
|
fullscreen_enabled = other.fullscreen_enabled;
|
||||||
@@ -165,6 +168,7 @@ public:
|
|||||||
KYTY_CFG_SET(user_name);
|
KYTY_CFG_SET(user_name);
|
||||||
KYTY_CFG_SET(user_id);
|
KYTY_CFG_SET(user_id);
|
||||||
KYTY_CFG_SET(audio_input_device);
|
KYTY_CFG_SET(audio_input_device);
|
||||||
|
KYTY_CFG_SET(controller_color);
|
||||||
KYTY_CFG_SET(present_mode);
|
KYTY_CFG_SET(present_mode);
|
||||||
KYTY_CFG_SET(gpu_index);
|
KYTY_CFG_SET(gpu_index);
|
||||||
KYTY_CFG_SET(fullscreen_enabled);
|
KYTY_CFG_SET(fullscreen_enabled);
|
||||||
@@ -204,6 +208,8 @@ public:
|
|||||||
? saved_user_id
|
? saved_user_id
|
||||||
: Config::DEFAULT_USER_ID;
|
: Config::DEFAULT_USER_ID;
|
||||||
audio_input_device = s->value("audio_input_device", audio_input_device).toString();
|
audio_input_device = s->value("audio_input_device", audio_input_device).toString();
|
||||||
|
const QColor color(s->value("controller_color", controller_color).toString());
|
||||||
|
controller_color = color.isValid() ? color.name(QColor::HexRgb) : QString {};
|
||||||
KYTY_CFG_GET(present_mode);
|
KYTY_CFG_GET(present_mode);
|
||||||
gpu_index = s->value("gpu_index", -1).toInt();
|
gpu_index = s->value("gpu_index", -1).toInt();
|
||||||
if (EnumToText(present_mode).isEmpty()) {
|
if (EnumToText(present_mode).isEmpty()) {
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ public:
|
|||||||
void SetGameDirectories(const QStringList& dirs);
|
void SetGameDirectories(const QStringList& dirs);
|
||||||
[[nodiscard]] QStringList GetGameDirectories() const;
|
[[nodiscard]] QStringList GetGameDirectories() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void PreviewControllerColor(const QString& color);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::ConfigurationEditDialog* m_ui = nullptr;
|
Ui::ConfigurationEditDialog* m_ui = nullptr;
|
||||||
Configuration& m_info;
|
Configuration& m_info;
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ public:
|
|||||||
ConfigurationItem* GetSelectedItem() { return m_selected_item; }
|
ConfigurationItem* GetSelectedItem() { return m_selected_item; }
|
||||||
|
|
||||||
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
|
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
|
||||||
|
[[nodiscard]] const QString& GetGlobalControllerColor() const {
|
||||||
|
return m_global_info.controller_color;
|
||||||
|
}
|
||||||
[[nodiscard]] std::unique_ptr<Configuration>
|
[[nodiscard]] std::unique_ptr<Configuration>
|
||||||
CreateConfiguration(const ConfigurationItem& item) const;
|
CreateConfiguration(const ConfigurationItem& item) const;
|
||||||
|
|
||||||
@@ -44,6 +47,7 @@ signals:
|
|||||||
|
|
||||||
void Run();
|
void Run();
|
||||||
void Select();
|
void Select();
|
||||||
|
void PreviewControllerColor(const QString& color);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef CONTROLLER_LIGHTBAR_H
|
||||||
|
#define CONTROLLER_LIGHTBAR_H
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <SDL3/SDL_gamepad.h>
|
||||||
|
|
||||||
|
class ControllerLightbar final: public QObject {
|
||||||
|
public:
|
||||||
|
explicit ControllerLightbar(QObject* parent = nullptr);
|
||||||
|
~ControllerLightbar() override;
|
||||||
|
|
||||||
|
void SetColor(const QString& color);
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Refresh();
|
||||||
|
void ApplyColor(SDL_Gamepad* gamepad) const;
|
||||||
|
|
||||||
|
QTimer m_timer;
|
||||||
|
QHash<SDL_JoystickID, SDL_Gamepad*> m_gamepads;
|
||||||
|
QColor m_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONTROLLER_LIGHTBAR_H
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <QAbstractItemView>
|
#include <QAbstractItemView>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QColorDialog>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@@ -40,6 +41,22 @@ constexpr char SETTINGS_CFG_DIALOG[] = "ConfigurationEditDialog";
|
|||||||
constexpr char SETTINGS_CFG_LAST_GEOMETRY[] = "geometry";
|
constexpr char SETTINGS_CFG_LAST_GEOMETRY[] = "geometry";
|
||||||
constexpr int GLOBAL_SETTINGS_GAME_DIRS_MIN_WIDTH = 560;
|
constexpr int GLOBAL_SETTINGS_GAME_DIRS_MIN_WIDTH = 560;
|
||||||
|
|
||||||
|
static void UpdateControllerColorButton(QPushButton* button, const QString& hex) {
|
||||||
|
const QColor color(hex);
|
||||||
|
if (!color.isValid()) {
|
||||||
|
button->setProperty("controllerColor", QString {});
|
||||||
|
button->setText(QObject::tr("Custom"));
|
||||||
|
button->setStyleSheet({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto normalized = color.name(QColor::HexRgb);
|
||||||
|
button->setProperty("controllerColor", normalized);
|
||||||
|
button->setText(normalized.toUpper());
|
||||||
|
button->setStyleSheet(QStringLiteral("background-color: %1; color: %2;")
|
||||||
|
.arg(normalized, color.lightness() < 128 ? QStringLiteral("white")
|
||||||
|
: QStringLiteral("black")));
|
||||||
|
}
|
||||||
|
|
||||||
const QStringList CONSOLE_LANGUAGE_NAMES = {
|
const QStringList CONSOLE_LANGUAGE_NAMES = {
|
||||||
"Japanese",
|
"Japanese",
|
||||||
"English (United States)",
|
"English (United States)",
|
||||||
@@ -110,6 +127,21 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
|||||||
connect(m_ui->ok_button, &QPushButton::clicked, this, &ConfigurationEditDialog::save);
|
connect(m_ui->ok_button, &QPushButton::clicked, this, &ConfigurationEditDialog::save);
|
||||||
connect(m_ui->cancel_button, &QPushButton::clicked, this, &QDialog::reject);
|
connect(m_ui->cancel_button, &QPushButton::clicked, this, &QDialog::reject);
|
||||||
connect(m_ui->clear_button, &QPushButton::clicked, this, &ConfigurationEditDialog::clear);
|
connect(m_ui->clear_button, &QPushButton::clicked, this, &ConfigurationEditDialog::clear);
|
||||||
|
connect(m_ui->button_controller_color, &QPushButton::clicked, this, [this]() {
|
||||||
|
const auto current = m_ui->button_controller_color->property("controllerColor").toString();
|
||||||
|
const auto initial =
|
||||||
|
current.isEmpty() ? QColor(QStringLiteral("#0070d1")) : QColor(current);
|
||||||
|
const auto color = QColorDialog::getColor(initial, this, tr("DualSense lightbar color"));
|
||||||
|
if (color.isValid()) {
|
||||||
|
const auto hex = color.name(QColor::HexRgb);
|
||||||
|
UpdateControllerColorButton(m_ui->button_controller_color, hex);
|
||||||
|
emit PreviewControllerColor(hex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_ui->button_controller_color_reset, &QPushButton::clicked, this, [this]() {
|
||||||
|
UpdateControllerColorButton(m_ui->button_controller_color, {});
|
||||||
|
emit PreviewControllerColor({});
|
||||||
|
});
|
||||||
connect(m_ui->comboBox_shader_log_direction, &QComboBox::currentTextChanged, this,
|
connect(m_ui->comboBox_shader_log_direction, &QComboBox::currentTextChanged, this,
|
||||||
[this](const QString& text) {
|
[this](const QString& text) {
|
||||||
auto log = TextToEnum<Configuration::LogDirection>(text);
|
auto log = TextToEnum<Configuration::LogDirection>(text);
|
||||||
@@ -169,6 +201,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
|||||||
m_ui->lineEdit_user_name->setMaxLength(static_cast<int>(Config::MAX_USER_NAME_LENGTH));
|
m_ui->lineEdit_user_name->setMaxLength(static_cast<int>(Config::MAX_USER_NAME_LENGTH));
|
||||||
m_ui->lineEdit_user_name->setText(info.user_name);
|
m_ui->lineEdit_user_name->setText(info.user_name);
|
||||||
m_ui->spinBox_user_id->setValue(info.user_id);
|
m_ui->spinBox_user_id->setValue(info.user_id);
|
||||||
|
UpdateControllerColorButton(m_ui->button_controller_color, info.controller_color);
|
||||||
auto* microphone = m_ui->comboBox_audio_input_device;
|
auto* microphone = m_ui->comboBox_audio_input_device;
|
||||||
microphone->clear();
|
microphone->clear();
|
||||||
microphone->addItem(tr("None"), QString {});
|
microphone->addItem(tr("None"), QString {});
|
||||||
@@ -370,6 +403,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
|||||||
info.user_name = ui.lineEdit_user_name->text().trimmed();
|
info.user_name = ui.lineEdit_user_name->text().trimmed();
|
||||||
info.user_id = ui.spinBox_user_id->value();
|
info.user_id = ui.spinBox_user_id->value();
|
||||||
info.audio_input_device = ui.comboBox_audio_input_device->currentData().toString();
|
info.audio_input_device = ui.comboBox_audio_input_device->currentData().toString();
|
||||||
|
info.controller_color = ui.button_controller_color->property("controllerColor").toString();
|
||||||
info.screen_resolution =
|
info.screen_resolution =
|
||||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
||||||
info.present_mode =
|
info.present_mode =
|
||||||
@@ -427,6 +461,7 @@ void ConfigurationEditDialog::save() {
|
|||||||
void ConfigurationEditDialog::clear() {
|
void ConfigurationEditDialog::clear() {
|
||||||
Configuration default_info;
|
Configuration default_info;
|
||||||
Init(default_info);
|
Init(default_info);
|
||||||
|
emit PreviewControllerColor({});
|
||||||
|
|
||||||
if (m_show_game_dirs) {
|
if (m_show_game_dirs) {
|
||||||
m_game_dirs_list->clear();
|
m_game_dirs_list->clear();
|
||||||
|
|||||||
@@ -751,6 +751,8 @@ void ConfigurationListWidget::edit_configuration() {
|
|||||||
auto info = CreateConfiguration(*item);
|
auto info = CreateConfiguration(*item);
|
||||||
ConfigurationEditDialog dlg(*info, this);
|
ConfigurationEditDialog dlg(*info, this);
|
||||||
dlg.setWindowTitle(tr("Edit game settings"));
|
dlg.setWindowTitle(tr("Edit game settings"));
|
||||||
|
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
|
||||||
|
&ConfigurationListWidget::PreviewControllerColor);
|
||||||
|
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
if (dlg.exec() == QDialog::Accepted) {
|
||||||
info->custom_settings = true;
|
info->custom_settings = true;
|
||||||
@@ -761,6 +763,8 @@ void ConfigurationListWidget::edit_configuration() {
|
|||||||
WriteSettings();
|
WriteSettings();
|
||||||
item->Update();
|
item->Update();
|
||||||
SelectItem(item);
|
SelectItem(item);
|
||||||
|
} else {
|
||||||
|
emit Select();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -788,6 +792,8 @@ void ConfigurationListWidget::edit_global_settings() {
|
|||||||
ConfigurationEditDialog dlg(info, this);
|
ConfigurationEditDialog dlg(info, this);
|
||||||
dlg.setWindowTitle(tr("Global settings"));
|
dlg.setWindowTitle(tr("Global settings"));
|
||||||
dlg.SetGameDirectories(m_game_dirs);
|
dlg.SetGameDirectories(m_game_dirs);
|
||||||
|
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
|
||||||
|
&ConfigurationListWidget::PreviewControllerColor);
|
||||||
|
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
if (dlg.exec() == QDialog::Accepted) {
|
||||||
m_global_info.CopyEmulatorSettingsFrom(info);
|
m_global_info.CopyEmulatorSettingsFrom(info);
|
||||||
@@ -799,6 +805,7 @@ void ConfigurationListWidget::edit_global_settings() {
|
|||||||
ScanGameDirectory();
|
ScanGameDirectory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emit Select();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigurationListWidget::edit_input_mapping() {
|
void ConfigurationListWidget::edit_input_mapping() {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#include "controllerLightbar.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
ControllerLightbar::ControllerLightbar(QObject* parent): QObject(parent) {
|
||||||
|
m_timer.setInterval(100);
|
||||||
|
connect(&m_timer, &QTimer::timeout, this, &ControllerLightbar::Refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerLightbar::~ControllerLightbar() {
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerLightbar::SetColor(const QString& color) {
|
||||||
|
const QColor parsed(color);
|
||||||
|
if (!parsed.isValid()) {
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_color == parsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_timer.isActive()) {
|
||||||
|
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
|
||||||
|
qWarning() << "Cannot initialize controller color preview:" << SDL_GetError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_timer.start();
|
||||||
|
}
|
||||||
|
m_color = parsed;
|
||||||
|
for (auto* gamepad: m_gamepads) {
|
||||||
|
ApplyColor(gamepad);
|
||||||
|
}
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerLightbar::Stop() {
|
||||||
|
if (!m_timer.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_timer.stop();
|
||||||
|
for (auto* gamepad: m_gamepads) {
|
||||||
|
SDL_CloseGamepad(gamepad);
|
||||||
|
}
|
||||||
|
m_gamepads.clear();
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
|
||||||
|
m_color = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerLightbar::Refresh() {
|
||||||
|
// Bluetooth DualSense may defer an accepted LED command until its connection
|
||||||
|
// animation ends. Polling events also delivers those deferred commands.
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_EVENT_GAMEPAD_ADDED && !m_gamepads.contains(event.gdevice.which)) {
|
||||||
|
auto* gamepad = SDL_OpenGamepad(event.gdevice.which);
|
||||||
|
if (gamepad == nullptr) {
|
||||||
|
qWarning() << "Cannot open controller color preview:" << SDL_GetError();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SDL_GetBooleanProperty(SDL_GetGamepadProperties(gamepad),
|
||||||
|
SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN, false)) {
|
||||||
|
m_gamepads.insert(event.gdevice.which, gamepad);
|
||||||
|
ApplyColor(gamepad);
|
||||||
|
} else {
|
||||||
|
SDL_CloseGamepad(gamepad);
|
||||||
|
}
|
||||||
|
} else if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||||
|
if (auto* gamepad = m_gamepads.take(event.gdevice.which); gamepad != nullptr) {
|
||||||
|
SDL_CloseGamepad(gamepad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerLightbar::ApplyColor(SDL_Gamepad* gamepad) const {
|
||||||
|
if (!SDL_SetGamepadLED(gamepad, static_cast<Uint8>(m_color.red()),
|
||||||
|
static_cast<Uint8>(m_color.green()),
|
||||||
|
static_cast<Uint8>(m_color.blue()))) {
|
||||||
|
qWarning() << "Cannot set controller LED:" << SDL_GetError();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "configurationItem.h"
|
#include "configurationItem.h"
|
||||||
#include "configurationListWidget.h"
|
#include "configurationListWidget.h"
|
||||||
|
#include "controllerLightbar.h"
|
||||||
#include "gameContent.h"
|
#include "gameContent.h"
|
||||||
#include "patchesDialog.h"
|
#include "patchesDialog.h"
|
||||||
#include "updateChecker.h"
|
#include "updateChecker.h"
|
||||||
@@ -88,6 +89,7 @@ private:
|
|||||||
Ui::MainDialog* m_ui = {nullptr};
|
Ui::MainDialog* m_ui = {nullptr};
|
||||||
MainDialog* m_main_dialog = nullptr;
|
MainDialog* m_main_dialog = nullptr;
|
||||||
UpdateChecker* m_update_checker = nullptr;
|
UpdateChecker* m_update_checker = nullptr;
|
||||||
|
ControllerLightbar m_lightbar;
|
||||||
QString m_interpreter;
|
QString m_interpreter;
|
||||||
|
|
||||||
QProcess m_process;
|
QProcess m_process;
|
||||||
@@ -103,6 +105,7 @@ MainDialog::MainDialog(QWidget* parent): QDialog(parent), m_p(new MainDialogPriv
|
|||||||
}
|
}
|
||||||
|
|
||||||
MainDialogPrivate::~MainDialogPrivate() {
|
MainDialogPrivate::~MainDialogPrivate() {
|
||||||
|
m_process.disconnect(this);
|
||||||
delete m_ui;
|
delete m_ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +124,12 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
|||||||
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
|
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
|
||||||
|
connect(m_ui->widget, &ConfigurationListWidget::PreviewControllerColor, this,
|
||||||
|
[this](const QString& color) {
|
||||||
|
if (m_process.state() == QProcess::NotRunning) {
|
||||||
|
m_lightbar.SetColor(color);
|
||||||
|
}
|
||||||
|
});
|
||||||
connect(m_ui->widget, &ConfigurationListWidget::Run, this, &MainDialogPrivate::Run);
|
connect(m_ui->widget, &ConfigurationListWidget::Run, this, &MainDialogPrivate::Run);
|
||||||
connect(m_ui->check_updates_link, &QLabel::linkActivated, this,
|
connect(m_ui->check_updates_link, &QLabel::linkActivated, this,
|
||||||
[this](const QString&) { m_update_checker->Check(true); });
|
[this](const QString&) { m_update_checker->Check(true); });
|
||||||
@@ -135,13 +144,13 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
|||||||
m_ui->widget->WriteSettings();
|
m_ui->widget->WriteSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&m_process,
|
connect(&m_process, &QProcess::stateChanged, this, [this](QProcess::ProcessState state) {
|
||||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
if (state == QProcess::NotRunning) {
|
||||||
[this](int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) {
|
|
||||||
if (m_running_item != nullptr) {
|
if (m_running_item != nullptr) {
|
||||||
m_running_item->SetRunning(false);
|
m_running_item->SetRunning(false);
|
||||||
}
|
}
|
||||||
Update();
|
Update();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
m_ui->label_settings_file->setText(tr("Settings file: ") + m_ui->widget->GetSettingsFile());
|
m_ui->label_settings_file->setText(tr("Settings file: ") + m_ui->widget->GetSettingsFile());
|
||||||
@@ -219,6 +228,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
|||||||
if (!info.audio_input_device.isEmpty()) {
|
if (!info.audio_input_device.isEmpty()) {
|
||||||
args << "--mic" << info.audio_input_device;
|
args << "--mic" << info.audio_input_device;
|
||||||
}
|
}
|
||||||
|
if (!info.controller_color.isEmpty()) {
|
||||||
|
args << "--controller-color" << info.controller_color;
|
||||||
|
}
|
||||||
args << "--present-mode" << EnumToText(info.present_mode);
|
args << "--present-mode" << EnumToText(info.present_mode);
|
||||||
if (info.gpu_index >= 0) {
|
if (info.gpu_index >= 0) {
|
||||||
args << "--gpu" << QString::number(info.gpu_index);
|
args << "--gpu" << QString::number(info.gpu_index);
|
||||||
@@ -493,6 +505,7 @@ void MainDialogPrivate::Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_running_item->SetRunning(true);
|
m_running_item->SetRunning(true);
|
||||||
|
m_lightbar.Stop();
|
||||||
|
|
||||||
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
|
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
|
||||||
m_main_dialog->RunInterpreter(&m_process, *info);
|
m_main_dialog->RunInterpreter(&m_process, *info);
|
||||||
@@ -512,6 +525,12 @@ void MainDialogPrivate::Update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_ui->widget->SetRunEnabled(run_enabled);
|
m_ui->widget->SetRunEnabled(run_enabled);
|
||||||
|
if (m_process.state() != QProcess::NotRunning) {
|
||||||
|
m_lightbar.Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_lightbar.SetColor(item != nullptr ? m_ui->widget->CreateConfiguration(*item)->controller_color
|
||||||
|
: m_ui->widget->GetGlobalControllerColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "mainDialog.moc"
|
#include "mainDialog.moc"
|
||||||
|
|||||||
@@ -297,6 +297,9 @@ void GameController::Connect(int id) {
|
|||||||
if (id != HOST_INPUT_CONTROLLER_ID) {
|
if (id != HOST_INPUT_CONTROLLER_ID) {
|
||||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
||||||
pad != nullptr) {
|
pad != nullptr) {
|
||||||
|
if (const auto& color = Config::GetControllerColor()) {
|
||||||
|
(void)SDL_SetGamepadLED(pad, (*color)[0], (*color)[1], (*color)[2]);
|
||||||
|
}
|
||||||
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
|
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
|
||||||
if (SDL_GamepadHasSensor(pad, sensor) &&
|
if (SDL_GamepadHasSensor(pad, sensor) &&
|
||||||
!SDL_SetGamepadSensorEnabled(pad, sensor, true)) {
|
!SDL_SetGamepadSensorEnabled(pad, sensor, true)) {
|
||||||
@@ -581,6 +584,11 @@ int GameController::GetActiveControllerId() {
|
|||||||
|
|
||||||
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
|
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
Common::LockGuard lock(m_mutex);
|
Common::LockGuard lock(m_mutex);
|
||||||
|
if (const auto& color = Config::GetControllerColor()) {
|
||||||
|
r = (*color)[0];
|
||||||
|
g = (*color)[1];
|
||||||
|
b = (*color)[2];
|
||||||
|
}
|
||||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||||
pad != nullptr) {
|
pad != nullptr) {
|
||||||
(void)SDL_SetGamepadLED(pad, r, g, b);
|
(void)SDL_SetGamepadLED(pad, r, g, b);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ static void PrintUsage() {
|
|||||||
::printf(" --user-id <num> Local user ID. Default: %d.\n",
|
::printf(" --user-id <num> Local user ID. Default: %d.\n",
|
||||||
Config::DEFAULT_USER_ID);
|
Config::DEFAULT_USER_ID);
|
||||||
::printf(" --mic <name> Capture from this microphone; omit for silence.\n");
|
::printf(" --mic <name> Capture from this microphone; omit for silence.\n");
|
||||||
|
::printf(" --controller-color <#RRGGBB> Override the controller lightbar color.\n");
|
||||||
::printf(
|
::printf(
|
||||||
" --present-mode <value> Fifo, Mailbox, or Immediate. Default: Mailbox.\n");
|
" --present-mode <value> Fifo, Mailbox, or Immediate. Default: Mailbox.\n");
|
||||||
::printf(
|
::printf(
|
||||||
@@ -145,6 +146,20 @@ static bool ParseUint32(const std::string& value, uint32_t& out) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool ParseControllerColor(const std::string& value, Config::ControllerColor& out) {
|
||||||
|
if (value.size() != 7 || value[0] != '#') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint32_t rgb = 0;
|
||||||
|
auto [end, error] = std::from_chars(value.data() + 1, value.data() + value.size(), rgb, 16);
|
||||||
|
if (error != std::errc {} || end != value.data() + value.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = {static_cast<uint8_t>(rgb >> 16), static_cast<uint8_t>(rgb >> 8),
|
||||||
|
static_cast<uint8_t>(rgb)};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool ParseInt32(const std::string& value, int32_t& out) {
|
static bool ParseInt32(const std::string& value, int32_t& out) {
|
||||||
int32_t number = 0;
|
int32_t number = 0;
|
||||||
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), number);
|
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), number);
|
||||||
@@ -302,6 +317,13 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
|||||||
}
|
}
|
||||||
} else if (arg == "--mic") {
|
} else if (arg == "--mic") {
|
||||||
options.config.audio_input_device = value;
|
options.config.audio_input_device = value;
|
||||||
|
} else if (arg == "--controller-color") {
|
||||||
|
Config::ControllerColor color {};
|
||||||
|
if (!ParseControllerColor(value, color)) {
|
||||||
|
::printf("invalid controller color (expected #RRGGBB): %s\n", value.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
options.config.controller_color = color;
|
||||||
} else if (arg == "--present-mode") {
|
} else if (arg == "--present-mode") {
|
||||||
if (!ParseEnum(value, options.config.present_mode)) {
|
if (!ParseEnum(value, options.config.present_mode)) {
|
||||||
::printf("invalid present mode: %s\n", value.c_str());
|
::printf("invalid present mode: %s\n", value.c_str());
|
||||||
|
|||||||
Reference in New Issue
Block a user