summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt20
-rw-r--r--src/yuzu/compatdb.cpp65
-rw-r--r--src/yuzu/compatdb.h26
-rw-r--r--src/yuzu/compatdb.ui215
-rw-r--r--src/yuzu/configuration/config.cpp18
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp1
-rw-r--r--src/yuzu/configuration/configure_web.cpp119
-rw-r--r--src/yuzu/configuration/configure_web.h38
-rw-r--r--src/yuzu/configuration/configure_web.ui206
-rw-r--r--src/yuzu/discord.h25
-rw-r--r--src/yuzu/discord_impl.cpp52
-rw-r--r--src/yuzu/discord_impl.h20
-rw-r--r--src/yuzu/main.cpp82
-rw-r--r--src/yuzu/main.h10
-rw-r--r--src/yuzu/main.ui16
-rw-r--r--src/yuzu/ui_settings.h3
17 files changed, 911 insertions, 16 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index f48b69809..04464ad5e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -29,6 +29,8 @@ add_executable(yuzu
configuration/configure_input.h
configuration/configure_system.cpp
configuration/configure_system.h
+ configuration/configure_web.cpp
+ configuration/configure_web.h
debugger/graphics/graphics_breakpoint_observer.cpp
debugger/graphics/graphics_breakpoint_observer.h
debugger/graphics/graphics_breakpoints.cpp
@@ -42,6 +44,7 @@ add_executable(yuzu
debugger/profiler.h
debugger/wait_tree.cpp
debugger/wait_tree.h
+ discord.h
game_list.cpp
game_list.h
game_list_p.h
@@ -57,6 +60,8 @@ add_executable(yuzu
util/spinbox.h
util/util.cpp
util/util.h
+ compatdb.cpp
+ compatdb.h
yuzu.rc
)
@@ -70,8 +75,10 @@ set(UIS
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_system.ui
+ configuration/configure_web.ui
hotkeys.ui
main.ui
+ compatdb.ui
)
file(GLOB COMPAT_LIST
@@ -113,6 +120,19 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
+if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
+ add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING)
+endif()
+
+if (USE_DISCORD_PRESENCE)
+ target_sources(yuzu PUBLIC
+ discord_impl.cpp
+ discord_impl.h
+ )
+ target_link_libraries(yuzu PRIVATE discord-rpc)
+ target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
+endif()
+
if(UNIX AND NOT APPLE)
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
new file mode 100644
index 000000000..91e754274
--- /dev/null
+++ b/src/yuzu/compatdb.cpp
@@ -0,0 +1,65 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QButtonGroup>
+#include <QMessageBox>
+#include <QPushButton>
+#include "common/logging/log.h"
+#include "common/telemetry.h"
+#include "core/core.h"
+#include "core/telemetry_session.h"
+#include "ui_compatdb.h"
+#include "yuzu/compatdb.h"
+
+CompatDB::CompatDB(QWidget* parent)
+ : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
+ ui{std::make_unique<Ui::CompatDB>()} {
+ ui->setupUi(this);
+ connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);
+}
+
+CompatDB::~CompatDB() = default;
+
+enum class CompatDBPage {
+ Intro = 0,
+ Selection = 1,
+ Final = 2,
+};
+
+void CompatDB::Submit() {
+ QButtonGroup* compatibility = new QButtonGroup(this);
+ compatibility->addButton(ui->radioButton_Perfect, 0);
+ compatibility->addButton(ui->radioButton_Great, 1);
+ compatibility->addButton(ui->radioButton_Okay, 2);
+ compatibility->addButton(ui->radioButton_Bad, 3);
+ compatibility->addButton(ui->radioButton_IntroMenu, 4);
+ compatibility->addButton(ui->radioButton_WontBoot, 5);
+ switch ((static_cast<CompatDBPage>(currentId()))) {
+ case CompatDBPage::Selection:
+ if (compatibility->checkedId() == -1) {
+ button(NextButton)->setEnabled(false);
+ }
+ break;
+ case CompatDBPage::Final:
+ LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
+ Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility",
+ compatibility->checkedId());
+ // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
+ // workaround
+ button(QWizard::CancelButton)->setVisible(false);
+ break;
+ default:
+ LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
+ }
+}
+
+void CompatDB::EnableNext() {
+ button(NextButton)->setEnabled(true);
+}
diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h
new file mode 100644
index 000000000..ca0dd11d6
--- /dev/null
+++ b/src/yuzu/compatdb.h
@@ -0,0 +1,26 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWizard>
+
+namespace Ui {
+class CompatDB;
+}
+
+class CompatDB : public QWizard {
+ Q_OBJECT
+
+public:
+ explicit CompatDB(QWidget* parent = nullptr);
+ ~CompatDB();
+
+private:
+ std::unique_ptr<Ui::CompatDB> ui;
+
+ void Submit();
+ void EnableNext();
+};
diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui
new file mode 100644
index 000000000..fed402176
--- /dev/null
+++ b/src/yuzu/compatdb.ui
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CompatDB</class>
+ <widget class="QWizard" name="CompatDB">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>600</width>
+ <height>482</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>410</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Report Compatibility</string>
+ </property>
+ <property name="options">
+ <set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set>
+ </property>
+ <widget class="QWizardPage" name="wizard_Info">
+ <property name="title">
+ <string>Report Game Compatibility</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">0</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="lbl_Spiel">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="wizard_Report">
+ <property name="title">
+ <string>Report Game Compatibility</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">1</string>
+ </attribute>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="2" column="0">
+ <widget class="QRadioButton" name="radioButton_Perfect">
+ <property name="text">
+ <string>Perfect</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="lbl_Perfect">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QRadioButton" name="radioButton_Great">
+ <property name="text">
+ <string>Great </string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="lbl_Great">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QRadioButton" name="radioButton_Okay">
+ <property name="text">
+ <string>Okay</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLabel" name="lbl_Okay">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QRadioButton" name="radioButton_Bad">
+ <property name="text">
+ <string>Bad</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLabel" name="lbl_Bad">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QRadioButton" name="radioButton_IntroMenu">
+ <property name="text">
+ <string>Intro/Menu</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QLabel" name="lbl_IntroMenu">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QRadioButton" name="radioButton_WontBoot">
+ <property name="text">
+ <string>Won't Boot</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="1">
+ <widget class="QLabel" name="lbl_WontBoot">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="lbl_Independent">
+ <property name="font">
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="wizard_ThankYou">
+ <property name="title">
+ <string>Thank you for your submission!</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">2</string>
+ </attribute>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index d229225b4..650dd03c0 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -136,8 +136,18 @@ void Config::ReadValues() {
Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
qt_config->endGroup();
+ qt_config->beginGroup("WebService");
+ Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
+ Settings::values.web_api_url =
+ qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
+ Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString();
+ Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
+ qt_config->endGroup();
+
qt_config->beginGroup("UI");
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
+ UISettings::values.enable_discord_presence =
+ qt_config->value("enable_discord_presence", true).toBool();
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -261,8 +271,16 @@ void Config::SaveValues() {
qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
qt_config->endGroup();
+ qt_config->beginGroup("WebService");
+ qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
+ qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url));
+ qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
+ qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
+ qt_config->endGroup();
+
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
+ qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
qt_config->beginGroup("UIGameList");
qt_config->setValue("show_unknown", UISettings::values.show_unknown);
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 20f120134..9b297df28 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -54,6 +54,11 @@
<string>Debug</string>
</attribute>
</widget>
+ <widget class="ConfigureWeb" name="webTab">
+ <attribute name="title">
+ <string>Web</string>
+ </attribute>
+ </widget>
</widget>
</item>
<item>
@@ -108,6 +113,12 @@
<header>configuration/configure_graphics.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>ConfigureWeb</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_web.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index daa4cc0d9..3905423e9 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -27,5 +27,6 @@ void ConfigureDialog::applyConfiguration() {
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration();
+ ui->webTab->applyConfiguration();
Settings::Apply();
}
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
new file mode 100644
index 000000000..5fb9251db
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -0,0 +1,119 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QIcon>
+#include <QMessageBox>
+#include <QtConcurrent/QtConcurrentRun>
+#include "core/settings.h"
+#include "core/telemetry_session.h"
+#include "ui_configure_web.h"
+#include "yuzu/configuration/configure_web.h"
+#include "yuzu/ui_settings.h"
+
+ConfigureWeb::ConfigureWeb(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
+ ui->setupUi(this);
+ connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
+ &ConfigureWeb::RefreshTelemetryID);
+ connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
+ connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
+
+#ifndef USE_DISCORD_PRESENCE
+ ui->discord_group->setVisible(false);
+#endif
+ this->setConfiguration();
+}
+
+ConfigureWeb::~ConfigureWeb() = default;
+
+void ConfigureWeb::setConfiguration() {
+ ui->web_credentials_disclaimer->setWordWrap(true);
+ ui->telemetry_learn_more->setOpenExternalLinks(true);
+ ui->telemetry_learn_more->setText(
+ tr("<a href='https://yuzu-emu.org/help/features/telemetry/'><span style=\"text-decoration: "
+ "underline; color:#039be5;\">Learn more</span></a>"));
+
+ ui->web_signup_link->setOpenExternalLinks(true);
+ ui->web_signup_link->setText(
+ tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "
+ "color:#039be5;\">Sign up</span></a>"));
+ ui->web_token_info_link->setOpenExternalLinks(true);
+ ui->web_token_info_link->setText(
+ tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: "
+ "underline; color:#039be5;\">What is my token?</span></a>"));
+
+ ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
+ ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username));
+ ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token));
+ // Connect after setting the values, to avoid calling OnLoginChanged now
+ connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
+ connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
+ ui->label_telemetry_id->setText(
+ tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
+ user_verified = true;
+
+ ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
+}
+
+void ConfigureWeb::applyConfiguration() {
+ Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
+ UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
+ if (user_verified) {
+ Settings::values.yuzu_username = ui->edit_username->text().toStdString();
+ Settings::values.yuzu_token = ui->edit_token->text().toStdString();
+ } else {
+ QMessageBox::warning(this, tr("Username and token not verified"),
+ tr("Username and token were not verified. The changes to your "
+ "username and/or token have not been saved."));
+ }
+}
+
+void ConfigureWeb::RefreshTelemetryID() {
+ const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
+ ui->label_telemetry_id->setText(
+ tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
+}
+
+void ConfigureWeb::OnLoginChanged() {
+ if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
+ user_verified = true;
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ } else {
+ user_verified = false;
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ }
+}
+
+void ConfigureWeb::VerifyLogin() {
+ ui->button_verify_login->setDisabled(true);
+ ui->button_verify_login->setText(tr("Verifying"));
+ verify_watcher.setFuture(
+ QtConcurrent::run([this, username = ui->edit_username->text().toStdString(),
+ token = ui->edit_token->text().toStdString()]() {
+ return Core::VerifyLogin(username, token);
+ }));
+}
+
+void ConfigureWeb::OnLoginVerified() {
+ ui->button_verify_login->setEnabled(true);
+ ui->button_verify_login->setText(tr("Verify"));
+ if (verify_watcher.result()) {
+ user_verified = true;
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ } else {
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ QMessageBox::critical(
+ this, tr("Verification failed"),
+ tr("Verification failed. Check that you have entered your username and token "
+ "correctly, and that your internet connection is working."));
+ }
+}
+
+void ConfigureWeb::retranslateUi() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h
new file mode 100644
index 000000000..7741ab95d
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.h
@@ -0,0 +1,38 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QFutureWatcher>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureWeb;
+}
+
+class ConfigureWeb : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureWeb(QWidget* parent = nullptr);
+ ~ConfigureWeb();
+
+ void applyConfiguration();
+ void retranslateUi();
+
+public slots:
+ void RefreshTelemetryID();
+ void OnLoginChanged();
+ void VerifyLogin();
+ void OnLoginVerified();
+
+private:
+ void setConfiguration();
+
+ bool user_verified = true;
+ QFutureWatcher<bool> verify_watcher;
+
+ std::unique_ptr<Ui::ConfigureWeb> ui;
+};
diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui
new file mode 100644
index 000000000..2f4b9dd73
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.ui
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureWeb</class>
+ <widget class="QWidget" name="ConfigureWeb">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>926</width>
+ <height>561</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="groupBoxWebConfig">
+ <property name="title">
+ <string>yuzu Web Service</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService">
+ <item>
+ <widget class="QLabel" name="web_credentials_disclaimer">
+ <property name="text">
+ <string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayoutYuzuUsername">
+ <item row="2" column="3">
+ <widget class="QPushButton" name="button_verify_login">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
+ </property>
+ <property name="text">
+ <string>Verify</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="web_signup_link">
+ <property name="text">
+ <string>Sign up</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="3">
+ <widget class="QLineEdit" name="edit_username">
+ <property name="maxLength">
+ <number>36</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_token">
+ <property name="text">
+ <string>Token: </string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QLabel" name="label_token_verified">
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_username">
+ <property name="text">
+ <string>Username: </string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="label_username_verified">
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="3">
+ <widget class="QLineEdit" name="edit_token">
+ <property name="maxLength">
+ <number>36</number>
+ </property>
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="web_token_info_link">
+ <property name="text">
+ <string>What is my token?</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Telemetry</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="toggle_telemetry">
+ <property name="text">
+ <string>Share anonymous usage data with the yuzu team</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="telemetry_learn_more">
+ <property name="text">
+ <string>Learn more</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayoutTelemetryId">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_telemetry_id">
+ <property name="text">
+ <string>Telemetry ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="button_regenerate_telemetry_id">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
+ </property>
+ <property name="text">
+ <string>Regenerate</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="discord_group">
+ <property name="title">
+ <string>Discord Presence</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <item>
+ <widget class="QCheckBox" name="toggle_discordrpc">
+ <property name="text">
+ <string>Show Current Game in your Discord Status</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </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/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/discord.h b/src/yuzu/discord.h
new file mode 100644
index 000000000..a867cc4d6
--- /dev/null
+++ b/src/yuzu/discord.h
@@ -0,0 +1,25 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace DiscordRPC {
+
+class DiscordInterface {
+public:
+ virtual ~DiscordInterface() = default;
+
+ virtual void Pause() = 0;
+ virtual void Update() = 0;
+};
+
+class NullImpl : public DiscordInterface {
+public:
+ ~NullImpl() = default;
+
+ void Pause() override {}
+ void Update() override {}
+};
+
+} // namespace DiscordRPC
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
new file mode 100644
index 000000000..9d87a41eb
--- /dev/null
+++ b/src/yuzu/discord_impl.cpp
@@ -0,0 +1,52 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <string>
+#include <discord_rpc.h>
+#include "common/common_types.h"
+#include "core/core.h"
+#include "core/loader/loader.h"
+#include "yuzu/discord_impl.h"
+#include "yuzu/ui_settings.h"
+
+namespace DiscordRPC {
+
+DiscordImpl::DiscordImpl() {
+ DiscordEventHandlers handlers{};
+
+ // The number is the client ID for yuzu, it's used for images and the
+ // application name
+ Discord_Initialize("471872241299226636", &handlers, 1, nullptr);
+}
+
+DiscordImpl::~DiscordImpl() {
+ Discord_ClearPresence();
+ Discord_Shutdown();
+}
+
+void DiscordImpl::Pause() {
+ Discord_ClearPresence();
+}
+
+void DiscordImpl::Update() {
+ s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ std::string title;
+ if (Core::System::GetInstance().IsPoweredOn())
+ Core::System::GetInstance().GetAppLoader().ReadTitle(title);
+ DiscordRichPresence presence{};
+ presence.largeImageKey = "yuzu_logo";
+ presence.largeImageText = "yuzu is an emulator for the Nintendo Switch";
+ if (Core::System::GetInstance().IsPoweredOn()) {
+ presence.state = title.c_str();
+ presence.details = "Currently in game";
+ } else {
+ presence.details = "Not in game";
+ }
+ presence.startTimestamp = start_time;
+ Discord_UpdatePresence(&presence);
+}
+} // namespace DiscordRPC
diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h
new file mode 100644
index 000000000..4bfda8cdf
--- /dev/null
+++ b/src/yuzu/discord_impl.h
@@ -0,0 +1,20 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "yuzu/discord.h"
+
+namespace DiscordRPC {
+
+class DiscordImpl : public DiscordInterface {
+public:
+ DiscordImpl();
+ ~DiscordImpl() override;
+
+ void Pause() override;
+ void Update() override;
+};
+
+} // namespace DiscordRPC
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 27015d02c..147e7fcad 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -35,6 +35,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QtWidgets>
#include <fmt/format.h>
#include "common/common_paths.h"
+#include "common/detached_tasks.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
@@ -65,6 +66,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "video_core/debug_utils/debug_utils.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
+#include "yuzu/compatdb.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
@@ -73,12 +75,17 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/debugger/profiler.h"
#include "yuzu/debugger/wait_tree.h"
+#include "yuzu/discord.h"
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
#include "yuzu/hotkeys.h"
#include "yuzu/main.h"
#include "yuzu/ui_settings.h"
+#ifdef USE_DISCORD_PRESENCE
+#include "yuzu/discord_impl.h"
+#endif
+
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
@@ -102,23 +109,22 @@ enum class CalloutFlag : uint32_t {
DRDDeprecation = 0x2,
};
-static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
- if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
+void GMainWindow::ShowTelemetryCallout() {
+ if (UISettings::values.callout_flags & static_cast<uint32_t>(CalloutFlag::Telemetry)) {
return;
}
- UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
-
- QMessageBox msg;
- msg.setText(message);
- msg.setStandardButtons(QMessageBox::Ok);
- msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- msg.setStyleSheet("QLabel{min-width: 900px;}");
- msg.exec();
+ UISettings::values.callout_flags |= static_cast<uint32_t>(CalloutFlag::Telemetry);
+ const QString telemetry_message =
+ tr("<a href='https://yuzu-emu.org/help/features/telemetry/'>Anonymous "
+ "data is collected</a> to help improve yuzu. "
+ "<br/><br/>Would you like to share your usage data with us?");
+ if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
+ Settings::values.enable_telemetry = false;
+ Settings::Apply();
+ }
}
-void GMainWindow::ShowCallouts() {}
-
const int GMainWindow::max_recent_files_item;
static void InitializeLogging() {
@@ -145,6 +151,9 @@ GMainWindow::GMainWindow()
default_theme_paths = QIcon::themeSearchPaths();
UpdateUITheme();
+ SetDiscordEnabled(UISettings::values.enable_discord_presence);
+ discord_rpc->Update();
+
InitializeWidgets();
InitializeDebugWidgets();
InitializeRecentFileMenuActions();
@@ -168,7 +177,7 @@ GMainWindow::GMainWindow()
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
// Show one-time "callout" messages to the user
- ShowCallouts();
+ ShowTelemetryCallout();
QStringList args = QApplication::arguments();
if (args.length() >= 2) {
@@ -183,6 +192,9 @@ GMainWindow::~GMainWindow() {
}
void GMainWindow::InitializeWidgets() {
+#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
+ ui.action_Report_Compatibility->setVisible(true);
+#endif
render_window = new GRenderWindow(this, emu_thread.get());
render_window->hide();
@@ -411,6 +423,8 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
+ connect(ui.action_Report_Compatibility, &QAction::triggered, this,
+ &GMainWindow::OnMenuReportCompatibility);
connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
@@ -647,6 +661,7 @@ void GMainWindow::BootGame(const QString& filename) {
}
void GMainWindow::ShutdownGame() {
+ discord_rpc->Pause();
emu_thread->RequestStop();
emit EmulationStopping();
@@ -655,6 +670,8 @@ void GMainWindow::ShutdownGame() {
emu_thread->wait();
emu_thread = nullptr;
+ discord_rpc->Update();
+
// The emulation is stopped, so closing the window or not does not matter anymore
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -664,6 +681,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false);
+ ui.action_Report_Compatibility->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -1147,6 +1165,9 @@ void GMainWindow::OnStartGame() {
ui.action_Pause->setEnabled(true);
ui.action_Stop->setEnabled(true);
ui.action_Restart->setEnabled(true);
+ ui.action_Report_Compatibility->setEnabled(true);
+
+ discord_rpc->Update();
}
void GMainWindow::OnPauseGame() {
@@ -1161,6 +1182,20 @@ void GMainWindow::OnStopGame() {
ShutdownGame();
}
+void GMainWindow::OnMenuReportCompatibility() {
+ if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
+ CompatDB compatdb{this};
+ compatdb.exec();
+ } else {
+ QMessageBox::critical(
+ this, tr("Missing yuzu Account"),
+ tr("In order to submit a game compatibility test case, you must link your yuzu "
+ "account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration "
+ "&gt; "
+ "Web."));
+ }
+}
+
void GMainWindow::ToggleFullscreen() {
if (!emulation_running) {
return;
@@ -1224,11 +1259,14 @@ void GMainWindow::ToggleWindowMode() {
void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this, hotkey_registry);
auto old_theme = UISettings::values.theme;
+ const bool old_discord_presence = UISettings::values.enable_discord_presence;
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
if (UISettings::values.theme != old_theme)
UpdateUITheme();
+ if (UISettings::values.enable_discord_presence != old_discord_presence)
+ SetDiscordEnabled(UISettings::values.enable_discord_presence);
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
config->Save();
}
@@ -1443,11 +1481,25 @@ void GMainWindow::UpdateUITheme() {
emit UpdateThemedIcons();
}
+void GMainWindow::SetDiscordEnabled(bool state) {
+#ifdef USE_DISCORD_PRESENCE
+ if (state) {
+ discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>();
+ } else {
+ discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
+ }
+#else
+ discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
+#endif
+ discord_rpc->Update();
+}
+
#ifdef main
#undef main
#endif
int main(int argc, char* argv[]) {
+ Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
@@ -1465,5 +1517,7 @@ int main(int argc, char* argv[]) {
GMainWindow main_window;
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();
- return app.exec();
+ int result = app.exec();
+ detached_tasks.WaitForAllTasks();
+ return result;
}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 8ee9242b1..fe0e9a50a 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -41,6 +41,10 @@ enum class EmulatedDirectoryTarget {
SDMC,
};
+namespace DiscordRPC {
+class DiscordInterface;
+}
+
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -61,6 +65,8 @@ public:
GMainWindow();
~GMainWindow() override;
+ std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
+
signals:
/**
@@ -99,7 +105,8 @@ private:
void BootGame(const QString& filename);
void ShutdownGame();
- void ShowCallouts();
+ void ShowTelemetryCallout();
+ void SetDiscordEnabled(bool state);
/**
* Stores the filename in the recently loaded files list.
@@ -135,6 +142,7 @@ private slots:
void OnStartGame();
void OnPauseGame();
void OnStopGame();
+ void OnMenuReportCompatibility();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 3879d4813..cb1664b21 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -45,7 +45,7 @@
<x>0</x>
<y>0</y>
<width>1081</width>
- <height>19</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@@ -101,6 +101,8 @@
<property name="title">
<string>&amp;Help</string>
</property>
+ <addaction name="action_Report_Compatibility"/>
+ <addaction name="separator"/>
<addaction name="action_About"/>
</widget>
<addaction name="menu_File"/>
@@ -239,6 +241,18 @@
<string>Restart</string>
</property>
</action>
+ <action name="action_Report_Compatibility">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Report Compatibility</string>
+ </property>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ </action>
</widget>
<resources/>
+ <connections/>
</ui>
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 2d679004f..2e617d52a 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -38,6 +38,9 @@ struct Values {
bool confirm_before_closing;
bool first_start;
+ // Discord RPC
+ bool enable_discord_presence;
+
QString roms_path;
QString symbols_path;
QString gamedir;