Merge pull request #1781 from DarkLordZach/applet-profile-select

am: Implement HLE profile selector applet
This commit is contained in:
bunnei 2018-12-23 14:35:13 -05:00 committed by GitHub
commit f95f6c7d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 466 additions and 0 deletions

View File

@ -83,6 +83,8 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
frontend/applets/profile_select.cpp
frontend/applets/profile_select.h
frontend/applets/software_keyboard.cpp
frontend/applets/software_keyboard.h
frontend/emu_window.cpp
@ -162,6 +164,8 @@ add_library(core STATIC
hle/service/am/applet_oe.h
hle/service/am/applets/applets.cpp
hle/service/am/applets/applets.h
hle/service/am/applets/profile_select.cpp
hle/service/am/applets/profile_select.h
hle/service/am/applets/software_keyboard.cpp
hle/service/am/applets/software_keyboard.h
hle/service/am/applets/stub_applet.cpp

View File

@ -99,6 +99,8 @@ struct System::Impl {
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
/// Create default implementations of applets if one is not provided.
if (profile_selector == nullptr)
profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
if (software_keyboard == nullptr)
software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
@ -229,6 +231,7 @@ struct System::Impl {
bool is_powered_on = false;
/// Frontend applets
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
/// Service manager
@ -424,6 +427,14 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
return impl->virtual_filesystem;
}
void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) {
impl->profile_selector = std::move(applet);
}
const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const {
return *impl->profile_selector;
}
void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) {
impl->software_keyboard = std::move(applet);
}

View File

@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
#include "frontend/applets/profile_select.h"
namespace Core::Frontend {
class EmuWindow;
@ -241,6 +242,10 @@ public:
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet);
const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const;
void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet);
const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;

View File

@ -0,0 +1,19 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/frontend/applets/profile_select.h"
#include "core/settings.h"
namespace Core::Frontend {
ProfileSelectApplet::~ProfileSelectApplet() = default;
void DefaultProfileSelectApplet::SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const {
Service::Account::ProfileManager manager;
callback(manager.GetUser(Settings::values.current_user).value_or(Service::Account::UUID{}));
LOG_INFO(Service_ACC, "called, selecting current user instead of prompting...");
}
} // namespace Core::Frontend

View File

@ -0,0 +1,27 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <optional>
#include "core/hle/service/acc/profile_manager.h"
namespace Core::Frontend {
class ProfileSelectApplet {
public:
virtual ~ProfileSelectApplet();
virtual void SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const = 0;
};
class DefaultProfileSelectApplet final : public ProfileSelectApplet {
public:
void SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
};
} // namespace Core::Frontend

View File

