When profile select applet is asking to select a profile (#99)

Co-authored-by: N3xoX1 <n3xox1@nxemu.com>
This commit is contained in:
N3xoX1 2026-07-27 21:34:17 +09:30 committed by GitHub
parent 9270b8f9c3
commit cff490d184
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 817 additions and 69 deletions

View File

@ -0,0 +1,86 @@
.profile-select-body
{
padding: 0.75rem 0.5rem 0.25rem;
min-width: 30rem;
min-height: 18rem;
color: var(--color-ui-text-primary);
font-family: var(--font-ui);
font-size: 0.9rem;
flow: vertical;
height: *;
}
.profile-select-instruction
{
margin: 0 0 0.65rem 0;
color: var(--color-ui-text-primary);
font-size: 0.95rem;
}
.profile-grid
{
flow: horizontal-wrap;
border: 1px solid #55575a;
border-radius: 5px;
background: #1e1e1e;
padding: 0.5rem;
width: 100%;
height: *;
overflow-y: auto;
vertical-align: top;
}
.profile-tile
{
position: relative;
width: 96px;
margin: 0.35rem;
padding: 0.25rem;
border: 2px solid transparent;
border-radius: 5px;
flow: vertical;
horizontal-align: center;
cursor: pointer;
behavior: clickable;
}
.profile-tile:hover
{
background: rgba(255, 255, 255, 0.06);
}
.profile-tile.current
{
background: rgba(61, 174, 233, 0.22);
border: 2px solid #3daee9;
}
.profile-tile.current > .profile-tile-name
{
color: #ffffff;
font-weight: 600;
}
.profile-tile-avatar
{
width: 96px;
height: 96px;
border-radius: 4px;
background: #111111;
overflow: hidden;
foreground-repeat: no-repeat;
foreground-position: 50% 50%;
foreground-size: 100% 100%;
}
.profile-tile-name
{
width: 90px;
margin-top: 0.35rem;
text-align: center;
color: var(--color-ui-text-primary);
font-size: 0.85rem;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -0,0 +1,24 @@
<html window-frame="solid-with-shadow" window-icon="window-icon.png">
<head>
<link href="base.css" rel="stylesheet" type="text/css" />
<link href="dialog.css" rel="stylesheet" type="text/css" />
<link href="profile_select_dialog.css" rel="stylesheet" type="text/css" />
<title>Profile Selector</title>
</head>
<header>
<window-caption role="window-caption"><img src="window-icon-32.png" /><span id="ProfileSelectTitle">Profile Selector</span></window-caption>
<window-buttons>
<window-button role="window-close"></window-button>
</window-buttons>
</header>
<body>
<div class="profile-select-body">
<div id="ProfileSelectInstruction" class="profile-select-instruction">Select a user:</div>
<div id="profileSelectGrid" class="profile-grid"></div>
</div>
<div class="footer">
<button id="profileSelectCancel" role="window-close">Cancel</button>
<button id="profileSelectOk" role="window-ok">OK</button>
</div>
</body>
</html>

View File

@ -24,7 +24,7 @@ typedef struct CabinetParameters
typedef void (CALL *CabinetFinishedFn)(void * user_data, bool success, const char * amiibo_name_utf8);
nxinterface ICabinetFrontendApplet
nxinterface ICabinetApplet
{
virtual void Close() = 0;
virtual void ShowCabinetApplet(void * user_data, CabinetFinishedFn on_finished, const CabinetParameters * parameters) = 0;
@ -52,7 +52,7 @@ typedef struct ControllerHostParameters
typedef void (CALL *ControllerReconfigureFn)(void * user_data, bool ok);
nxinterface IControllerFrontendApplet
nxinterface IControllerApplet
{
virtual void Close() = 0;
virtual void ReconfigureControllers(void * user_data, ControllerReconfigureFn on_complete, const ControllerHostParameters * parameters) = 0;
@ -60,7 +60,7 @@ nxinterface IControllerFrontendApplet
typedef void (CALL *SimpleFinishedFn)(void * user_data);
nxinterface IErrorFrontendApplet
nxinterface IErrorApplet
{
virtual void Close() = 0;
virtual void ShowError(uint32_t result_raw, void * user_data, SimpleFinishedFn finished) const = 0;
@ -68,7 +68,7 @@ nxinterface IErrorFrontendApplet
virtual void ShowCustomErrorText(uint32_t result_raw, const char * dialog_text_utf8, const char * fullscreen_text_utf8, void * user_data, SimpleFinishedFn finished) const = 0;
};
nxinterface IMiiEditFrontendApplet
nxinterface IMiiEditApplet
{
virtual void Close() = 0;
virtual void ShowMiiEdit(void * user_data, SimpleFinishedFn finished) const = 0;
@ -76,7 +76,7 @@ nxinterface IMiiEditFrontendApplet
typedef void (CALL *BoolFinishedFn)(void * user_data, bool ok);
nxinterface IParentalControlsFrontendApplet
nxinterface IParentalControlsApplet
{
virtual void Close() = 0;
virtual void VerifyPIN(void * user_data, BoolFinishedFn finished, bool suspend_future_verification_temporarily) = 0;
@ -85,7 +85,7 @@ nxinterface IParentalControlsFrontendApplet
virtual void ChangePIN(void * user_data, SimpleFinishedFn finished) = 0;
};
nxinterface IPhotoViewerFrontendApplet
nxinterface IPhotoViewerApplet
{
virtual void Close() = 0;
virtual void ShowPhotosForApplication(uint64_t title_id, void * user_data, SimpleFinishedFn finished) const = 0;
@ -146,7 +146,7 @@ typedef struct ProfileSelectHostParameters
typedef void (CALL *ProfileSelectFinishedFn)(void * user_data, bool has_uuid, const uint8_t uuid_bytes[16]);
nxinterface IProfileSelectFrontendApplet
nxinterface IProfileSelectApplet
{
virtual void Close() = 0;
virtual void SelectProfile(void * user_data, ProfileSelectFinishedFn finished, const ProfileSelectHostParameters * parameters) const = 0;
@ -265,7 +265,7 @@ typedef struct InlineTextHostParameters
typedef void (CALL *SwkbdSubmitNormalFn)(void * user_data, uint32_t result_raw, const uint16_t * text_utf16, uint32_t text_utf16_unit_count, bool confirmed);
typedef void (CALL *SwkbdSubmitInlineFn)(void * user_data, uint32_t reply_raw, const uint16_t * text_utf16, uint32_t text_utf16_unit_count, int32_t cursor);
nxinterface ISoftwareKeyboardFrontendApplet
nxinterface ISoftwareKeyboardApplet
{
virtual void Close() = 0;
virtual void InitializeKeyboard(bool is_inline, const KeyboardInitializeParameters * initialize_parameters, void * user_data_normal, SwkbdSubmitNormalFn submit_normal, void * user_data_inline, SwkbdSubmitInlineFn submit_inline) = 0;
@ -290,7 +290,7 @@ enum class WebExitReasonHost : uint32_t
typedef void (CALL *ExtractRomFsFn)(void * user_data);
typedef void (CALL *OpenWebPageFn)(void * user_data, uint32_t exit_reason_raw, const char * last_url_utf8);
nxinterface IWebBrowserFrontendApplet
nxinterface IWebBrowserApplet
{
virtual void Close() = 0;
virtual void OpenLocalWebPage(const char * local_url_utf8, void * extract_user_data, ExtractRomFsFn extract_romfs, void * open_user_data, OpenWebPageFn open_callback) const = 0;
@ -695,7 +695,7 @@ nxinterface IOperatingSystem
virtual PerfStatsResults GetAndResetPerfStats() = 0;
virtual void SetEmulationPaused(bool paused) = 0;
virtual bool IsEmulationPaused() const = 0;
virtual void SetFrontendApplets(ICabinetFrontendApplet * cabinet, IControllerFrontendApplet * controller, IErrorFrontendApplet * error, IMiiEditFrontendApplet * mii_edit, IParentalControlsFrontendApplet * parental_controls, IPhotoViewerFrontendApplet * photo_viewer, IProfileSelectFrontendApplet * profile_select, ISoftwareKeyboardFrontendApplet * software_keyboard, IWebBrowserFrontendApplet * web_browser) = 0;
virtual void SetFrontendApplets(ICabinetApplet * cabinet, IControllerApplet * controller, IErrorApplet * error, IMiiEditApplet * mii_edit, IParentalControlsApplet * parental_controls, IPhotoViewerApplet * photo_viewer, IProfileSelectApplet * profile_select, ISoftwareKeyboardApplet * software_keyboard, IWebBrowserApplet * web_browser) = 0;
virtual void SetPlayerButtonState(uint32_t player_index, uint32_t button_ordinal, bool pressed) = 0;
virtual void SetPlayerAnalogState(uint32_t player_index, uint32_t stick_index, float x, float y) = 0;
virtual uint32_t GetProfileCount() const = 0;

View File

@ -6,7 +6,7 @@
#include <nxemu-module-spec/operating_system.h>
class DefaultCabinetApplet final :
public ICabinetFrontendApplet
public ICabinetApplet
{
public:
void Close() override;

View File

@ -11,7 +11,7 @@ class HIDCore;
}
class DefaultControllerApplet final :
public IControllerFrontendApplet
public IControllerApplet
{
public:
explicit DefaultControllerApplet(Core::HID::HIDCore & hid_core_);

View File

@ -22,7 +22,7 @@ public:
};
class DefaultErrorApplet final :
public IErrorFrontendApplet
public IErrorApplet
{
public:
void Close() override;

View File

@ -7,7 +7,7 @@
#include <nxemu-module-spec/operating_system.h>
class DefaultParentalControlsApplet final :
public IParentalControlsFrontendApplet
public IParentalControlsApplet
{
public:
void Close() override;
@ -18,7 +18,7 @@ public:
};
class DefaultPhotoViewerApplet final :
public IPhotoViewerFrontendApplet
public IPhotoViewerApplet
{
public:
void Close() override;

View File

@ -5,7 +5,7 @@
#include <nxemu-module-spec/operating_system.h>
class DefaultMiiEditApplet final :
public IMiiEditFrontendApplet
public IMiiEditApplet
{
public:
void Close() override;

View File

@ -19,7 +19,7 @@ struct ProfileSelectParameters
};
class DefaultProfileSelectApplet final :
public IProfileSelectFrontendApplet
public IProfileSelectApplet
{
public:
void Close() override;

View File

@ -14,7 +14,7 @@ struct InlineTextParameters
};
class DefaultSoftwareKeyboardApplet final :
public ISoftwareKeyboardFrontendApplet
public ISoftwareKeyboardApplet
{
public:
void Close() override;

View File

@ -5,7 +5,7 @@
#include <nxemu-module-spec/operating_system.h>
class DefaultWebBrowserApplet final :
public IWebBrowserFrontendApplet
public IWebBrowserApplet
{
public:
void Close() override;

View File

@ -60,7 +60,7 @@ static ControllerHostParameters ConvertToFrontendParameters(
return params;
}
Controller::Controller(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IControllerFrontendApplet & frontend_) :
Controller::Controller(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IControllerApplet & frontend_) :
FrontendApplet{system_, applet_, applet_mode_},
frontend{frontend_}
{

View File

@ -121,7 +121,7 @@ static_assert(sizeof(ControllerSupportResultInfo) == 0xC,
class Controller final : public FrontendApplet {
public:
explicit Controller(Core::System& system_, std::shared_ptr<Applet> applet_,
LibraryAppletMode applet_mode_, IControllerFrontendApplet & frontend_);
LibraryAppletMode applet_mode_, IControllerApplet & frontend_);
~Controller() override;
void Initialize() override;
@ -136,7 +136,7 @@ public:
private:
static void OnConfigurationComplete(void * user_data, bool is_success);
[[maybe_unused]] IControllerFrontendApplet & frontend;
[[maybe_unused]] IControllerApplet & frontend;
ControllerAppletVersion controller_applet_version;
ControllerSupportArgPrivate controller_private_arg;
ControllerSupportArgOld controller_user_arg_old;

View File

@ -15,7 +15,7 @@
namespace Service::AM::Frontend
{
MiiEdit::MiiEdit(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IMiiEditFrontendApplet & frontend_) :
MiiEdit::MiiEdit(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IMiiEditApplet & frontend_) :
FrontendApplet{system_, applet_, applet_mode_},
frontend{frontend_}
{

View File

@ -24,7 +24,7 @@ namespace Service::AM::Frontend
class MiiEdit final : public FrontendApplet
{
public:
explicit MiiEdit(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IMiiEditFrontendApplet & frontend_);
explicit MiiEdit(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IMiiEditApplet & frontend_);
~MiiEdit() override;
void Initialize() override;
@ -39,7 +39,7 @@ public:
void MiiEditOutputForCharInfoEditing(MiiEditResult result, const MiiEditCharInfo & char_info);
private:
IMiiEditFrontendApplet & frontend;
IMiiEditApplet & frontend;
MiiEditAppletInputCommon applet_input_common{};
MiiEditAppletInputV3 applet_input_v3{};

View File

@ -4,6 +4,7 @@
#include <cstring>
#include "core/core.h"
#include "applets/profile_select.h"
#include "core/hle/service/acc/errors.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/frontend/applet_profile_select.h"
@ -11,11 +12,39 @@
#include "yuzu_common/string_util.h"
#include "yuzu_common/yuzu_assert.h"
namespace
{
ProfileSelectHostParameters CreateHostParameters(Service::AM::Frontend::UiMode mode, const std::array<Common::UUID, 8> & invalid_uid_list, const Service::AM::Frontend::UiSettingsDisplayOptions & display_options, Service::AM::Frontend::UserSelectionPurpose purpose)
{
ProfileSelectHostParameters parameters{};
parameters.mode = (ProfileUiMode)mode;
for (size_t i = 0; i < invalid_uid_list.size(); ++i)
{
std::memcpy(parameters.invalid_uid_list[i], invalid_uid_list[i].uuid.data(), sizeof(parameters.invalid_uid_list[i]));
}
parameters.display_options = {
.is_network_service_account_required = display_options.is_network_service_account_required,
.is_skip_enabled = display_options.is_skip_enabled,
.is_system_or_launcher = display_options.is_system_or_launcher,
.is_registration_permitted = display_options.is_registration_permitted,
.show_skip_button = display_options.show_skip_button,
.additional_select = display_options.additional_select,
.show_user_selector = display_options.show_user_selector,
.is_unqualified_user_selectable = display_options.is_unqualified_user_selectable,
};
parameters.purpose = (UserSelectionPurposeHost)purpose;
return parameters;
}
} // namespace
namespace Service::AM::Frontend
{
ProfileSelect::ProfileSelect(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_) :
FrontendApplet{system_, applet_, applet_mode_}
ProfileSelect::ProfileSelect(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IProfileSelectApplet & frontend_) :
FrontendApplet{system_, applet_, applet_mode_},
frontend{frontend_}
{
}
@ -65,7 +94,30 @@ void ProfileSelect::ExecuteInteractive()
void ProfileSelect::Execute()
{
UNIMPLEMENTED();
if (complete)
{
PushOutData(std::make_shared<IStorage>(system, std::move(final_data)));
Exit();
return;
}
ProfileSelectHostParameters parameters{};
switch (profile_select_version)
{
case ProfileSelectAppletVersion::Version1:
parameters = CreateHostParameters(config_old.mode, config_old.invalid_uid_list, config_old.display_options, UserSelectionPurpose::General);
break;
case ProfileSelectAppletVersion::Version2:
case ProfileSelectAppletVersion::Version3:
parameters = CreateHostParameters(config.mode, config.invalid_uid_list, config.display_options, config.purpose);
break;
default:
UNIMPLEMENTED_MSG("Unknown profile_select_version = {}", profile_select_version);
break;
}
frontend.SelectProfile(this, OnProfileSelected, &parameters);
}
void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid)
@ -93,8 +145,23 @@ void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid)
Result ProfileSelect::RequestExit()
{
UNIMPLEMENTED();
frontend.Close();
R_SUCCEED();
}
void CALL ProfileSelect::OnProfileSelected(void * user_data, bool has_uuid, const uint8_t uuid_bytes[16])
{
ProfileSelect * const self = (ProfileSelect *)user_data;
if (has_uuid)
{
std::array<u8, 16> uuid{};
std::memcpy(uuid.data(), uuid_bytes, uuid.size());
self->SelectionComplete(Common::UUID{uuid});
}
else
{
self->SelectionComplete(std::nullopt);
}
}
} // namespace Service::AM::Frontend

View File

@ -5,25 +5,29 @@
#include <vector>
#include "yuzu_common/common_funcs.h"
#include "yuzu_common/uuid.h"
#include "core/hle/result.h"
#include "core/hle/service/am/frontend/applets.h"
#include "yuzu_common/common_funcs.h"
#include "yuzu_common/uuid.h"
namespace Core {
namespace Core
{
class System;
}
namespace Service::AM::Frontend {
namespace Service::AM::Frontend
{
enum class ProfileSelectAppletVersion : u32 {
enum class ProfileSelectAppletVersion : u32
{
Version1 = 0x1, // 1.0.0+
Version2 = 0x10000, // 2.0.0+
Version3 = 0x20000, // 6.0.0+
};
// This is nn::account::UiMode
enum class UiMode {
enum class UiMode
{
UserSelector,
UserCreator,
EnsureNetworkServiceAccountAvailable,
@ -41,7 +45,8 @@ enum class UiMode {
};
// This is nn::account::UserSelectionPurpose
enum class UserSelectionPurpose {
enum class UserSelectionPurpose
{
General,
GameCardRegistration,
EShopLaunch,
@ -55,14 +60,16 @@ enum class UserSelectionPurpose {
};
// This is nn::account::NintendoAccountStartupDialogType
enum class NintendoAccountStartupDialogType {
enum class NintendoAccountStartupDialogType
{
LoginAndCreate,
Login,
Create,
};
// This is nn::account::UserSelectionSettingsForSystemService
struct UserSelectionSettingsForSystemService {
struct UserSelectionSettingsForSystemService
{
UserSelectionPurpose purpose;
bool enable_user_creation;
INSERT_PADDING_BYTES(0x3);
@ -70,7 +77,8 @@ struct UserSelectionSettingsForSystemService {
static_assert(sizeof(UserSelectionSettingsForSystemService) == 0x8,
"UserSelectionSettingsForSystemService has incorrect size.");
struct UiSettingsDisplayOptions {
struct UiSettingsDisplayOptions
{
bool is_network_service_account_required;
bool is_skip_enabled;
bool is_system_or_launcher;
@ -80,10 +88,10 @@ struct UiSettingsDisplayOptions {
bool show_user_selector;
bool is_unqualified_user_selectable;
};
static_assert(sizeof(UiSettingsDisplayOptions) == 0x8,
"UiSettingsDisplayOptions has incorrect size.");
static_assert(sizeof(UiSettingsDisplayOptions) == 0x8, "UiSettingsDisplayOptions has incorrect size.");
struct UiSettingsV1 {
struct UiSettingsV1
{
UiMode mode;
INSERT_PADDING_BYTES(0x4);
std::array<Common::UUID, 8> invalid_uid_list;
@ -93,7 +101,8 @@ struct UiSettingsV1 {
static_assert(sizeof(UiSettingsV1) == 0x98, "UiSettings has incorrect size.");
// This is nn::account::UiSettings
struct UiSettings {
struct UiSettings
{
UiMode mode;
INSERT_PADDING_BYTES(0x4);
std::array<Common::UUID, 8> invalid_uid_list;
@ -105,16 +114,17 @@ struct UiSettings {
static_assert(sizeof(UiSettings) == 0xA0, "UiSettings has incorrect size.");
// This is nn::account::UiReturnArg
struct UiReturnArg {
struct UiReturnArg
{
u64 result;
Common::UUID uuid_selected;
};
static_assert(sizeof(UiReturnArg) == 0x18, "UiReturnArg has incorrect size.");
class ProfileSelect final : public FrontendApplet {
class ProfileSelect final : public FrontendApplet
{
public:
explicit ProfileSelect(Core::System& system_, std::shared_ptr<Applet> applet_,
LibraryAppletMode applet_mode_);
explicit ProfileSelect(Core::System& system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IProfileSelectApplet& frontend_);
~ProfileSelect() override;
void Initialize() override;
@ -127,6 +137,9 @@ public:
void SelectionComplete(std::optional<Common::UUID> uuid);
private:
static void CALL OnProfileSelected(void* user_data, bool has_uuid, const uint8_t uuid_bytes[16]);
IProfileSelectApplet& frontend;
UiSettings config;
UiSettingsV1 config_old;

View File

@ -76,7 +76,7 @@ void SetReplyBase(std::vector<u8> & reply, SwkbdState state, SwkbdReplyType repl
} // Anonymous namespace
SoftwareKeyboard::SoftwareKeyboard(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, ISoftwareKeyboardFrontendApplet & frontend_) :
SoftwareKeyboard::SoftwareKeyboard(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, ISoftwareKeyboardApplet & frontend_) :
FrontendApplet{system_, applet_, applet_mode_},
frontend{frontend_}
{

View File

@ -21,7 +21,7 @@ class SoftwareKeyboard final :
public FrontendApplet
{
public:
explicit SoftwareKeyboard(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, ISoftwareKeyboardFrontendApplet & frontend_);
explicit SoftwareKeyboard(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, ISoftwareKeyboardApplet & frontend_);
~SoftwareKeyboard() override;
void Initialize() override;
@ -150,7 +150,7 @@ private:
void ReplyChangedStringUtf8V2();
void ReplyMovedCursorUtf8V2();
ISoftwareKeyboardFrontendApplet & frontend;
ISoftwareKeyboardApplet & frontend;
SwkbdAppletVersion swkbd_applet_version;

View File

@ -345,7 +345,7 @@ void ExtractSharedFonts(Core::System & system)
} // namespace
WebBrowser::WebBrowser(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IWebBrowserFrontendApplet & frontend_) :
WebBrowser::WebBrowser(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IWebBrowserApplet & frontend_) :
FrontendApplet{system_, applet_, applet_mode_},
frontend(frontend_)
{

View File

@ -29,7 +29,7 @@ namespace Service::AM::Frontend
class WebBrowser final : public FrontendApplet
{
public:
WebBrowser(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IWebBrowserFrontendApplet & frontend_);
WebBrowser(Core::System & system_, std::shared_ptr<Applet> applet_, LibraryAppletMode applet_mode_, IWebBrowserApplet & frontend_);
~WebBrowser() override;
void Initialize() override;
@ -68,7 +68,7 @@ private:
void ExecuteWifi();
void ExecuteLobby();
IWebBrowserFrontendApplet & frontend;
IWebBrowserApplet & frontend;
bool complete{false};
Result status{ResultSuccess};

View File

@ -108,7 +108,7 @@ FrontendAppletSet::FrontendAppletSet() :
{
}
FrontendAppletSet::FrontendAppletSet(ICabinetFrontendApplet * cabinet_, IControllerFrontendApplet * controller_, IErrorFrontendApplet * error_, IMiiEditFrontendApplet * mii_edit_, IParentalControlsFrontendApplet * parental_controls, IPhotoViewerFrontendApplet * photo_viewer, IProfileSelectFrontendApplet * profile_select_, ISoftwareKeyboardFrontendApplet * software_keyboard_, IWebBrowserFrontendApplet * web_browser_) :
FrontendAppletSet::FrontendAppletSet(ICabinetApplet * cabinet_, IControllerApplet * controller_, IErrorApplet * error_, IMiiEditApplet * mii_edit_, IParentalControlsApplet * parental_controls, IPhotoViewerApplet * photo_viewer, IProfileSelectApplet * profile_select_, ISoftwareKeyboardApplet * software_keyboard_, IWebBrowserApplet * web_browser_) :
cabinet(cabinet_),
controller(controller_),
error(error_),
@ -232,7 +232,7 @@ std::shared_ptr<FrontendApplet> FrontendAppletHolder::GetApplet(std::shared_ptr<
case AppletId::Error:
return std::make_shared<Error>(system, applet, mode);
case AppletId::ProfileSelect:
return std::make_shared<ProfileSelect>(system, applet, mode);
return std::make_shared<ProfileSelect>(system, applet, mode, *frontend.profile_select);
case AppletId::SoftwareKeyboard:
return std::make_shared<SoftwareKeyboard>(system, applet, mode, *frontend.software_keyboard);
case AppletId::MiiEdit:

View File

@ -78,7 +78,7 @@ protected:
struct FrontendAppletSet
{
FrontendAppletSet();
FrontendAppletSet(ICabinetFrontendApplet * cabinet_, IControllerFrontendApplet * controller_, IErrorFrontendApplet * error_, IMiiEditFrontendApplet * mii_edit_, IParentalControlsFrontendApplet * parental_controls, IPhotoViewerFrontendApplet * photo_viewer, IProfileSelectFrontendApplet * profile_select_, ISoftwareKeyboardFrontendApplet * software_keyboard_, IWebBrowserFrontendApplet * web_browser_);
FrontendAppletSet(ICabinetApplet * cabinet_, IControllerApplet * controller_, IErrorApplet * error_, IMiiEditApplet * mii_edit_, IParentalControlsApplet * parental_controls, IPhotoViewerApplet * photo_viewer, IProfileSelectApplet * profile_select_, ISoftwareKeyboardApplet * software_keyboard_, IWebBrowserApplet * web_browser_);
~FrontendAppletSet();
FrontendAppletSet(const FrontendAppletSet &) = delete;
@ -87,15 +87,15 @@ struct FrontendAppletSet
FrontendAppletSet(FrontendAppletSet &&) noexcept;
FrontendAppletSet & operator=(FrontendAppletSet &&) noexcept;
ICabinetFrontendApplet * cabinet;
IControllerFrontendApplet * controller;
IErrorFrontendApplet * error;
IMiiEditFrontendApplet * mii_edit;
IParentalControlsFrontendApplet * parental_controls;
IPhotoViewerFrontendApplet * photo_viewer;
IProfileSelectFrontendApplet * profile_select;
ISoftwareKeyboardFrontendApplet * software_keyboard;
IWebBrowserFrontendApplet * web_browser;
ICabinetApplet * cabinet;
IControllerApplet * controller;
IErrorApplet * error;
IMiiEditApplet * mii_edit;
IParentalControlsApplet * parental_controls;
IPhotoViewerApplet * photo_viewer;
IProfileSelectApplet * profile_select;
ISoftwareKeyboardApplet * software_keyboard;
IWebBrowserApplet * web_browser;
};
class FrontendAppletHolder

View File

@ -421,7 +421,7 @@ bool OSManager::IsEmulationPaused() const
return !m_emuThread->IsRunning();
}
void OSManager::SetFrontendApplets(ICabinetFrontendApplet * cabinet, IControllerFrontendApplet * controller, IErrorFrontendApplet * error, IMiiEditFrontendApplet * mii_edit, IParentalControlsFrontendApplet * parental_controls, IPhotoViewerFrontendApplet * photo_viewer, IProfileSelectFrontendApplet * profile_select, ISoftwareKeyboardFrontendApplet * software_keyboard, IWebBrowserFrontendApplet * web_browser)
void OSManager::SetFrontendApplets(ICabinetApplet * cabinet, IControllerApplet * controller, IErrorApplet * error, IMiiEditApplet * mii_edit, IParentalControlsApplet * parental_controls, IPhotoViewerApplet * photo_viewer, IProfileSelectApplet * profile_select, ISoftwareKeyboardApplet * software_keyboard, IWebBrowserApplet * web_browser)
{
Service::AM::Frontend::FrontendAppletSet applets{};
applets.cabinet = cabinet;

View File

@ -48,7 +48,7 @@ public:
PerfStatsResults GetAndResetPerfStats() override;
void SetEmulationPaused(bool paused) override;
bool IsEmulationPaused() const override;
void SetFrontendApplets(ICabinetFrontendApplet * cabinet, IControllerFrontendApplet * controller, IErrorFrontendApplet * error, IMiiEditFrontendApplet * mii_edit, IParentalControlsFrontendApplet * parental_controls, IPhotoViewerFrontendApplet * photo_viewer, IProfileSelectFrontendApplet * profile_select, ISoftwareKeyboardFrontendApplet * software_keyboard, IWebBrowserFrontendApplet * web_browser) override;
void SetFrontendApplets(ICabinetApplet * cabinet, IControllerApplet * controller, IErrorApplet * error, IMiiEditApplet * mii_edit, IParentalControlsApplet * parental_controls, IPhotoViewerApplet * photo_viewer, IProfileSelectApplet * profile_select, ISoftwareKeyboardApplet * software_keyboard, IWebBrowserApplet * web_browser) override;
void SetPlayerButtonState(uint32_t player_index, uint32_t button_ordinal, bool pressed) override;
void SetPlayerAnalogState(uint32_t player_index, uint32_t stick_index, float x, float y) override;
uint32_t GetProfileCount() const override;

View File

@ -0,0 +1,481 @@
#include "profile_select.h"
#include "user_interface/html_utils.h"
#include <common/std_string.h>
#include <nxemu-core/modules/system_modules.h>
ProfileSelectApplet::ProfileSelectApplet() :
m_sciterUI(nullptr),
m_modules(nullptr),
m_parentHwnd(nullptr),
m_window(nullptr),
m_selectedIndex(0),
m_dialogOpen(false),
m_pending(false),
m_userData(nullptr),
m_finished(nullptr),
m_parameters{}
{
}
void ProfileSelectApplet::Attach(ISciterUI & sciterUI, SystemModules & modules, SciterElement rootElement, void * parentHwnd)
{
m_sciterUI = &sciterUI;
m_modules = &modules;
m_rootElement = rootElement;
m_parentHwnd = parentHwnd;
if (m_rootElement.IsValid())
{
m_sciterUI->AttachHandler(m_rootElement, IID_EVENTSINK, (IEventSink *)this);
}
}
void ProfileSelectApplet::Detach()
{
CloseDialog();
if (m_sciterUI != nullptr && m_rootElement.IsValid())
{
m_sciterUI->DetachHandler(m_rootElement, IID_EVENTSINK, (IEventSink *)this);
}
m_sciterUI = nullptr;
m_modules = nullptr;
m_rootElement = {};
m_parentHwnd = nullptr;
std::lock_guard<std::mutex> lock(m_mutex);
m_pending = false;
m_userData = nullptr;
m_finished = nullptr;
m_parameters = {};
}
void ProfileSelectApplet::Close()
{
ProfileSelectFinishedFn finished = nullptr;
void * userData = nullptr;
{
std::lock_guard<std::mutex> lock(m_mutex);
finished = m_finished;
userData = m_userData;
m_pending = false;
m_finished = nullptr;
m_userData = nullptr;
}
CloseDialog();
if (finished != nullptr)
{
uint8_t uuidBytes[HOST_PROFILE_UUID_SIZE]{};
finished(userData, false, uuidBytes);
}
}
void ProfileSelectApplet::SelectProfile(void * user_data, ProfileSelectFinishedFn finished, const ProfileSelectHostParameters * parameters) const
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_userData = user_data;
m_finished = finished;
m_parameters = parameters != nullptr ? *parameters : ProfileSelectHostParameters{};
m_pending = true;
}
if (m_rootElement.IsValid())
{
m_rootElement.PostEvent(EVENT_PROFILE_SELECT);
}
else if (finished != nullptr)
{
uint8_t uuidBytes[HOST_PROFILE_UUID_SIZE]{};
finished(user_data, false, uuidBytes);
}
}
void ProfileSelectApplet::ProcessPendingSelect()
{
bool pending = false;
{
std::lock_guard<std::mutex> lock(m_mutex);
pending = m_pending;
m_pending = false;
}
if (!pending)
{
return;
}
ShowDialog();
}
bool ProfileSelectApplet::OnEvent(SCITER_ELEMENT /*element*/, SCITER_ELEMENT /*source*/, uint32_t event_code, uint64_t /*reason*/)
{
if (event_code == EVENT_PROFILE_SELECT)
{
ProcessPendingSelect();
return true;
}
return false;
}
bool ProfileSelectApplet::OnClick(SCITER_ELEMENT element, SCITER_ELEMENT source, uint32_t /*reason*/)
{
SciterElement clickElem(element);
const std::string elementId = clickElem.GetAttributeByName("id");
if (elementId == "profileSelectOk")
{
AcceptSelection();
return true;
}
else if (elementId == "profileSelectCancel")
{
CancelSelection();
return true;
}
int32_t index = -1;
if (ResolveTileIndex(source, index))
{
SelectIndex(index);
return true;
}
return true;
}
bool ProfileSelectApplet::ResolveTileIndex(SCITER_ELEMENT source, int32_t & index) const
{
SciterElement elem(source);
for (SciterElement walk = elem; walk.IsValid(); walk = walk.GetParent())
{
if (walk.GetAttribute("role") == "profile-tile")
{
const std::string indexAttr = walk.GetAttribute("data-index");
if (indexAttr.empty())
{
return false;
}
index = std::stoi(indexAttr);
return true;
}
}
return false;
}
void ProfileSelectApplet::ShowDialog()
{
if (m_sciterUI == nullptr || m_modules == nullptr || !m_modules->IsValid() || m_dialogOpen)
{
CancelSelection();
return;
}
IOperatingSystem & os = m_modules->Modules().OperatingSystem();
const uint32_t count = os.GetProfileCount();
if (count == 0)
{
CancelSelection();
return;
}
if (count == 1)
{
HostProfileInfo profile{};
if (os.GetProfile(0, &profile))
{
Finish(true, profile.uuid);
}
else
{
CancelSelection();
}
return;
}
enum
{
WINDOW_WIDTH = 560,
WINDOW_HEIGHT = 420,
};
m_selectedIndex = 0;
m_window = nullptr;
if (!m_sciterUI->WindowCreate(m_parentHwnd, "profile_select_dialog.html", 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SUIW_CHILD, m_window))
{
CancelSelection();
return;
}
m_dialogOpen = true;
SciterElement root(m_window->GetRootElement());
if (root.IsValid())
{
ProfileSelectHostParameters parameters{};
{
std::lock_guard<std::mutex> lock(m_mutex);
parameters = m_parameters;
}
SciterElement titleEl(root.GetElementByID("ProfileSelectTitle"));
if (titleEl.IsValid())
{
titleEl.SetText(WindowTitleForMode(parameters.mode));
}
SciterElement instructionEl(root.GetElementByID("ProfileSelectInstruction"));
if (instructionEl.IsValid())
{
instructionEl.SetText(InstructionForPurpose(parameters.purpose));
}
PopulateProfiles();
AttachClickHandler(*m_sciterUI, root.GetElementByID("profileSelectOk"), this);
AttachClickHandler(*m_sciterUI, root.GetElementByID("profileSelectCancel"), this);
}
m_window->CenterWindow();
m_window->RunModal();
m_dialogOpen = false;
m_window = nullptr;
bool needsCancel = false;
{
std::lock_guard<std::mutex> lock(m_mutex);
needsCancel = m_finished != nullptr;
}
if (needsCancel)
{
CancelSelection();
}
}
void ProfileSelectApplet::PopulateProfiles()
{
if (m_window == nullptr || m_sciterUI == nullptr || m_modules == nullptr || !m_modules->IsValid())
{
return;
}
SciterElement root(m_window->GetRootElement());
SciterElement grid = root.IsValid() ? root.GetElementByID("profileSelectGrid") : nullptr;
if (!grid.IsValid())
{
return;
}
grid.Clear();
IOperatingSystem & os = m_modules->Modules().OperatingSystem();
const uint32_t count = os.GetProfileCount();
if (m_selectedIndex < 0 || (uint32_t)m_selectedIndex >= count)
{
m_selectedIndex = 0;
}
std::string html;
std::vector<std::string> imageUris;
imageUris.reserve(count);
for (uint32_t i = 0; i < count; ++i)
{
HostProfileInfo profile{};
if (!os.GetProfile(i, &profile))
{
continue;
}
const bool selected = (int32_t)i == m_selectedIndex;
const std::string imageUri = GetImageFileUrl(profile);
imageUris.push_back(imageUri);
html += stdstr_f(
"<div class=\"profile-tile%s\" role=\"profile-tile\" data-index=\"%u\">"
"<div class=\"profile-tile-avatar\"></div>"
"<div class=\"profile-tile-name\">%s</div>"
"</div>",
selected ? " current" : "",
i,
HtmlEscape(profile.username).c_str());
}
grid.SetHTML((const uint8_t *)html.c_str(), html.size());
m_sciterUI->AttachHandler(grid, IID_ICLICKSINK, (IClickSink *)this);
for (uint32_t i = 0; i < grid.GetChildCount() && i < imageUris.size(); ++i)
{
if (imageUris[i].empty())
{
continue;
}
SciterElement tile = grid.GetChild(i);
if (!tile.IsValid())
{
continue;
}
SciterElement avatar = tile.FindFirst(".profile-tile-avatar");
if (avatar.IsValid())
{
avatar.SetStyleAttribute("foreground-image", ("url(" + imageUris[i] + ")").c_str());
avatar.SetStyleAttribute("foreground-size", "100% 100%");
avatar.SetStyleAttribute("foreground-repeat", "no-repeat");
avatar.SetStyleAttribute("foreground-position", "50% 50%");
}
}
}
void ProfileSelectApplet::SelectIndex(int32_t index)
{
if (index == m_selectedIndex || m_window == nullptr)
{
return;
}
SciterElement root(m_window->GetRootElement());
SciterElement grid = root.IsValid() ? root.GetElementByID("profileSelectGrid") : nullptr;
if (!grid.IsValid())
{
return;
}
SciterElement newTile = grid.FindFirst(stdstr_f("[data-index='%d']", index).c_str());
if (!newTile.IsValid())
{
return;
}
SciterElement oldTile = grid.FindFirst(stdstr_f("[data-index='%d']", m_selectedIndex).c_str());
if (oldTile.IsValid())
{
oldTile.SetAttribute("class", "profile-tile");
}
newTile.SetAttribute("class", "profile-tile current");
m_selectedIndex = index;
}
void ProfileSelectApplet::AcceptSelection()
{
if (m_modules == nullptr || !m_modules->IsValid())
{
CancelSelection();
return;
}
HostProfileInfo profile{};
if (!m_modules->Modules().OperatingSystem().GetProfile((uint32_t)m_selectedIndex, &profile))
{
CancelSelection();
return;
}
const HostProfileInfo selected = profile;
CloseDialog();
Finish(true, selected.uuid);
}
void ProfileSelectApplet::CancelSelection()
{
CloseDialog();
uint8_t uuidBytes[HOST_PROFILE_UUID_SIZE]{};
Finish(false, uuidBytes);
}
void ProfileSelectApplet::CloseDialog()
{
if (m_window == nullptr || m_window->IsClosed())
{
m_window = nullptr;
m_dialogOpen = false;
return;
}
m_dialogOpen = false;
m_window->Destroy();
m_window = nullptr;
}
void ProfileSelectApplet::Finish(bool hasUuid, const uint8_t uuidBytes[HOST_PROFILE_UUID_SIZE])
{
ProfileSelectFinishedFn finished = nullptr;
void * userData = nullptr;
{
std::lock_guard<std::mutex> lock(m_mutex);
finished = m_finished;
userData = m_userData;
m_finished = nullptr;
m_userData = nullptr;
m_pending = false;
}
if (finished != nullptr)
{
finished(userData, hasUuid, uuidBytes);
}
}
std::string ProfileSelectApplet::GetImageFileUrl(const HostProfileInfo & profile) const
{
if (m_modules == nullptr || !m_modules->IsValid())
{
return {};
}
char path[1024] = {};
if (!m_modules->Modules().OperatingSystem().GetProfileImagePath(profile.uuid, path, sizeof(path)) || path[0] == '\0')
{
return {};
}
return ImageDataUriFromFile(path);
}
const char * ProfileSelectApplet::WindowTitleForMode(ProfileUiMode mode)
{
switch (mode)
{
case ProfileUiMode::UserCreator:
case ProfileUiMode::UserCreatorForStarter:
return "Profile Creator";
case ProfileUiMode::UserIconEditor:
return "Profile Icon Editor";
case ProfileUiMode::UserNicknameEditor:
return "Profile Nickname Editor";
default:
return "Profile Selector";
}
}
const char * ProfileSelectApplet::InstructionForPurpose(UserSelectionPurposeHost purpose)
{
switch (purpose)
{
case UserSelectionPurposeHost::GameCardRegistration:
return "Who will receive the points?";
case UserSelectionPurposeHost::EShopLaunch:
return "Who is using Nintendo eShop?";
case UserSelectionPurposeHost::EShopItemShow:
return "Who is making this purchase?";
case UserSelectionPurposeHost::PicturePost:
return "Who is posting?";
case UserSelectionPurposeHost::NintendoAccountLinkage:
return "Select a user to link to a Nintendo Account.";
case UserSelectionPurposeHost::SettingsUpdate:
return "Change settings for which user?";
case UserSelectionPurposeHost::SaveDataDeletion:
return "Format data for which user?";
case UserSelectionPurposeHost::UserMigration:
return "Which user will be transferred to another console?";
case UserSelectionPurposeHost::SaveDataTransfer:
return "Send save data for which user?";
case UserSelectionPurposeHost::General:
default:
return "Select a user:";
}
}

View File

@ -0,0 +1,64 @@
#pragma once
#include <nxemu-module-spec/operating_system.h>
#include <sciter_element.h>
#include <sciter_handler.h>
#include <mutex>
class SystemModules;
class ProfileSelectApplet :
public IProfileSelectApplet,
public IEventSink,
public IClickSink
{
public:
ProfileSelectApplet();
void Attach(ISciterUI & sciterUI, SystemModules & modules, SciterElement rootElement, void * parentHwnd);
void Detach();
// IProfileSelectApplet
void Close() override;
void SelectProfile(void * user_data, ProfileSelectFinishedFn finished, const ProfileSelectHostParameters * parameters) const override;
// IEventSink
bool OnEvent(SCITER_ELEMENT element, SCITER_ELEMENT source, uint32_t event_code, uint64_t reason) override;
// IClickSink
bool OnClick(SCITER_ELEMENT element, SCITER_ELEMENT source, uint32_t reason) override;
private:
ProfileSelectApplet(const ProfileSelectApplet &) = delete;
ProfileSelectApplet & operator=(const ProfileSelectApplet &) = delete;
static constexpr uint32_t EVENT_PROFILE_SELECT = 0x2008;
void ProcessPendingSelect();
void ShowDialog();
void PopulateProfiles();
void SelectIndex(int32_t index);
bool ResolveTileIndex(SCITER_ELEMENT source, int32_t & index) const;
void AcceptSelection();
void CancelSelection();
void CloseDialog();
void Finish(bool hasUuid, const uint8_t uuidBytes[HOST_PROFILE_UUID_SIZE]);
std::string GetImageFileUrl(const HostProfileInfo & profile) const;
static const char * WindowTitleForMode(ProfileUiMode mode);
static const char * InstructionForPurpose(UserSelectionPurposeHost purpose);
ISciterUI * m_sciterUI;
SystemModules * m_modules;
SciterElement m_rootElement;
void * m_parentHwnd;
ISciterWindow * m_window;
int32_t m_selectedIndex;
bool m_dialogOpen;
mutable std::mutex m_mutex;
mutable bool m_pending;
mutable void * m_userData;
mutable ProfileSelectFinishedFn m_finished;
mutable ProfileSelectHostParameters m_parameters;
};

View File

@ -3,7 +3,7 @@
#include <nxemu-module-spec/operating_system.h>
class WebBrowserApplet final :
public IWebBrowserFrontendApplet
public IWebBrowserApplet
{
public:
WebBrowserApplet();

View File

@ -37,6 +37,7 @@
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="applets\profile_select.cpp" />
<ClCompile Include="applets\web_browser.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="settings\ui_settings.cpp" />
@ -69,6 +70,7 @@
<ClCompile Include="user_interface\widgets\rom_browser.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="applets\profile_select.h" />
<ClInclude Include="applets\web_browser.h" />
<ClInclude Include="settings\ui_identifiers.h" />
<ClInclude Include="settings\ui_settings.h" />

View File

@ -135,6 +135,9 @@
<ClCompile Include="user_interface\settings\game_config_addons.cpp">
<Filter>Source Files\user_interface\settings</Filter>
</ClCompile>
<ClCompile Include="applets\profile_select.cpp">
<Filter>Source Files\applets</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="user_interface\resource.h">
@ -236,6 +239,9 @@
<ClInclude Include="user_interface\settings\game_config_addons.h">
<Filter>Header Files\user_interface\settings</Filter>
</ClInclude>
<ClInclude Include="applets\profile_select.h">
<Filter>Header Files\applets</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="user_interface\nxemu.rc">

View File

@ -289,6 +289,7 @@ const std::string * SciterMainWindow::MenuIconSvg(GuiAction action)
SciterMainWindow::~SciterMainWindow()
{
Notification::GetInstance().ClearSciterContext();
m_ProfileSelect.Detach();
m_WebBrowser.DetachWindow();
SettingsStore & settings = SettingsStore::GetInstance();
@ -333,8 +334,9 @@ void SciterMainWindow::RegisterApplets()
return;
}
IOperatingSystem & os = m_modules.Modules().OperatingSystem();
m_ProfileSelect.Attach(m_sciterUI, m_modules, m_rootElement, (void *)m_window->GetHandle());
m_WebBrowser.AttachToWindow(m_window->GetHandle());
os.SetFrontendApplets(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &m_WebBrowser);
os.SetFrontendApplets(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &m_ProfileSelect, nullptr, &m_WebBrowser);
}
void SciterMainWindow::ResetMenu()
@ -1491,6 +1493,7 @@ void SciterMainWindow::OnRecetGame(uint32_t fileIndex)
void SciterMainWindow::OnWindowDestroy(HWINDOW /*hWnd*/)
{
m_ProfileSelect.Detach();
m_WebBrowser.DetachWindow();
m_sciterUI.Stop();
}

View File

@ -1,4 +1,5 @@
#pragma once
#include "applets/profile_select.h"
#include "applets/web_browser.h"
#include "startup_checks.h"
#include "user_interface/discord_presence.h"
@ -214,6 +215,7 @@ private:
std::string m_pendingGameConfigPath;
std::unique_ptr<InputConfig> m_inputConfig;
std::unique_ptr<AboutDialog> m_aboutDialog;
ProfileSelectApplet m_ProfileSelect;
WebBrowserApplet m_WebBrowser;
float m_resolutionUpFactor;
bool m_useMultiCore;