Merge pull request #1332 from FearlessTobi/port-web-backend

Port web_service from Citra
This commit is contained in:
bunnei
2018-10-06 02:43:09 -04:00
committed by GitHub
55 changed files with 21637 additions and 40 deletions

View File

@ -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()

65
src/yuzu/compatdb.cpp Normal file
View File

@ -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);
}

26
src/yuzu/compatdb.h Normal file
View File

@ -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();
};

215
src/yuzu/compatdb.ui Normal file
View File

@ -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>

View File

@ -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);

View File

@ -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>

View File

@ -27,5 +27,6 @@ void ConfigureDialog::applyConfiguration() {
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration();
ui->webTab->applyConfiguration();
Settings::Apply();
}

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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>

25
src/yuzu/discord.h Normal file
View File

@ -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

52
src/yuzu/discord_impl.cpp Normal file
View File

@ -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

20
src/yuzu/discord_impl.h Normal file
View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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>

View File

@ -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;