@ -19,6 +19,7 @@
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/applets/profile_select.h"
#include "core/hle/service/am/applets/software_keyboard.h"
#include "core/hle/service/am/applets/stub_applet.h"
#include "core/hle/service/am/idle.h"
@ -39,6 +40,7 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
enum class AppletId : u32 {
ProfileSelect = 0x10,
SoftwareKeyboard = 0x11,
};
@ -775,6 +777,8 @@ ILibraryAppletCreator::~ILibraryAppletCreator() = default;
static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
switch (id) {
case AppletId::ProfileSelect:
return std::make_shared<Applets::ProfileSelect>();
case AppletId::SoftwareKeyboard:
return std::make_shared<Applets::SoftwareKeyboard>();
default:

View File

@ -0,0 +1,77 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/profile_select.h"
namespace Service::AM::Applets {
constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
ProfileSelect::ProfileSelect() = default;
ProfileSelect::~ProfileSelect() = default;
void ProfileSelect::Initialize() {
complete = false;
status = RESULT_SUCCESS;
final_data.clear();
Applet::Initialize();
const auto user_config_storage = broker.PopNormalDataToApplet();
ASSERT(user_config_storage != nullptr);
const auto& user_config = user_config_storage->GetData();
ASSERT(user_config.size() >= sizeof(UserSelectionConfig));
std::memcpy(&config, user_config.data(), sizeof(UserSelectionConfig));
}
bool ProfileSelect::TransactionComplete() const {
return complete;
}
ResultCode ProfileSelect::GetStatus() const {
return status;
}
void ProfileSelect::ExecuteInteractive() {
UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
}
void ProfileSelect::Execute() {
if (complete) {
broker.PushNormalDataFromApplet(IStorage{final_data});
return;
}
const auto& frontend{Core::System::GetInstance().GetProfileSelector()};
frontend.SelectProfile([this](std::optional<Account::UUID> uuid) { SelectionComplete(uuid); });
}
void ProfileSelect::SelectionComplete(std::optional<Account::UUID> uuid) {
UserSelectionOutput output{};
if (uuid.has_value() && uuid->uuid != Account::INVALID_UUID) {
output.result = 0;
output.uuid_selected = uuid->uuid;
} else {
status = ERR_USER_CANCELLED_SELECTION;
output.result = ERR_USER_CANCELLED_SELECTION.raw;
output.uuid_selected = Account::INVALID_UUID;
}
final_data = std::vector<u8>(sizeof(UserSelectionOutput));
std::memcpy(final_data.data(), &output, final_data.size());
broker.PushNormalDataFromApplet(IStorage{final_data});
broker.SignalStateChanged();
}
} // namespace Service::AM::Applets

View File

@ -0,0 +1,50 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_funcs.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
namespace Service::AM::Applets {
struct UserSelectionConfig {
// TODO(DarkLordZach): RE this structure
// It seems to be flags and the like that determine the UI of the applet on the switch... from
// my research this is safe to ignore for now.
INSERT_PADDING_BYTES(0xA0);
};
static_assert(sizeof(UserSelectionConfig) == 0xA0, "UserSelectionConfig has incorrect size.");
struct UserSelectionOutput {
u64 result;
u128 uuid_selected;
};
static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has incorrect size.");
class ProfileSelect final : public Applet {
public:
ProfileSelect();
~ProfileSelect() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void SelectionComplete(std::optional<Account::UUID> uuid);
private:
UserSelectionConfig config;
bool complete = false;
ResultCode status = RESULT_SUCCESS;
std::vector<u8> final_data;
};
} // namespace Service::AM::Applets

View File

@ -7,6 +7,8 @@ add_executable(yuzu
Info.plist
about_dialog.cpp
about_dialog.h
applets/profile_select.cpp
applets/profile_select.h
applets/software_keyboard.cpp
applets/software_keyboard.h
bootmanager.cpp

View File

@ -0,0 +1,168 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/hle/lock.h"
#include "yuzu/applets/profile_select.h"
#include "yuzu/main.h"
// 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 FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
return QtProfileSelectionDialog::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()));
}
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);
}
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);
}
QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
outer_layout = new QVBoxLayout;
instruction_label = new QLabel(tr("Select a user:"));
scroll_area = new QScrollArea;
buttons = new QDialogButtonBox;
buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
buttons->addButton(tr("OK"), QDialogButtonBox::AcceptRole);
connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject);
outer_layout->addWidget(instruction_label);
outer_layout->addWidget(scroll_area);
outer_layout->addWidget(buttons);
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);
scroll_area->setLayout(layout);
connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
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);
setLayout(outer_layout);
setWindowTitle(tr("Profile Selector"));
resize(550, 400);
}
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
void QtProfileSelectionDialog::accept() {
ok = true;
QDialog::accept();
}
void QtProfileSelectionDialog::reject() {
ok = false;
user_index = 0;
QDialog::reject();
}
bool QtProfileSelectionDialog::GetStatus() const {
return ok;
}
u32 QtProfileSelectionDialog::GetIndex() const {
return user_index;
}
void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) {
user_index = index.row();
}
QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
&GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
&QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
}
QtProfileSelector::~QtProfileSelector() = default;
void QtProfileSelector::SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const {
this->callback = std::move(callback);
emit MainWindowSelectProfile();
}
void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) {
// Acquire the HLE mutex
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
callback(uuid);
}

View File

@ -0,0 +1,73 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <QDialog>
#include <QList>
#include "core/frontend/applets/profile_select.h"
class GMainWindow;
class QDialogButtonBox;
class QGraphicsScene;
class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
class QtProfileSelectionDialog final : public QDialog {
Q_OBJECT
public:
explicit QtProfileSelectionDialog(QWidget* parent);
~QtProfileSelectionDialog() override;
void accept() override;
void reject() override;
bool GetStatus() const;
u32 GetIndex() const;
private:
bool ok = false;
u32 user_index = 0;
void SelectUser(const QModelIndex& index);
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
QVBoxLayout* outer_layout;
QLabel* instruction_label;
QScrollArea* scroll_area;
QDialogButtonBox* buttons;
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};
class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
Q_OBJECT
public:
explicit QtProfileSelector(GMainWindow& parent);
~QtProfileSelector() override;
void SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
signals:
void MainWindowSelectProfile() const;
private:
void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid);
mutable std::function<void(std::optional<Service::Account::UUID>)> callback;
};

View File

@ -8,6 +8,7 @@
#include <thread>
// 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 "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
@ -208,6 +209,28 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
void GMainWindow::ProfileSelectorSelectProfile() {
QtProfileSelectionDialog dialog(this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
if (!dialog.GetStatus()) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
}
Service::Account::ProfileManager manager;
const auto uuid = manager.GetUser(dialog.GetIndex());
if (!uuid.has_value()) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
}
emit ProfileSelectorFinishedSelection(uuid);
}
void GMainWindow::SoftwareKeyboardGetText(
const Core::Frontend::SoftwareKeyboardParameters& parameters) {
QtSoftwareKeyboardDialog dialog(this, parameters);
@ -574,6 +597,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetGPUDebugContext(debug_context);
system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};

View File

@ -99,10 +99,12 @@ signals:
// Signal that tells widgets to update icons to use the current theme
void UpdateThemedIcons();
void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid);
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
public slots:
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);