From ffd9d40d657eb3a16ffa8de84c841fbe73b21275 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 28 Jun 2026 20:07:14 +1000 Subject: [PATCH] Qt: Add Register button for achievements And fix a potential deadlock. --- src/core/settings.cpp | 6 +-- src/core/settings.h | 2 +- .../achievementsettingswidget.cpp | 43 ++++++++++++++----- .../achievementsettingswidget.h | 4 +- .../achievementsettingswidget.ui | 26 ++++++++++- .../resources/duckstation-qt.qrc | 2 + .../icons/monochrome/svg/logout-box-line.svg | 1 + .../icons/monochrome/svg/user-add-line.svg | 1 + src/duckstation-qt/settingswindow.cpp | 20 ++++++--- src/duckstation-qt/settingswindow.h | 6 +-- 10 files changed, 85 insertions(+), 26 deletions(-) create mode 100644 src/duckstation-qt/resources/icons/monochrome/svg/logout-box-line.svg create mode 100644 src/duckstation-qt/resources/icons/monochrome/svg/user-add-line.svg diff --git a/src/core/settings.cpp b/src/core/settings.cpp index eac6deedf..fae4bf08c 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -2774,7 +2774,7 @@ const char* Settings::GetPIODeviceTypeModeDisplayName(PIODeviceType type) namespace EmuFolders { static void EnsureFolderExists(const std::string& path); -static std::string LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* section, +static std::string LoadPathFromSettings(const SettingsInterface& si, const std::string& root, const char* section, const char* name, const char* def); std::string AppRoot; @@ -2844,7 +2844,7 @@ void EmuFolders::SetDefaults() Videos = Path::Combine(DataRoot, "videos"); } -std::string EmuFolders::LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* section, +std::string EmuFolders::LoadPathFromSettings(const SettingsInterface& si, const std::string& root, const char* section, const char* name, const char* def) { std::string value = si.GetStringValue(section, name, def); @@ -2856,7 +2856,7 @@ std::string EmuFolders::LoadPathFromSettings(SettingsInterface& si, const std::s return value; } -void EmuFolders::LoadConfig(SettingsInterface& si) +void EmuFolders::LoadConfig(const SettingsInterface& si) { Bios = LoadPathFromSettings(si, DataRoot, "BIOS", "SearchDirectory", "bios"); Cache = LoadPathFromSettings(si, DataRoot, "Folders", "Cache", "cache"); diff --git a/src/core/settings.h b/src/core/settings.h index 42c308daa..b40ce1c6f 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -687,7 +687,7 @@ extern std::string Videos; // Assumes that AppRoot and DataRoot have been initialized. void SetDefaults(); void EnsureFoldersExist(); -void LoadConfig(SettingsInterface& si); +void LoadConfig(const SettingsInterface& si); void Save(SettingsInterface& si); // Returns the default path for the given settings key. diff --git a/src/duckstation-qt/achievementsettingswidget.cpp b/src/duckstation-qt/achievementsettingswidget.cpp index 9d50953e4..12d93113d 100644 --- a/src/duckstation-qt/achievementsettingswidget.cpp +++ b/src/duckstation-qt/achievementsettingswidget.cpp @@ -136,7 +136,9 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi if (!m_dialog->isPerGameSettings()) { - connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed); + connect(m_ui.login, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginPressed); + connect(m_ui.logout, &QPushButton::clicked, this, &AchievementSettingsWidget::onLogoutPressed); + connect(m_ui.registerUser, &QPushButton::clicked, this, &AchievementSettingsWidget::onRegisterUserPressed); connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed); connect(g_core_thread, &CoreThread::achievementsLoginSuccess, this, &AchievementSettingsWidget::updateLoginState); updateLoginState(); @@ -317,31 +319,45 @@ void AchievementSettingsWidget::updateLoginState() const QString login_timestamp = QtHost::FormatNumber(Host::NumberFormatType::ShortDateTime, static_cast(login_unix_timestamp)); m_ui.loginStatus->setText(tr("Logged in as %1\nToken generated at %2").arg(qusername).arg(login_timestamp)); - m_ui.loginButton->setText(tr("Logout")); } else { m_ui.loginStatus->setText(tr("Not Logged In.")); - m_ui.loginButton->setText(tr("Login...")); } + m_ui.viewProfile->setVisible(logged_in); m_ui.viewProfile->setEnabled(logged_in); + m_ui.logout->setVisible(logged_in); + m_ui.logout->setEnabled(logged_in); + m_ui.registerUser->setVisible(!logged_in); + m_ui.registerUser->setEnabled(!logged_in); + m_ui.login->setVisible(!logged_in); + m_ui.login->setEnabled(!logged_in); } -void AchievementSettingsWidget::onLoginLogoutPressed() +void AchievementSettingsWidget::onLoginPressed() { - if (!Core::GetBaseStringSettingValue("Cheevos", "Username").empty()) - { - Host::RunOnCoreThread([]() { Achievements::Logout(); }, true); - updateLoginState(); - return; - } - AchievementLoginDialog* login = new AchievementLoginDialog(this, Achievements::LoginRequestReason::UserInitiated); connect(login, &AchievementLoginDialog::accepted, this, &AchievementSettingsWidget::onLoginCompleted); login->open(); } +void AchievementSettingsWidget::onLogoutPressed() +{ + if (Core::GetBaseStringSettingValue("Cheevos", "Username").empty()) + return; + + Host::RunOnCoreThread([]() { + Achievements::Logout(); + Host::RunOnUIThread([]() { + SettingsWindow* settings = g_main_window ? g_main_window->getSettingsWindow() : nullptr; + AchievementSettingsWidget* achievement_settings = settings ? settings->getAchievementSettingsWidget() : nullptr; + if (achievement_settings) + achievement_settings->updateLoginState(); + }); + }); +} + void AchievementSettingsWidget::onLoginCompleted() { updateLoginState(); @@ -356,6 +372,11 @@ void AchievementSettingsWidget::onLoginCompleted() m_ui.hardcoreMode->setChecked(true); } +void AchievementSettingsWidget::onRegisterUserPressed() +{ + QtUtils::OpenURL(this, QUrl(QStringLiteral("https://retroachievements.org/createaccount.php"))); +} + void AchievementSettingsWidget::onViewProfilePressed() { const std::string username(Core::GetBaseStringSettingValue("Cheevos", "Username")); diff --git a/src/duckstation-qt/achievementsettingswidget.h b/src/duckstation-qt/achievementsettingswidget.h index 4d2bd2996..2a2f3f22f 100644 --- a/src/duckstation-qt/achievementsettingswidget.h +++ b/src/duckstation-qt/achievementsettingswidget.h @@ -26,8 +26,10 @@ private: void onHardcoreModeStateChanged(); void onAchievementsNotificationDurationSliderChanged(); void onLeaderboardsNotificationDurationSliderChanged(); - void onLoginLogoutPressed(); + void onLoginPressed(); + void onLogoutPressed(); void onLoginCompleted(); + void onRegisterUserPressed(); void onViewProfilePressed(); Ui::AchievementSettingsWidget m_ui; diff --git a/src/duckstation-qt/achievementsettingswidget.ui b/src/duckstation-qt/achievementsettingswidget.ui index cdb53d20a..c60df9fa2 100644 --- a/src/duckstation-qt/achievementsettingswidget.ui +++ b/src/duckstation-qt/achievementsettingswidget.ui @@ -25,7 +25,7 @@ - + 10 @@ -61,6 +61,17 @@ + + + + Register + + + + :/icons/monochrome/svg/user-add-line.svg:/icons/monochrome/svg/user-add-line.svg + + + @@ -73,7 +84,7 @@ - + Login... @@ -83,6 +94,17 @@ + + + + Logout + + + + :/icons/monochrome/svg/logout-box-line.svg:/icons/monochrome/svg/logout-box-line.svg + + + diff --git a/src/duckstation-qt/resources/duckstation-qt.qrc b/src/duckstation-qt/resources/duckstation-qt.qrc index 988351768..837513ff7 100644 --- a/src/duckstation-qt/resources/duckstation-qt.qrc +++ b/src/duckstation-qt/resources/duckstation-qt.qrc @@ -132,6 +132,7 @@ icons/monochrome/svg/lightbulb-line.svg icons/monochrome/svg/list-check.svg icons/monochrome/svg/login-box-line.svg + icons/monochrome/svg/logout-box-line.svg icons/monochrome/svg/mag-line.svg icons/monochrome/svg/memcard-line.svg icons/monochrome/svg/memcards-line.svg @@ -167,6 +168,7 @@ icons/monochrome/svg/trash-fill.svg icons/monochrome/svg/trophy-line.svg icons/monochrome/svg/tv-2-line.svg + icons/monochrome/svg/user-add-line.svg icons/monochrome/svg/video-on-line.svg icons/monochrome/svg/vidicon-line.svg icons/monochrome/svg/volume-up-line.svg diff --git a/src/duckstation-qt/resources/icons/monochrome/svg/logout-box-line.svg b/src/duckstation-qt/resources/icons/monochrome/svg/logout-box-line.svg new file mode 100644 index 000000000..210475017 --- /dev/null +++ b/src/duckstation-qt/resources/icons/monochrome/svg/logout-box-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/duckstation-qt/resources/icons/monochrome/svg/user-add-line.svg b/src/duckstation-qt/resources/icons/monochrome/svg/user-add-line.svg new file mode 100644 index 000000000..3b65dd63c --- /dev/null +++ b/src/duckstation-qt/resources/icons/monochrome/svg/user-add-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp index bd2ededfb..b6f359691 100644 --- a/src/duckstation-qt/settingswindow.cpp +++ b/src/duckstation-qt/settingswindow.cpp @@ -100,7 +100,7 @@ void SettingsWindow::addPages() if (!isPerGameSettings()) { addWidget( - m_game_list_settings = new GameListSettingsWidget(this, m_ui.settingsContainer), tr("Game List"), + new GameListSettingsWidget(this, m_ui.settingsContainer), tr("Game List"), u":/icons/monochrome/svg/folder-open-line.svg"_s, tr("Game List Settings
The list above shows the directories which will be searched by " "DuckStation to populate the game list. Search directories can be added, removed, and switched to " @@ -138,12 +138,11 @@ void SettingsWindow::addPages() } addWidget( - (m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer)), tr("Memory Cards"), + new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"), u":/icons/monochrome/svg/memcard-line.svg"_s, tr("Memory Card Settings
This page lets you control what mode the memory card emulation will " "function in, and where the images for these cards will be stored on disk.")); - GraphicsSettingsWidget* graphics_settings; - addWidget(graphics_settings = new GraphicsSettingsWidget(this, m_ui.settingsContainer), tr("Graphics"), + addWidget(new GraphicsSettingsWidget(this, m_ui.settingsContainer), tr("Graphics"), u":/icons/monochrome/svg/image-fill.svg"_s, tr("Graphics Settings
These options control how the graphics of the emulated console " "are rendered. Not all options are available for the software renderer. Mouse over each option for " @@ -387,6 +386,16 @@ void SettingsWindow::onClearSettingsClicked() tr("Per-game configuration cleared.")); } +GameListSettingsWidget* SettingsWindow::getGameListSettingsWidget() const +{ + return findChild(); +} + +AchievementSettingsWidget* SettingsWindow::getAchievementSettingsWidget() const +{ + return findChild(); +} + void SettingsWindow::registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text) { if (!object) @@ -736,7 +745,8 @@ MultitapMode SettingsWindow::getEffectiveMultitapMode() const void SettingsWindow::onMultitapModeChanged(MultitapMode mode) { - m_memory_card_settings->createPortSettings(mode); + if (MemoryCardSettingsWidget* memcard_settings = findChild()) + memcard_settings->createPortSettings(mode); } SettingsWindow* SettingsWindow::openGamePropertiesDialog(const GameList::Entry* entry, diff --git a/src/duckstation-qt/settingswindow.h b/src/duckstation-qt/settingswindow.h index 5c24ce5ef..eed5182af 100644 --- a/src/duckstation-qt/settingswindow.h +++ b/src/duckstation-qt/settingswindow.h @@ -31,6 +31,7 @@ struct Entry; class GameSummaryWidget; class GameListSettingsWidget; class MemoryCardSettingsWidget; +class AchievementSettingsWidget; class SettingsWindow final : public QWidget { @@ -55,7 +56,8 @@ public: ALWAYS_INLINE const GameDatabase::Entry* getDatabaseEntry() const { return m_database_entry; } ALWAYS_INLINE bool hasDatabaseEntry() const { return (m_database_entry != nullptr); } - ALWAYS_INLINE GameListSettingsWidget* getGameListSettingsWidget() const { return m_game_list_settings; } + GameListSettingsWidget* getGameListSettingsWidget() const; + AchievementSettingsWidget* getAchievementSettingsWidget() const; void registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text); bool eventFilter(QObject* object, QEvent* event) override; @@ -126,8 +128,6 @@ private: const GameDatabase::Entry* m_database_entry = nullptr; GameSummaryWidget* m_game_summary = nullptr; - GameListSettingsWidget* m_game_list_settings = nullptr; - MemoryCardSettingsWidget* m_memory_card_settings = nullptr; std::array m_category_help_text;