summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/applets/web_browser.cpp113
-rw-r--r--src/yuzu/applets/web_browser.h52
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp17
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp300
-rw-r--r--src/yuzu/configuration/configure_profile_manager.h57
-rw-r--r--src/yuzu/configuration/configure_profile_manager.ui172
-rw-r--r--src/yuzu/configuration/configure_system.cpp274
-rw-r--r--src/yuzu/configuration/configure_system.h27
-rw-r--r--src/yuzu/configuration/configure_system.ui217
-rw-r--r--src/yuzu/configuration/configure_web.cpp11
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/main.cpp151
-rw-r--r--src/yuzu/main.h7
16 files changed, 980 insertions, 460 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 17ecaafde..1f852df4b 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -11,6 +11,8 @@ add_executable(yuzu
applets/profile_select.h
applets/software_keyboard.cpp
applets/software_keyboard.h
+ applets/web_browser.cpp
+ applets/web_browser.h
bootmanager.cpp
bootmanager.h
compatibility_list.cpp
@@ -37,6 +39,8 @@ add_executable(yuzu
configuration/configure_input_simple.h
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
+ configuration/configure_profile_manager.cpp
+ configuration/configure_profile_manager.h
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_per_general.cpp
@@ -94,6 +98,7 @@ set(UIS
configuration/configure_input_simple.ui
configuration/configure_mouse_advanced.ui
configuration/configure_per_general.ui
+ configuration/configure_profile_manager.ui
configuration/configure_system.ui
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
@@ -154,6 +159,11 @@ if (USE_DISCORD_PRESENCE)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
+if (YUZU_USE_QT_WEB_ENGINE)
+ target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets)
+ target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
+endif ()
+
if(UNIX AND NOT APPLE)
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp
new file mode 100644
index 000000000..6a9138d53
--- /dev/null
+++ b/src/yuzu/applets/web_browser.cpp
@@ -0,0 +1,113 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <mutex>
+
+#include <QKeyEvent>
+
+#include "core/hle/lock.h"
+#include "yuzu/applets/web_browser.h"
+#include "yuzu/main.h"
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+constexpr char NX_SHIM_INJECT_SCRIPT[] = R"(
+ window.nx = {};
+ window.nx.playReport = {};
+ window.nx.playReport.setCounterSetIdentifier = function () {
+ console.log("nx.playReport.setCounterSetIdentifier called - unimplemented");
+ };
+
+ window.nx.playReport.incrementCounter = function () {
+ console.log("nx.playReport.incrementCounter called - unimplemented");
+ };
+
+ window.nx.footer = {};
+ window.nx.footer.unsetAssign = function () {
+ console.log("nx.footer.unsetAssign called - unimplemented");
+ };
+
+ var yuzu_key_callbacks = [];
+ window.nx.footer.setAssign = function(key, discard1, func, discard2) {
+ switch (key) {
+ case 'A':
+ yuzu_key_callbacks[0] = func;
+ break;
+ case 'B':
+ yuzu_key_callbacks[1] = func;
+ break;
+ case 'X':
+ yuzu_key_callbacks[2] = func;
+ break;
+ case 'Y':
+ yuzu_key_callbacks[3] = func;
+ break;
+ case 'L':
+ yuzu_key_callbacks[6] = func;
+ break;
+ case 'R':
+ yuzu_key_callbacks[7] = func;
+ break;
+ }
+ };
+
+ var applet_done = false;
+ window.nx.endApplet = function() {
+ applet_done = true;
+ };
+)";
+
+QString GetNXShimInjectionScript() {
+ return QString::fromStdString(NX_SHIM_INJECT_SCRIPT);
+}
+
+NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {}
+
+void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) {
+ parent()->event(event);
+}
+
+void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) {
+ parent()->event(event);
+}
+
+#endif
+
+QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
+ connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage,
+ Qt::QueuedConnection);
+ connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this,
+ &QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection);
+ connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this,
+ &QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection);
+}
+
+QtWebBrowser::~QtWebBrowser() = default;
+
+void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) {
+ this->unpack_romfs_callback = std::move(unpack_romfs_callback);
+ this->finished_callback = std::move(finished_callback);
+
+ const auto index = url.find('?');
+ if (index == std::string::npos) {
+ emit MainWindowOpenPage(url, "");
+ } else {
+ const auto front = url.substr(0, index);
+ const auto back = url.substr(index);
+ emit MainWindowOpenPage(front, back);
+ }
+}
+
+void QtWebBrowser::MainWindowUnpackRomFS() {
+ // Acquire the HLE mutex
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ unpack_romfs_callback();
+}
+
+void QtWebBrowser::MainWindowFinishedBrowsing() {
+ // Acquire the HLE mutex
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ finished_callback();
+}
diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h
new file mode 100644
index 000000000..1a3d67353
--- /dev/null
+++ b/src/yuzu/applets/web_browser.h
@@ -0,0 +1,52 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <QObject>
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <QWebEngineView>
+#endif
+
+#include "core/frontend/applets/web_browser.h"
+
+class GMainWindow;
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+QString GetNXShimInjectionScript();
+
+class NXInputWebEngineView : public QWebEngineView {
+public:
+ explicit NXInputWebEngineView(QWidget* parent = nullptr);
+
+protected:
+ void keyPressEvent(QKeyEvent* event) override;
+ void keyReleaseEvent(QKeyEvent* event) override;
+};
+
+#endif
+
+class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
+ Q_OBJECT
+
+public:
+ explicit QtWebBrowser(GMainWindow& main_window);
+ ~QtWebBrowser() override;
+
+ void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) override;
+
+signals:
+ void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
+
+private:
+ void MainWindowUnpackRomFS();
+ void MainWindowFinishedBrowsing();
+
+ std::function<void()> unpack_romfs_callback;
+ std::function<void()> finished_callback;
+};
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 165d70e9c..ddf4cf552 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -419,13 +419,21 @@ void Config::ReadValues() {
Settings::values.language_index = qt_config->value("language_index", 1).toInt();
- const auto enabled = qt_config->value("rng_seed_enabled", false).toBool();
- if (enabled) {
+ const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool();
+ if (rng_seed_enabled) {
Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong();
} else {
Settings::values.rng_seed = std::nullopt;
}
+ const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool();
+ if (custom_rtc_enabled) {
+ Settings::values.custom_rtc =
+ std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong());
+ } else {
+ Settings::values.custom_rtc = std::nullopt;
+ }
+
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
@@ -653,6 +661,11 @@ void Config::SaveValues() {
qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value());
qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0));
+ qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value());
+ qt_config->setValue("custom_rtc",
+ QVariant::fromValue<long long>(
+ Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()));
+
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index ce833b6c8..3f03f0b77 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -52,6 +52,11 @@
<string>System</string>
</attribute>
</widget>
+ <widget class="ConfigureProfileManager" name="profileManagerTab">
+ <attribute name="title">
+ <string>Profiles</string>
+ </attribute>
+ </widget>
<widget class="ConfigureInputSimple" name="inputTab">
<attribute name="title">
<string>Input</string>
@@ -104,6 +109,12 @@
<container>1</container>
</customwidget>
<customwidget>
+ <class>ConfigureProfileManager</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_profile_manager.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
<class>ConfigureAudio</class>
<extends>QWidget</extends>
<header>configuration/configure_audio.h</header>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 90d7c6372..d802443d0 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -32,6 +32,7 @@ void ConfigureDialog::applyConfiguration() {
ui->generalTab->applyConfiguration();
ui->gameListTab->applyConfiguration();
ui->systemTab->applyConfiguration();
+ ui->profileManagerTab->applyConfiguration();
ui->inputTab->applyConfiguration();
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
@@ -43,7 +44,7 @@ void ConfigureDialog::applyConfiguration() {
void ConfigureDialog::PopulateSelectionList() {
const std::array<std::pair<QString, QStringList>, 4> items{
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
- {tr("System"), {tr("System"), tr("Audio")}},
+ {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
{tr("Graphics"), {tr("Graphics")}},
{tr("Controls"), {tr("Input")}}}};
@@ -60,11 +61,15 @@ void ConfigureDialog::UpdateVisibleTabs() {
if (items.isEmpty())
return;
- const std::map<QString, QWidget*> widgets = {
- {tr("General"), ui->generalTab}, {tr("System"), ui->systemTab},
- {tr("Input"), ui->inputTab}, {tr("Graphics"), ui->graphicsTab},
- {tr("Audio"), ui->audioTab}, {tr("Debug"), ui->debugTab},
- {tr("Web"), ui->webTab}, {tr("Game List"), ui->gameListTab}};
+ const std::map<QString, QWidget*> widgets = {{tr("General"), ui->generalTab},
+ {tr("System"), ui->systemTab},
+ {tr("Profiles"), ui->profileManagerTab},
+ {tr("Input"), ui->inputTab},
+ {tr("Graphics"), ui->graphicsTab},
+ {tr("Audio"), ui->audioTab},
+ {tr("Debug"), ui->debugTab},
+ {tr("Web"), ui->webTab},
+ {tr("Game List"), ui->gameListTab}};
ui->tabWidget->clear();
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
new file mode 100644
index 000000000..41663e39a
--- /dev/null
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -0,0 +1,300 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <QFileDialog>
+#include <QGraphicsItem>
+#include <QGraphicsScene>
+#include <QHeaderView>
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include <QTreeView>
+#include <QVBoxLayout>
+#include "common/assert.h"
+#include "common/file_util.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/settings.h"
+#include "ui_configure_profile_manager.h"
+#include "yuzu/configuration/configure_profile_manager.h"
+#include "yuzu/util/limitable_input_dialog.h"
+
+namespace {
+// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
+constexpr std::array<u8, 107> backup_jpeg{
+ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
+ 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
+ 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
+ 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
+ 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
+};
+
+QString GetImagePath(Service::Account::UUID uuid) {
+ const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
+ return QString::fromStdString(path);
+}
+
+QString GetAccountUsername(const Service::Account::ProfileManager& manager,
+ Service::Account::UUID uuid) {
+ Service::Account::ProfileBase profile;
+ if (!manager.GetProfileBase(uuid, profile)) {
+ return {};
+ }
+
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+ return QString::fromStdString(text);
+}
+
+QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
+ return ConfigureProfileManager::tr("%1\n%2",
+ "%1 is the profile username, %2 is the formatted UUID (e.g. "
+ "00112233-4455-6677-8899-AABBCCDDEEFF))")
+ .arg(username, QString::fromStdString(uuid.FormatSwitch()));
+}
+
+QPixmap GetIcon(Service::Account::UUID uuid) {
+ QPixmap icon{GetImagePath(uuid)};
+
+ if (!icon) {
+ icon.fill(Qt::black);
+ icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
+ }
+
+ return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+}
+
+QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
+ return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"),
+ description_text, 1,
+ static_cast<int>(Service::Account::profile_username_size));
+}
+} // Anonymous namespace
+
+ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
+ : QWidget(parent), ui(new Ui::ConfigureProfileManager),
+ profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
+ ui->setupUi(this);
+
+ layout = new QVBoxLayout;
+ tree_view = new QTreeView;
+ item_model = new QStandardItemModel(tree_view);
+ tree_view->setModel(item_model);
+
+ tree_view->setAlternatingRowColors(true);
+ tree_view->setSelectionMode(QHeaderView::SingleSelection);
+ tree_view->setSelectionBehavior(QHeaderView::SelectRows);
+ tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setSortingEnabled(true);
+ tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
+ tree_view->setUniformRowHeights(true);
+ tree_view->setIconSize({64, 64});
+ tree_view->setContextMenuPolicy(Qt::NoContextMenu);
+
+ item_model->insertColumns(0, 1);
+ item_model->setHeaderData(0, Qt::Horizontal, "Users");
+
+ // We must register all custom types with the Qt Automoc system so that we are able to use it
+ // with signals/slots. In this case, QList falls under the umbrells of custom types.
+ qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
+
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+ layout->addWidget(tree_view);
+
+ ui->scrollArea->setLayout(layout);
+
+ connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser);
+
+ connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser);
+ connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser);
+ connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser);
+ connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage);
+
+ scene = new QGraphicsScene;
+ ui->current_user_icon->setScene(scene);
+
+ this->setConfiguration();
+}
+
+ConfigureProfileManager::~ConfigureProfileManager() = default;
+
+void ConfigureProfileManager::setConfiguration() {
+ enabled = !Core::System::GetInstance().IsPoweredOn();
+ item_model->removeRows(0, item_model->rowCount());
+ list_items.clear();
+
+ PopulateUserList();
+ UpdateCurrentUser();
+}
+
+void ConfigureProfileManager::PopulateUserList() {
+ const auto& profiles = profile_manager->GetAllUsers();
+ for (const auto& user : profiles) {
+ Service::Account::ProfileBase profile;
+ if (!profile_manager->GetProfileBase(user, profile))
+ continue;
+
+ const auto username = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+
+ list_items.push_back(QList<QStandardItem*>{new QStandardItem{
+ GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
+ }
+
+ for (const auto& item : list_items)
+ item_model->appendRow(item);
+}
+
+void ConfigureProfileManager::UpdateCurrentUser() {
+ ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
+
+ const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
+ ASSERT(current_user);
+ const auto username = GetAccountUsername(*profile_manager, *current_user);
+
+ scene->clear();
+ scene->addPixmap(
+ GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ ui->current_user_username->setText(username);
+}
+
+void ConfigureProfileManager::applyConfiguration() {
+ if (!enabled)
+ return;
+
+ Settings::Apply();
+}
+
+void ConfigureProfileManager::SelectUser(const QModelIndex& index) {
+ Settings::values.current_user =
+ std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));
+
+ UpdateCurrentUser();
+
+ ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
+ ui->pm_rename->setEnabled(true);
+ ui->pm_set_image->setEnabled(true);
+}
+
+void ConfigureProfileManager::AddUser() {
+ const auto username =
+ GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
+ if (username.isEmpty()) {
+ return;
+ }
+
+ const auto uuid = Service::Account::UUID::Generate();
+ profile_manager->CreateNewUser(uuid, username.toStdString());
+
+ item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
+}
+
+void ConfigureProfileManager::RenameUser() {
+ const auto user = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(user);
+ ASSERT(uuid);
+
+ Service::Account::ProfileBase profile;
+ if (!profile_manager->GetProfileBase(*uuid, profile))
+ return;
+
+ const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
+ if (new_username.isEmpty()) {
+ return;
+ }
+
+ const auto username_std = new_username.toStdString();
+ std::fill(profile.username.begin(), profile.username.end(), '\0');
+ std::copy(username_std.begin(), username_std.end(), profile.username.begin());
+
+ profile_manager->SetProfileBase(*uuid, profile);
+
+ item_model->setItem(
+ user, 0,
+ new QStandardItem{GetIcon(*uuid),
+ FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
+ UpdateCurrentUser();
+}
+
+void ConfigureProfileManager::DeleteUser() {
+ const auto index = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(index);
+ ASSERT(uuid);
+ const auto username = GetAccountUsername(*profile_manager, *uuid);
+
+ const auto confirm = QMessageBox::question(
+ this, tr("Confirm Delete"),
+ tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
+
+ if (confirm == QMessageBox::No)
+ return;
+
+ if (Settings::values.current_user == tree_view->currentIndex().row())
+ Settings::values.current_user = 0;
+ UpdateCurrentUser();
+
+ if (!profile_manager->RemoveUser(*uuid))
+ return;
+
+ item_model->removeRows(tree_view->currentIndex().row(), 1);
+ tree_view->clearSelection();
+
+ ui->pm_remove->setEnabled(false);
+ ui->pm_rename->setEnabled(false);
+}
+
+void ConfigureProfileManager::SetUserImage() {
+ const auto index = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(index);
+ ASSERT(uuid);
+
+ const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
+ tr("JPEG Images (*.jpg *.jpeg)"));
+
+ if (file.isEmpty()) {
+ return;
+ }
+
+ const auto image_path = GetImagePath(*uuid);
+ if (QFile::exists(image_path) && !QFile::remove(image_path)) {
+ QMessageBox::warning(
+ this, tr("Error deleting image"),
+ tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
+ return;
+ }
+
+ const auto raw_path = QString::fromStdString(
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
+ const QFileInfo raw_info{raw_path};
+ if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
+ QMessageBox::warning(this, tr("Error deleting file"),
+ tr("Unable to delete existing file: %1.").arg(raw_path));
+ return;
+ }
+
+ const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
+ if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
+ QMessageBox::warning(
+ this, tr("Error creating user image directory"),
+ tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
+ return;
+ }
+
+ if (!QFile::copy(file, image_path)) {
+ QMessageBox::warning(this, tr("Error copying user image"),
+ tr("Unable to copy image from %1 to %2").arg(file, image_path));
+ return;
+ }
+
+ const auto username = GetAccountUsername(*profile_manager, *uuid);
+ item_model->setItem(index, 0,
+ new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
+ UpdateCurrentUser();
+}
diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h
new file mode 100644
index 000000000..7fe95a2a8
--- /dev/null
+++ b/src/yuzu/configuration/configure_profile_manager.h
@@ -0,0 +1,57 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include <QList>
+#include <QWidget>
+
+class QGraphicsScene;
+class QStandardItem;
+class QStandardItemModel;
+class QTreeView;
+class QVBoxLayout;
+
+namespace Service::Account {
+class ProfileManager;
+}
+
+namespace Ui {
+class ConfigureProfileManager;
+}
+
+class ConfigureProfileManager : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureProfileManager(QWidget* parent = nullptr);
+ ~ConfigureProfileManager() override;
+
+ void applyConfiguration();
+ void setConfiguration();
+
+private:
+ void PopulateUserList();
+ void UpdateCurrentUser();
+
+ void SelectUser(const QModelIndex& index);
+ void AddUser();
+ void RenameUser();
+ void DeleteUser();
+ void SetUserImage();
+
+ QVBoxLayout* layout;
+ QTreeView* tree_view;
+ QStandardItemModel* item_model;
+ QGraphicsScene* scene;
+
+ std::vector<QList<QStandardItem*>> list_items;
+
+ std::unique_ptr<Ui::ConfigureProfileManager> ui;
+ bool enabled = false;
+
+ std::unique_ptr<Service::Account::ProfileManager> profile_manager;
+};
diff --git a/src/yuzu/configuration/configure_profile_manager.ui b/src/yuzu/configuration/configure_profile_manager.ui
new file mode 100644
index 000000000..dedba4998
--- /dev/null
+++ b/src/yuzu/configuration/configure_profile_manager.ui
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureProfileManager</class>
+ <widget class="QWidget" name="ConfigureProfileManager">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>366</width>
+ <height>483</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="gridGroupBox">
+ <property name="title">
+ <string>Profile Manager</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetNoConstraint</enum>
+ </property>
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Current User</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGraphicsView" name="current_user_icon">
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="interactive">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="current_user_username">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Username</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QPushButton" name="pm_set_image">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Set Image</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <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>
+ <item>
+ <widget class="QPushButton" name="pm_add">
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_rename">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_remove">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_disable_info">
+ <property name="text">
+ <string>Profile management is available only when game is not running.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index ab5d46492..94e27349d 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -15,7 +15,6 @@
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/core.h"
-#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "ui_configure_system.h"
#include "yuzu/configuration/configure_system.h"
@@ -36,64 +35,9 @@ constexpr std::array<int, 12> days_in_month = {{
30,
31,
}};
-
-// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
-constexpr std::array<u8, 107> backup_jpeg{
- 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
- 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
- 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
- 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
- 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
- 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
- 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
-};
-
-QString GetImagePath(Service::Account::UUID uuid) {
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
- return QString::fromStdString(path);
-}
-
-QString GetAccountUsername(const Service::Account::ProfileManager& manager,
- Service::Account::UUID uuid) {
- Service::Account::ProfileBase profile;
- if (!manager.GetProfileBase(uuid, profile)) {
- return {};
- }
-
- const auto text = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
- return QString::fromStdString(text);
-}
-
-QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
- return ConfigureSystem::tr("%1\n%2",
- "%1 is the profile username, %2 is the formatted UUID (e.g. "
- "00112233-4455-6677-8899-AABBCCDDEEFF))")
- .arg(username, QString::fromStdString(uuid.FormatSwitch()));
-}
-
-QPixmap GetIcon(Service::Account::UUID uuid) {
- QPixmap icon{GetImagePath(uuid)};
-
- if (!icon) {
- icon.fill(Qt::black);
- icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
- }
-
- return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-}
-
-QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
- return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"),
- description_text, 1,
- static_cast<int>(Service::Account::profile_username_size));
-}
} // Anonymous namespace
-ConfigureSystem::ConfigureSystem(QWidget* parent)
- : QWidget(parent), ui(new Ui::ConfigureSystem),
- profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
+ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
ui->setupUi(this);
connect(ui->combo_birthmonth,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
@@ -101,50 +45,17 @@ ConfigureSystem::ConfigureSystem(QWidget* parent)
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
- layout = new QVBoxLayout;
- tree_view = new QTreeView;
- item_model = new QStandardItemModel(tree_view);
- tree_view->setModel(item_model);
-
- tree_view->setAlternatingRowColors(true);
- tree_view->setSelectionMode(QHeaderView::SingleSelection);
- tree_view->setSelectionBehavior(QHeaderView::SelectRows);
- tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
- tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
- tree_view->setSortingEnabled(true);
- tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
- tree_view->setUniformRowHeights(true);
- tree_view->setIconSize({64, 64});
- tree_view->setContextMenuPolicy(Qt::NoContextMenu);
-
- item_model->insertColumns(0, 1);
- item_model->setHeaderData(0, Qt::Horizontal, "Users");
-
- // We must register all custom types with the Qt Automoc system so that we are able to use it
- // with signals/slots. In this case, QList falls under the umbrells of custom types.
- qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
-
- layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(0);
- layout->addWidget(tree_view);
-
- ui->scrollArea->setLayout(layout);
-
- connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser);
-
- connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser);
- connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser);
- connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser);
- connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage);
-
connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
ui->rng_seed_edit->setEnabled(checked);
if (!checked)
ui->rng_seed_edit->setText(QStringLiteral("00000000"));
});
- scene = new QGraphicsScene;
- ui->current_user_icon->setScene(scene);
+ connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
+ ui->custom_rtc_edit->setEnabled(checked);
+ if (!checked)
+ ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
+ });
this->setConfiguration();
}
@@ -156,49 +67,19 @@ void ConfigureSystem::setConfiguration() {
ui->combo_language->setCurrentIndex(Settings::values.language_index);
- item_model->removeRows(0, item_model->rowCount());
- list_items.clear();
-
- PopulateUserList();
- UpdateCurrentUser();
-
ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value());
ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value());
const auto rng_seed =
QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper();
ui->rng_seed_edit->setText(rng_seed);
-}
-
-void ConfigureSystem::PopulateUserList() {
- const auto& profiles = profile_manager->GetAllUsers();
- for (const auto& user : profiles) {
- Service::Account::ProfileBase profile;
- if (!profile_manager->GetProfileBase(user, profile))
- continue;
-
- const auto username = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
-
- list_items.push_back(QList<QStandardItem*>{new QStandardItem{
- GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
- }
- for (const auto& item : list_items)
- item_model->appendRow(item);
-}
-
-void ConfigureSystem::UpdateCurrentUser() {
- ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
-
- const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
- ASSERT(current_user);
- const auto username = GetAccountUsername(*profile_manager, *current_user);
+ ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
+ ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
- scene->clear();
- scene->addPixmap(
- GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
- ui->current_user_username->setText(username);
+ const auto rtc_time = Settings::values.custom_rtc.value_or(
+ std::chrono::seconds(QDateTime::currentSecsSinceEpoch()));
+ ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
}
void ConfigureSystem::ReadSystemSettings() {}
@@ -214,6 +95,12 @@ void ConfigureSystem::applyConfiguration() {
else
Settings::values.rng_seed = std::nullopt;
+ if (ui->custom_rtc_checkbox->isChecked())
+ Settings::values.custom_rtc =
+ std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch());
+ else
+ Settings::values.custom_rtc = std::nullopt;
+
Settings::Apply();
}
@@ -256,130 +143,3 @@ void ConfigureSystem::RefreshConsoleID() {
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
-
-void ConfigureSystem::SelectUser(const QModelIndex& index) {
- Settings::values.current_user =
- std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));
-
- UpdateCurrentUser();
-
- ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
- ui->pm_rename->setEnabled(true);
- ui->pm_set_image->setEnabled(true);
-}
-
-void ConfigureSystem::AddUser() {
- const auto username =
- GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
- if (username.isEmpty()) {
- return;
- }
-
- const auto uuid = Service::Account::UUID::Generate();
- profile_manager->CreateNewUser(uuid, username.toStdString());
-
- item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
-}
-
-void ConfigureSystem::RenameUser() {
- const auto user = tree_view->currentIndex().row();
- const auto uuid = profile_manager->GetUser(user);
- ASSERT(uuid);
-
- Service::Account::ProfileBase profile;
- if (!profile_manager->GetProfileBase(*uuid, profile))
- return;
-
- const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
- if (new_username.isEmpty()) {
- return;
- }
-
- const auto username_std = new_username.toStdString();
- std::fill(profile.username.begin(), profile.username.end(), '\0');
- std::copy(username_std.begin(), username_std.end(), profile.username.begin());
-
- profile_manager->SetProfileBase(*uuid, profile);
-
- item_model->setItem(
- user, 0,
- new QStandardItem{GetIcon(*uuid),
- FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
- UpdateCurrentUser();
-}
-
-void ConfigureSystem::DeleteUser() {
- const auto index = tree_view->currentIndex().row();
- const auto uuid = profile_manager->GetUser(index);
- ASSERT(uuid);
- const auto username = GetAccountUsername(*profile_manager, *uuid);
-
- const auto confirm = QMessageBox::question(
- this, tr("Confirm Delete"),
- tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
-
- if (confirm == QMessageBox::No)
- return;
-
- if (Settings::values.current_user == tree_view->currentIndex().row())
- Settings::values.current_user = 0;
- UpdateCurrentUser();
-
- if (!profile_manager->RemoveUser(*uuid))
- return;
-
- item_model->removeRows(tree_view->currentIndex().row(), 1);
- tree_view->clearSelection();
-
- ui->pm_remove->setEnabled(false);
- ui->pm_rename->setEnabled(false);
-}
-
-void ConfigureSystem::SetUserImage() {
- const auto index = tree_view->currentIndex().row();
- const auto uuid = profile_manager->GetUser(index);
- ASSERT(uuid);
-
- const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
- tr("JPEG Images (*.jpg *.jpeg)"));
-
- if (file.isEmpty()) {
- return;
- }
-
- const auto image_path = GetImagePath(*uuid);
- if (QFile::exists(image_path) && !QFile::remove(image_path)) {
- QMessageBox::warning(
- this, tr("Error deleting image"),
- tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
- return;
- }
-
- const auto raw_path = QString::fromStdString(
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
- const QFileInfo raw_info{raw_path};
- if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
- QMessageBox::warning(this, tr("Error deleting file"),
- tr("Unable to delete existing file: %1.").arg(raw_path));
- return;
- }
-
- const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
- if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
- QMessageBox::warning(
- this, tr("Error creating user image directory"),
- tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
- return;
- }
-
- if (!QFile::copy(file, image_path)) {
- QMessageBox::warning(this, tr("Error copying user image"),
- tr("Unable to copy image from %1 to %2").arg(file, image_path));
- return;
- }
-
- const auto username = GetAccountUsername(*profile_manager, *uuid);
- item_model->setItem(index, 0,
- new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
- UpdateCurrentUser();
-}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index 07764e1f7..cf1e54de5 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -9,16 +9,6 @@
#include <QList>
#include <QWidget>
-class QGraphicsScene;
-class QStandardItem;
-class QStandardItemModel;
-class QTreeView;
-class QVBoxLayout;
-
-namespace Service::Account {
-class ProfileManager;
-}
-
namespace Ui {
class ConfigureSystem;
}
@@ -39,21 +29,6 @@ private:
void UpdateBirthdayComboBox(int birthmonth_index);
void RefreshConsoleID();
- void PopulateUserList();
- void UpdateCurrentUser();
- void SelectUser(const QModelIndex& index);
- void AddUser();
- void RenameUser();
- void DeleteUser();
- void SetUserImage();
-
- QVBoxLayout* layout;
- QTreeView* tree_view;
- QStandardItemModel* item_model;
- QGraphicsScene* scene;
-
- std::vector<QList<QStandardItem*>> list_items;
-
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
@@ -61,6 +36,4 @@ private:
int birthday = 0;
int language_index = 0;
int sound_index = 0;
-
- std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index a91580893..073327298 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -22,6 +22,13 @@
<string>System Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_sound">
+ <property name="text">
+ <string>Sound output mode</string>
+ </property>
+ </widget>
+ </item>
<item row="1" column="1">
<widget class="QComboBox" name="combo_language">
<property name="toolTip">
@@ -114,27 +121,6 @@
</item>
</widget>
</item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_console_id">
- <property name="text">
- <string>Console ID:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_sound">
- <property name="text">
- <string>Sound output mode</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_birthday">
- <property name="text">
- <string>Birthday</string>
- </property>
- </widget>
- </item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_birthday2">
<item>
@@ -206,6 +192,20 @@
</item>
</layout>
</item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_console_id">
+ <property name="text">
+ <string>Console ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_birthday">
+ <property name="text">
+ <string>Birthday</string>
+ </property>
+ </widget>
+ </item>
<item row="3" column="1">
<widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy">
@@ -241,21 +241,21 @@
</item>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_language">
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="rng_seed_checkbox">
<property name="text">
- <string>Language</string>
+ <string>RNG Seed</string>
</property>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="rng_seed_checkbox">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_language">
<property name="text">
- <string>RNG Seed</string>
+ <string>Language</string>
</property>
</widget>
</item>
- <item row="4" column="1">
+ <item row="5" column="1">
<widget class="QLineEdit" name="rng_seed_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@@ -276,147 +276,44 @@
</property>
</widget>
</item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="gridGroupBox">
- <property name="title">
- <string>Profile Manager</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <property name="sizeConstraint">
- <enum>QLayout::SetNoConstraint</enum>
- </property>
- <item row="0" column="0">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Current User</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QGraphicsView" name="current_user_icon">
- <property name="minimumSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="interactive">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="current_user_username">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Username</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <widget class="QScrollArea" name="scrollArea">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="custom_rtc_checkbox">
+ <property name="text">
+ <string>Custom RTC</string>
</property>
- <property name="frameShape">
- <enum>QFrame::StyledPanel</enum>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QDateTimeEdit" name="custom_rtc_edit">
+ <property name="minimumDate">
+ <date>
+ <year>1970</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
</property>
- <property name="widgetResizable">
- <bool>false</bool>
+ <property name="displayFormat">
+ <string>d MMM yyyy h:mm:ss AP</string>
</property>
</widget>
</item>
- <item row="2" column="0">
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="QPushButton" name="pm_set_image">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Set Image</string>
- </property>
- </widget>
- </item>
- <item>
- <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>
- <item>
- <widget class="QPushButton" name="pm_add">
- <property name="text">
- <string>Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="pm_rename">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Rename</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="pm_remove">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Remove</string>
- </property>
- </widget>
- </item>
- </layout>
- </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>
+ <item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>System settings are available only when game is not running.</string>
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index 3c2ccb76f..18566d028 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -89,12 +89,11 @@ void ConfigureWeb::OnLoginChanged() {
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);
- }));
+ ui->button_verify_login->setText(tr("Verifying..."));
+ verify_watcher.setFuture(QtConcurrent::run([username = ui->edit_username->text().toStdString(),
+ token = ui->edit_token->text().toStdString()] {
+ return Core::VerifyLogin(username, token);
+ }));
}
void ConfigureWeb::OnLoginVerified() {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 8e9524fd6..c0e3c5fa9 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -376,7 +376,7 @@ void GameList::LoadCompatibilityList() {
QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8());
QJsonArray arr = json.array();
- for (const QJsonValueRef& value : arr) {
+ for (const QJsonValueRef value : arr) {
QJsonObject game = value.toObject();
if (game.contains("compatibility") && game["compatibility"].isDouble()) {
@@ -384,7 +384,7 @@ void GameList::LoadCompatibilityList() {
QString directory = game["directory"].toString();
QJsonArray ids = game["releases"].toArray();
- for (const QJsonValueRef& id_ref : ids) {
+ for (const QJsonValueRef id_ref : ids) {
QJsonObject id_object = id_ref.toObject();
QString id = id_object["id"].toString();
compatibility_list.emplace(
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1d5a2b51a..f564de994 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,11 +10,14 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
+#include "applets/web_browser.h"
#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -96,6 +99,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/discord_impl.h"
#endif
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineView>
+#endif
+
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
@@ -252,6 +263,144 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
emit SoftwareKeyboardFinishedCheckDialog();
}
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
+ NXInputWebEngineView web_browser_view(this);
+
+ // Scope to contain the QProgressDialog for initalization
+ {
+ QProgressDialog progress(this);
+ progress.setMinimumDuration(200);
+ progress.setLabelText(tr("Loading Web Applet..."));
+ progress.setRange(0, 4);
+ progress.setValue(0);
+ progress.show();
+
+ auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); });
+
+ while (!future.isFinished())
+ QApplication::processEvents();
+
+ progress.setValue(1);
+
+ // Load the special shim script to handle input and exit.
+ QWebEngineScript nx_shim;
+ nx_shim.setSourceCode(GetNXShimInjectionScript());
+ nx_shim.setWorldId(QWebEngineScript::MainWorld);
+ nx_shim.setName("nx_inject.js");
+ nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation);
+ nx_shim.setRunsOnSubFrames(true);
+ web_browser_view.page()->profile()->scripts()->insert(nx_shim);
+
+ web_browser_view.load(
+ QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() +
+ QString::fromStdString(std::string(additional_args))));
+
+ progress.setValue(2);
+
+ render_window->hide();
+ web_browser_view.setFocus();
+
+ const auto& layout = render_window->GetFramebufferLayout();
+ web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight());
+ web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height());
+ web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) /
+ Layout::ScreenUndocked::Width);
+ web_browser_view.settings()->setAttribute(
+ QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
+
+ web_browser_view.show();
+
+ progress.setValue(3);
+
+ QApplication::processEvents();
+
+ progress.setValue(4);
+ }
+
+ bool finished = false;
+ QAction* exit_action = new QAction(tr("Exit Web Applet"), this);
+ connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; });
+ ui.menubar->addAction(exit_action);
+
+ auto& npad =
+ Core::System::GetInstance()
+ .ServiceManager()
+ .GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ const auto fire_js_keypress = [&web_browser_view](u32 key_code) {
+ web_browser_view.page()->runJavaScript(
+ QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));")
+ .arg(QString::fromStdString(std::to_string(key_code))));
+ };
+
+ bool running_exit_check = false;
+ while (!finished) {
+ QApplication::processEvents();
+
+ if (!running_exit_check) {
+ web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"),
+ [&](const QVariant& res) {
+ running_exit_check = false;
+ if (res.toBool())
+ finished = true;
+ });
+ running_exit_check = true;
+ }
+
+ const auto input = npad.GetAndResetPressState();
+ for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ if ((input & (1 << i)) != 0) {
+ LOG_DEBUG(Frontend, "firing input for button id={:02X}", i);
+ web_browser_view.page()->runJavaScript(
+ QStringLiteral("yuzu_key_callbacks[%1]();").arg(i));
+ }
+ }
+
+ if (input & 0x00888000) // RStick Down | LStick Down | DPad Down
+ fire_js_keypress(40); // Down Arrow Key
+ else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right
+ fire_js_keypress(39); // Right Arrow Key
+ else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up
+ fire_js_keypress(38); // Up Arrow Key
+ else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left
+ fire_js_keypress(37); // Left Arrow Key
+ else if (input & 0x00000001) // A Button
+ fire_js_keypress(13); // Enter Key
+ }
+
+ web_browser_view.hide();
+ render_window->show();
+ render_window->setFocus();
+ ui.menubar->removeAction(exit_action);
+
+ // Needed to update render window focus/show and remove menubar action
+ QApplication::processEvents();
+ emit WebBrowserFinishedBrowsing();
+}
+
+#else
+
+void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
+ QMessageBox::warning(
+ this, tr("Web Applet"),
+ tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot "
+ "properly display the game manual or web page requested."),
+ QMessageBox::Ok, QMessageBox::Ok);
+
+ LOG_INFO(Frontend,
+ "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at "
+ "'{}' with arguments '{}'!",
+ filename, additional_args);
+
+ emit WebBrowserFinishedBrowsing();
+}
+
+#endif
+
void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui.action_Report_Compatibility->setVisible(true);
@@ -612,6 +761,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
+ system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this));
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
@@ -1325,6 +1475,7 @@ void GMainWindow::OnStartGame() {
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
+ qRegisterMetaType<std::string_view>("std::string_view");
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index d560bf75b..2d705ad54 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -13,6 +13,7 @@
#include "common/common_types.h"
#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "ui_main.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
@@ -26,6 +27,7 @@ class GraphicsSurfaceWidget;
class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
+class QLabel;
class WaitTreeWidget;
enum class GameListOpenTarget;
@@ -103,11 +105,16 @@ signals:
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
+ void WebBrowserUnpackRomFS();
+ void WebBrowserFinishedBrowsing();
+
public slots:
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
+ void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
+
private:
void InitializeWidgets();
void InitializeDebugWidgets();