mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Qt: Add RetroAchievements implementation
This commit is contained in:
committed by
refractionpcsx2
parent
843b0b3eb1
commit
0419de4baf
@@ -0,0 +1,2 @@
|
||||
lbsubmit.wav: https://freesound.org/people/Eponn/sounds/636656/
|
||||
unlock.wav and message.wav are from https://github.com/RetroAchievements/RAInterface
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "CocoaTools.h"
|
||||
#include "Console.h"
|
||||
#include "General.h"
|
||||
#include "WindowInfo.h"
|
||||
#include <vector>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
@@ -125,3 +126,12 @@ void CocoaTools::RemoveThemeChangeHandler(void* ctx)
|
||||
assert([NSThread isMainThread]);
|
||||
[s_themeChangeHandler removeCallback:ctx];
|
||||
}
|
||||
|
||||
// MARK: - Sound playback
|
||||
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
{
|
||||
NSString* nspath = [[NSString alloc] initWithUTF8String:path];
|
||||
NSSound* sound = [[NSSound alloc] initWithContentsOfFile:nspath byReference:YES];
|
||||
return [sound play];
|
||||
}
|
||||
|
||||
@@ -166,3 +166,10 @@ extern const u32 SPIN_TIME_NS;
|
||||
extern std::string GetOSVersionString();
|
||||
|
||||
void ScreensaverAllow(bool allow);
|
||||
|
||||
namespace Common
|
||||
{
|
||||
/// Abstracts platform-specific code for asynchronously playing a sound.
|
||||
/// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound.
|
||||
bool PlaySoundAsync(const char* path);
|
||||
} // namespace Common
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "common/Pcsx2Types.h"
|
||||
#include "common/General.h"
|
||||
@@ -64,4 +66,21 @@ void ScreensaverAllow(bool allow)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
{
|
||||
#ifdef __linux__
|
||||
// This is... pretty awful. But I can't think of a better way without linking to e.g. gstreamer.
|
||||
const char* cmdname = "aplay";
|
||||
const char* argv[] = {cmdname, path, nullptr};
|
||||
pid_t pid;
|
||||
|
||||
// Since we set SA_NOCLDWAIT in Qt, we don't need to wait here.
|
||||
int res = posix_spawnp(&pid, cmdname, nullptr, nullptr, const_cast<char**>(argv), environ);
|
||||
return (res == 0);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include "common/Exceptions.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/General.h"
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include <mmsystem.h>
|
||||
|
||||
#pragma comment(lib, "User32.lib")
|
||||
|
||||
alignas(16) static LARGE_INTEGER lfreq;
|
||||
@@ -116,4 +119,11 @@ void ScreensaverAllow(bool allow)
|
||||
flags |= ES_DISPLAY_REQUIRED;
|
||||
SetThreadExecutionState(flags);
|
||||
}
|
||||
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
{
|
||||
const std::wstring wpath(StringUtil::UTF8StringToWideString(path));
|
||||
return PlaySoundW(wpath.c_str(), NULL, SND_ASYNC | SND_NODEFAULT);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -104,12 +104,51 @@ static inline std::optional<T> ReadFileInZipToContainer(zip_t* zip, const char*
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
static inline std::optional<T> ReadFileInZipToContainer(zip_file_t* file, u32 chunk_size = 4096)
|
||||
{
|
||||
std::optional<T> ret = T();
|
||||
for (;;)
|
||||
{
|
||||
const size_t pos = ret->size();
|
||||
ret->resize(pos + chunk_size);
|
||||
const s64 read = zip_fread(file, ret->data() + pos, chunk_size);
|
||||
if (read < 0)
|
||||
{
|
||||
// read error
|
||||
ret.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
// if less than chunk size, we're EOF
|
||||
if (read != static_cast<s64>(chunk_size))
|
||||
{
|
||||
ret->resize(pos + static_cast<size_t>(read));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static inline std::optional<std::string> ReadFileInZipToString(zip_t* zip, const char* name)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::string>(zip, name);
|
||||
}
|
||||
|
||||
static inline std::optional<std::string> ReadFileInZipToString(zip_file_t* file, u32 chunk_size = 4096)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::string>(file, chunk_size);
|
||||
}
|
||||
|
||||
static inline std::optional<std::vector<u8>> ReadBinaryFileInZip(zip_t* zip, const char* name)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::vector<u8>>(zip, name);
|
||||
}
|
||||
|
||||
static inline std::optional<std::vector<u8>> ReadBinaryFileInZip(zip_file_t* file, u32 chunk_size = 4096)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::vector<u8>>(file, chunk_size);
|
||||
}
|
||||
@@ -121,6 +121,17 @@ target_sources(pcsx2-qt PRIVATE
|
||||
resources/resources.qrc
|
||||
)
|
||||
|
||||
if(USE_ACHIEVEMENTS)
|
||||
target_sources(pcsx2-qt PRIVATE
|
||||
Settings/AchievementLoginDialog.cpp
|
||||
Settings/AchievementLoginDialog.h
|
||||
Settings/AchievementLoginDialog.ui
|
||||
Settings/AchievementSettingsWidget.cpp
|
||||
Settings/AchievementSettingsWidget.h
|
||||
Settings/AchievementSettingsWidget.ui
|
||||
)
|
||||
endif()
|
||||
|
||||
target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
|
||||
|
||||
target_include_directories(pcsx2-qt PRIVATE
|
||||
|
||||
+44
-2
@@ -54,6 +54,10 @@
|
||||
#include "svnrev.h"
|
||||
#include "Tools/InputRecording/NewInputRecordingDlg.h"
|
||||
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
#include "pcsx2/Frontend/Achievements.h"
|
||||
#endif
|
||||
|
||||
|
||||
static constexpr char OPEN_FILE_FILTER[] =
|
||||
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.elf *.irx *.m3u *.gs *.gs.xz *.gs.zst *.dump);;"
|
||||
@@ -128,8 +132,8 @@ void MainWindow::initialize()
|
||||
CocoaTools::AddThemeChangeHandler(this, [](void* ctx) {
|
||||
// This handler is called *before* the style change has propagated far enough for Qt to see it
|
||||
// Use RunOnUIThread to delay until it has
|
||||
QtHost::RunOnUIThread([ctx = static_cast<MainWindow*>(ctx)]{
|
||||
ctx->updateTheme();// Qt won't notice the style change without us touching the palette in some way
|
||||
QtHost::RunOnUIThread([ctx = static_cast<MainWindow*>(ctx)] {
|
||||
ctx->updateTheme(); // Qt won't notice the style change without us touching the palette in some way
|
||||
});
|
||||
});
|
||||
#endif
|
||||
@@ -238,6 +242,38 @@ void MainWindow::setupAdditionalUi()
|
||||
|
||||
updateEmulationActions(false, false);
|
||||
updateDisplayRelatedActions(false, false, false);
|
||||
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
if (Achievements::IsUsingRAIntegration())
|
||||
{
|
||||
QMenu* raMenu = new QMenu(QStringLiteral("RAIntegration"), m_ui.menuDebug);
|
||||
connect(raMenu, &QMenu::aboutToShow, this, [this, raMenu]() {
|
||||
raMenu->clear();
|
||||
|
||||
const auto items = Achievements::RAIntegration::GetMenuItems();
|
||||
for (const auto& [id, title, checked] : items)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
raMenu->addSeparator();
|
||||
continue;
|
||||
}
|
||||
|
||||
QAction* raAction = raMenu->addAction(QString::fromUtf8(title));
|
||||
if (checked)
|
||||
{
|
||||
raAction->setCheckable(true);
|
||||
raAction->setChecked(checked);
|
||||
}
|
||||
|
||||
connect(raAction, &QAction::triggered, this, [id = id]() {
|
||||
Host::RunOnCPUThread([id]() { Achievements::RAIntegration::ActivateMenuItem(id); }, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
m_ui.menuDebug->insertMenu(m_ui.actionToggleSoftwareRendering, raMenu);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::connectSignals()
|
||||
@@ -271,6 +307,7 @@ void MainWindow::connectSignals()
|
||||
connect(m_ui.actionMemoryCardSettings, &QAction::triggered, [this]() { doSettings("Memory Cards"); });
|
||||
connect(m_ui.actionDEV9Settings, &QAction::triggered, [this]() { doSettings("Network & HDD"); });
|
||||
connect(m_ui.actionFolderSettings, &QAction::triggered, [this]() { doSettings("Folders"); });
|
||||
connect(m_ui.actionAchievementSettings, &QAction::triggered, [this]() { doSettings("Achievements"); });
|
||||
connect(
|
||||
m_ui.actionControllerSettings, &QAction::triggered, [this]() { doControllerSettings(ControllerSettingsDialog::Category::GlobalSettings); });
|
||||
connect(m_ui.actionHotkeySettings, &QAction::triggered, [this]() { doControllerSettings(ControllerSettingsDialog::Category::HotkeySettings); });
|
||||
@@ -1690,6 +1727,11 @@ void MainWindow::showEvent(QShowEvent* event)
|
||||
// the rest of the window... so, instead, let's just force it to be resized on show.
|
||||
if (isShowingGameList())
|
||||
m_game_list_widget->resizeTableViewColumnsToFit();
|
||||
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
if (Achievements::IsUsingRAIntegration())
|
||||
Achievements::RAIntegration::MainWindowChanged((void*)winId());
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* event)
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
<addaction name="actionFolderSettings"/>
|
||||
<addaction name="actionControllerSettings"/>
|
||||
<addaction name="actionHotkeySettings"/>
|
||||
<addaction name="actionAchievementSettings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAddGameDirectory"/>
|
||||
<addaction name="actionScanForNewGames"/>
|
||||
@@ -409,6 +410,15 @@
|
||||
<string>&Graphics</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAchievementSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="trophy-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>A&chievements</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPostProcessingSettings">
|
||||
<property name="text">
|
||||
<string>&Post-Processing Settings...</string>
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
#include "QtUtils.h"
|
||||
#include "svnrev.h"
|
||||
|
||||
#ifdef ENABLE_ACHIEVEMENTS
|
||||
#include "Frontend/Achievements.h"
|
||||
#endif
|
||||
|
||||
static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
|
||||
|
||||
EmuThread* g_emu_thread = nullptr;
|
||||
@@ -1111,6 +1115,45 @@ void Host::OnSaveStateSaved(const std::string_view& filename)
|
||||
emit g_emu_thread->onSaveStateSaved(QtUtils::StringViewToQString(filename));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_ACHIEVEMENTS
|
||||
void Host::OnAchievementsRefreshed()
|
||||
{
|
||||
u32 game_id = 0;
|
||||
u32 achievement_count = 0;
|
||||
u32 max_points = 0;
|
||||
|
||||
QString game_info;
|
||||
|
||||
if (Achievements::HasActiveGame())
|
||||
{
|
||||
game_id = Achievements::GetGameID();
|
||||
achievement_count = Achievements::GetAchievementCount();
|
||||
max_points = Achievements::GetMaximumPointsForGame();
|
||||
|
||||
game_info = qApp->translate("EmuThread",
|
||||
"Game ID: %1\n"
|
||||
"Game Title: %2\n"
|
||||
"Achievements: %5 (%6)\n\n")
|
||||
.arg(game_id)
|
||||
.arg(QString::fromStdString(Achievements::GetGameTitle()))
|
||||
.arg(achievement_count)
|
||||
.arg(qApp->translate("EmuThread", "%n points", "", max_points));
|
||||
|
||||
const std::string rich_presence_string(Achievements::GetRichPresenceString());
|
||||
if (!rich_presence_string.empty())
|
||||
game_info.append(QString::fromStdString(rich_presence_string));
|
||||
else
|
||||
game_info.append(qApp->translate("EmuThread", "Rich presence inactive or unsupported."));
|
||||
}
|
||||
else
|
||||
{
|
||||
game_info = qApp->translate("EmuThread", "Game not loaded or no RetroAchievements available.");
|
||||
}
|
||||
|
||||
emit g_emu_thread->onAchievementsRefreshed(game_id, game_info, achievement_count, max_points);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Host::CPUThreadVSync()
|
||||
{
|
||||
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
|
||||
@@ -1475,6 +1518,14 @@ void QtHost::HookSignals()
|
||||
{
|
||||
std::signal(SIGINT, SignalHandler);
|
||||
std::signal(SIGTERM, SignalHandler);
|
||||
|
||||
#ifdef __linux__
|
||||
// Ignore SIGCHLD by default on Linux, since we kick off aplay asynchronously.
|
||||
struct sigaction sa_chld = {};
|
||||
sigemptyset(&sa_chld.sa_mask);
|
||||
sa_chld.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT;
|
||||
sigaction(SIGCHLD, &sa_chld, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void QtHost::PrintCommandLineVersion()
|
||||
@@ -1504,6 +1555,9 @@ void QtHost::PrintCommandLineHelp(const std::string_view& progname)
|
||||
std::fprintf(stderr, " -fullscreen: Enters fullscreen mode immediately after starting.\n");
|
||||
std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n");
|
||||
std::fprintf(stderr, " -earlyconsolelog: Forces logging of early console messages to console.\n");
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
std::fprintf(stderr, " -raintegration: Use RAIntegration instead of built-in achievement support.\n");
|
||||
#endif
|
||||
std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n"
|
||||
" parameters make up the filename. Use when the filename contains\n"
|
||||
" spaces or starts with a dash.\n");
|
||||
@@ -1613,6 +1667,13 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
s_start_fullscreen_ui = true;
|
||||
continue;
|
||||
}
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
else if (CHECK_ARG(QStringLiteral("-raintegration")))
|
||||
{
|
||||
Achievements::SwitchToRAIntegration();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
else if (CHECK_ARG(QStringLiteral("--")))
|
||||
{
|
||||
no_more_args = true;
|
||||
|
||||
@@ -151,6 +151,9 @@ Q_SIGNALS:
|
||||
/// just signifies that the save has started, not necessarily completed.
|
||||
void onSaveStateSaved(const QString& path);
|
||||
|
||||
/// Called when achievements are reloaded/refreshed (e.g. game change, login, option change).
|
||||
void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "AchievementLoginDialog.h"
|
||||
#include "pcsx2/Frontend/Achievements.h"
|
||||
#include "QtHost.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AchievementLoginDialog::AchievementLoginDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.loginIcon->setPixmap(QIcon::fromTheme("login-box-line").pixmap(32));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
m_login = m_ui.buttonBox->addButton(tr("&Login"), QDialogButtonBox::AcceptRole);
|
||||
m_login->setEnabled(false);
|
||||
connectUi();
|
||||
}
|
||||
|
||||
AchievementLoginDialog::~AchievementLoginDialog() = default;
|
||||
|
||||
void AchievementLoginDialog::loginClicked()
|
||||
{
|
||||
std::string username(m_ui.userName->text().toStdString());
|
||||
std::string password(m_ui.password->text().toStdString());
|
||||
|
||||
// TODO: Make cancellable.
|
||||
m_ui.status->setText(tr("Logging in..."));
|
||||
enableUI(false);
|
||||
|
||||
Host::RunOnCPUThread([this, username = std::move(username), password = std::move(password)]() {
|
||||
const bool result = Achievements::Login(username.c_str(), password.c_str());
|
||||
QMetaObject::invokeMethod(this, "processLoginResult", Qt::QueuedConnection, Q_ARG(bool, result));
|
||||
});
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::cancelClicked()
|
||||
{
|
||||
done(1);
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::processLoginResult(bool result)
|
||||
{
|
||||
if (!result)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Login Error"),
|
||||
tr("Login failed. Please check your username and password, and try again."));
|
||||
m_ui.status->setText(tr("Login failed."));
|
||||
enableUI(true);
|
||||
return;
|
||||
}
|
||||
|
||||
done(0);
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::connectUi()
|
||||
{
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &AchievementLoginDialog::loginClicked);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &AchievementLoginDialog::cancelClicked);
|
||||
|
||||
auto enableLoginButton = [this](const QString&) { m_login->setEnabled(canEnableLoginButton()); };
|
||||
connect(m_ui.userName, &QLineEdit::textChanged, enableLoginButton);
|
||||
connect(m_ui.password, &QLineEdit::textChanged, enableLoginButton);
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::enableUI(bool enabled)
|
||||
{
|
||||
m_ui.userName->setEnabled(enabled);
|
||||
m_ui.password->setEnabled(enabled);
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(enabled);
|
||||
m_login->setEnabled(enabled && canEnableLoginButton());
|
||||
}
|
||||
|
||||
bool AchievementLoginDialog::canEnableLoginButton() const
|
||||
{
|
||||
return !m_ui.userName->text().isEmpty() && !m_ui.password->text().isEmpty();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "ui_AchievementLoginDialog.h"
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
class AchievementLoginDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AchievementLoginDialog(QWidget* parent);
|
||||
~AchievementLoginDialog();
|
||||
|
||||
private Q_SLOTS:
|
||||
void loginClicked();
|
||||
void cancelClicked();
|
||||
void processLoginResult(bool result);
|
||||
|
||||
private:
|
||||
void connectUi();
|
||||
void enableUI(bool enabled);
|
||||
bool canEnableLoginButton() const;
|
||||
|
||||
Ui::AchievementLoginDialog m_ui;
|
||||
QPushButton* m_login;
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AchievementLoginDialog</class>
|
||||
<widget class="QDialog" name="AchievementLoginDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>410</width>
|
||||
<height>190</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>410</width>
|
||||
<height>190</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>410</width>
|
||||
<height>190</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string comment="Window title">RetroAchievements Login</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="loginIcon">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>:/icons/black/48/login-box-line.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="Header text">RetroAchievements Login</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Please enter user name and password for retroachievements.org below. Your password will not be saved in PCSX2, an access token will be generated and used instead.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>User Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="userName"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="status">
|
||||
<property name="text">
|
||||
<string>Ready...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,194 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "AchievementSettingsWidget.h"
|
||||
#include "AchievementLoginDialog.h"
|
||||
#include "MainWindow.h"
|
||||
#include "SettingsDialog.h"
|
||||
#include "SettingWidgetBinder.h"
|
||||
#include "QtUtils.h"
|
||||
|
||||
#include "pcsx2/Frontend/Achievements.h"
|
||||
#include "pcsx2/HostSettings.h"
|
||||
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_dialog(dialog)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Achievements", "Enabled", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.richPresence, "Achievements", "RichPresence", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.challengeMode, "Achievements", "ChallengeMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboards, "Achievements", "Leaderboards", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.testMode, "Achievements", "TestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialTestMode, "Achievements",
|
||||
"UnofficialTestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Achievements",
|
||||
"SoundEffects", true);
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"),
|
||||
tr("When enabled and logged in, PCSX2 will scan for achievements on game load."));
|
||||
dialog->registerWidgetHelp(m_ui.testMode, tr("Enable Test Mode"), tr("Unchecked"),
|
||||
tr("When enabled, PCSX2 will assume all achievements are locked and not send any "
|
||||
"unlock notifications to the server."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.unofficialTestMode, tr("Test Unofficial Achievements"), tr("Unchecked"),
|
||||
tr("When enabled, PCSX2 will list achievements from unofficial sets. Please note that these achievements are "
|
||||
"not tracked by RetroAchievements, so they unlock every time."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.richPresence, tr("Enable Rich Presence"), tr("Unchecked"),
|
||||
tr("When enabled, rich presence information will be collected and sent to the server where supported."));
|
||||
dialog->registerWidgetHelp(m_ui.challengeMode, tr("Enable Hardcore Mode"), tr("Unchecked"),
|
||||
tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."));
|
||||
dialog->registerWidgetHelp(m_ui.leaderboards, tr("Enable Leaderboards"), tr("Checked"),
|
||||
tr("Enables tracking and submission of leaderboards in supported games. If leaderboards are disabled, you will still "
|
||||
"be able to view the leaderboard and scores, but no scores will be uploaded."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"),
|
||||
tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions."));
|
||||
|
||||
connect(m_ui.enable, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
||||
connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
||||
connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::onChallengeModeStateChanged);
|
||||
|
||||
if (!m_dialog->isPerGameSettings())
|
||||
{
|
||||
connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed);
|
||||
connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed);
|
||||
connect(g_emu_thread, &EmuThread::onAchievementsRefreshed, this, &AchievementSettingsWidget::onAchievementsRefreshed);
|
||||
updateLoginState();
|
||||
|
||||
// force a refresh of game info
|
||||
Host::RunOnCPUThread(Host::OnAchievementsRefreshed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove login and game info, not relevant for per-game
|
||||
m_ui.verticalLayout->removeWidget(m_ui.gameInfoBox);
|
||||
m_ui.gameInfoBox->deleteLater();
|
||||
m_ui.gameInfoBox = nullptr;
|
||||
m_ui.verticalLayout->removeWidget(m_ui.loginBox);
|
||||
m_ui.loginBox->deleteLater();
|
||||
m_ui.loginBox = nullptr;
|
||||
}
|
||||
|
||||
updateEnableState();
|
||||
}
|
||||
|
||||
AchievementSettingsWidget::~AchievementSettingsWidget() = default;
|
||||
|
||||
void AchievementSettingsWidget::updateEnableState()
|
||||
{
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("Achievements", "Enabled", false);
|
||||
const bool challenge = m_dialog->getEffectiveBoolValue("Achievements", "ChallengeMode", false);
|
||||
m_ui.testMode->setEnabled(enabled);
|
||||
m_ui.unofficialTestMode->setEnabled(enabled);
|
||||
m_ui.richPresence->setEnabled(enabled);
|
||||
m_ui.challengeMode->setEnabled(enabled);
|
||||
m_ui.leaderboards->setEnabled(enabled && challenge);
|
||||
m_ui.soundEffects->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onChallengeModeStateChanged()
|
||||
{
|
||||
if (!QtHost::IsVMValid())
|
||||
return;
|
||||
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("Achievements", "Enabled", false);
|
||||
const bool challenge = m_dialog->getEffectiveBoolValue("Achievements", "ChallengeMode", false);
|
||||
if (!enabled || !challenge)
|
||||
return;
|
||||
|
||||
// don't bother prompting if the game doesn't have achievements
|
||||
auto lock = Achievements::GetLock();
|
||||
if (!Achievements::HasActiveGame())
|
||||
return;
|
||||
|
||||
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Reset System"),
|
||||
tr("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?")) != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_emu_thread->resetVM();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::updateLoginState()
|
||||
{
|
||||
const std::string username(Host::GetBaseStringSettingValue("Achievements", "Username"));
|
||||
const bool logged_in = !username.empty();
|
||||
|
||||
if (logged_in)
|
||||
{
|
||||
const u64 login_unix_timestamp = StringUtil::FromChars<u64>(Host::GetBaseStringSettingValue("Achievements", "LoginTimestamp", "0")).value_or(0);
|
||||
const QDateTime login_timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(login_unix_timestamp)));
|
||||
m_ui.loginStatus->setText(tr("Username: %1\nLogin token generated on %2.")
|
||||
.arg(QString::fromStdString(username))
|
||||
.arg(login_timestamp.toString(Qt::TextDate)));
|
||||
m_ui.loginButton->setText(tr("Logout"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.loginStatus->setText(tr("Not Logged In."));
|
||||
m_ui.loginButton->setText(tr("Login..."));
|
||||
}
|
||||
|
||||
m_ui.viewProfile->setEnabled(logged_in);
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onLoginLogoutPressed()
|
||||
{
|
||||
if (!Host::GetBaseStringSettingValue("Achievements", "Username").empty())
|
||||
{
|
||||
Host::RunOnCPUThread(Achievements::Logout, true);
|
||||
updateLoginState();
|
||||
return;
|
||||
}
|
||||
|
||||
AchievementLoginDialog login(this);
|
||||
int res = login.exec();
|
||||
if (res != 0)
|
||||
return;
|
||||
|
||||
updateLoginState();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onViewProfilePressed()
|
||||
{
|
||||
const std::string username(Host::GetBaseStringSettingValue("Achievements", "Username"));
|
||||
if (username.empty())
|
||||
return;
|
||||
|
||||
const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(username)));
|
||||
QtUtils::OpenURL(
|
||||
QtUtils::GetRootWidget(this),
|
||||
QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username))));
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total,
|
||||
quint32 points)
|
||||
{
|
||||
m_ui.gameInfo->setText(game_info_string);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QtWidgets/QWidget>
|
||||
#include "ui_AchievementSettingsWidget.h"
|
||||
|
||||
class SettingsDialog;
|
||||
|
||||
class AchievementSettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~AchievementSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateEnableState();
|
||||
void onChallengeModeStateChanged();
|
||||
void onLoginLogoutPressed();
|
||||
void onViewProfilePressed();
|
||||
void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
|
||||
private:
|
||||
void updateLoginState();
|
||||
|
||||
Ui::AchievementSettingsWidget m_ui;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AchievementSettingsWidget</class>
|
||||
<widget class="QWidget" name="AchievementSettingsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>648</width>
|
||||
<height>456</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Global Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="leaderboards">
|
||||
<property name="text">
|
||||
<string>Enable Leaderboards</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="enable">
|
||||
<property name="text">
|
||||
<string>Enable Achievements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="richPresence">
|
||||
<property name="text">
|
||||
<string>Enable Rich Presence</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="challengeMode">
|
||||
<property name="text">
|
||||
<string>Enable Hardcore Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="testMode">
|
||||
<property name="text">
|
||||
<string>Enable Test Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="soundEffects">
|
||||
<property name="text">
|
||||
<string>Enable Sound Effects</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="unofficialTestMode">
|
||||
<property name="text">
|
||||
<string>Test Unofficial Achievements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="loginBox">
|
||||
<property name="title">
|
||||
<string>Account</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="loginStatus">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="loginButton">
|
||||
<property name="text">
|
||||
<string>Login...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="viewProfile">
|
||||
<property name="text">
|
||||
<string>View Profile...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameInfoBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>160</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Game Info</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="gameInfo">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="justify">PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user