From cff490d18495c449d3c9b3632cefa8ce2f969f69 Mon Sep 17 00:00:00 2001 From: N3xoX1 <36610570+N3xoX1@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:34:17 +0930 Subject: [PATCH] When profile select applet is asking to select a profile (#99) Co-authored-by: N3xoX1 --- lang/english/css/profile_select_dialog.css | 86 ++++ lang/english/html/profile_select_dialog.html | 24 + src/nxemu-module-spec/operating_system.h | 20 +- src/nxemu-os/applets/cabinet.h | 2 +- src/nxemu-os/applets/controller.h | 2 +- src/nxemu-os/applets/error.h | 2 +- src/nxemu-os/applets/general.h | 4 +- src/nxemu-os/applets/mii_edit.h | 2 +- src/nxemu-os/applets/profile_select.h | 2 +- src/nxemu-os/applets/software_keyboard.h | 2 +- src/nxemu-os/applets/web_browser.h | 2 +- .../service/am/frontend/applet_controller.cpp | 2 +- .../service/am/frontend/applet_controller.h | 4 +- .../service/am/frontend/applet_mii_edit.cpp | 2 +- .../hle/service/am/frontend/applet_mii_edit.h | 4 +- .../am/frontend/applet_profile_select.cpp | 75 ++- .../am/frontend/applet_profile_select.h | 49 +- .../am/frontend/applet_software_keyboard.cpp | 2 +- .../am/frontend/applet_software_keyboard.h | 4 +- .../am/frontend/applet_web_browser.cpp | 2 +- .../service/am/frontend/applet_web_browser.h | 4 +- .../core/hle/service/am/frontend/applets.cpp | 4 +- .../core/hle/service/am/frontend/applets.h | 20 +- src/nxemu-os/os_manager.cpp | 2 +- src/nxemu-os/os_manager.h | 2 +- src/nxemu/applets/profile_select.cpp | 481 ++++++++++++++++++ src/nxemu/applets/profile_select.h | 64 +++ src/nxemu/applets/web_browser.h | 2 +- src/nxemu/nxemu.vcxproj | 2 + src/nxemu/nxemu.vcxproj.filters | 6 + .../user_interface/sciter_main_window.cpp | 5 +- src/nxemu/user_interface/sciter_main_window.h | 2 + 32 files changed, 817 insertions(+), 69 deletions(-) create mode 100644 lang/english/css/profile_select_dialog.css create mode 100644 lang/english/html/profile_select_dialog.html create mode 100644 src/nxemu/applets/profile_select.cpp create mode 100644 src/nxemu/applets/profile_select.h diff --git a/lang/english/css/profile_select_dialog.css b/lang/english/css/profile_select_dialog.css new file mode 100644 index 00000000..28e0c7d0 --- /dev/null +++ b/lang/english/css/profile_select_dialog.css @@ -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; +} diff --git a/lang/english/html/profile_select_dialog.html b/lang/english/html/profile_select_dialog.html new file mode 100644 index 00000000..d53517cd --- /dev/null +++ b/lang/english/html/profile_select_dialog.html @@ -0,0 +1,24 @@ + + + + + + Profile Selector + +
+ Profile Selector + + + +
+ +
+
Select a user:
+
+
+ + + diff --git a/src/nxemu-module-spec/operating_system.h b/src/nxemu-module-spec/operating_system.h index 016a56c2..69d7c9a1 100644 --- a/src/nxemu-module-spec/operating_system.h +++ b/src/nxemu-module-spec/operating_system.h @@ -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; diff --git a/src/nxemu-os/applets/cabinet.h b/src/nxemu-os/applets/cabinet.h index 96c125dd..e1a638a2 100644 --- a/src/nxemu-os/applets/cabinet.h +++ b/src/nxemu-os/applets/cabinet.h @@ -6,7 +6,7 @@ #include class DefaultCabinetApplet final : - public ICabinetFrontendApplet + public ICabinetApplet { public: void Close() override; diff --git a/src/nxemu-os/applets/controller.h b/src/nxemu-os/applets/controller.h index edcc0233..339c04bd 100644 --- a/src/nxemu-os/applets/controller.h +++ b/src/nxemu-os/applets/controller.h @@ -11,7 +11,7 @@ class HIDCore; } class DefaultControllerApplet final : - public IControllerFrontendApplet + public IControllerApplet { public: explicit DefaultControllerApplet(Core::HID::HIDCore & hid_core_); diff --git a/src/nxemu-os/applets/error.h b/src/nxemu-os/applets/error.h index 61b23d2b..26eeb8ad 100644 --- a/src/nxemu-os/applets/error.h +++ b/src/nxemu-os/applets/error.h @@ -22,7 +22,7 @@ public: }; class DefaultErrorApplet final : - public IErrorFrontendApplet + public IErrorApplet { public: void Close() override; diff --git a/src/nxemu-os/applets/general.h b/src/nxemu-os/applets/general.h index d2103515..69d6901d 100644 --- a/src/nxemu-os/applets/general.h +++ b/src/nxemu-os/applets/general.h @@ -7,7 +7,7 @@ #include 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; diff --git a/src/nxemu-os/applets/mii_edit.h b/src/nxemu-os/applets/mii_edit.h index 1f23582d..28c0bd18 100644 --- a/src/nxemu-os/applets/mii_edit.h +++ b/src/nxemu-os/applets/mii_edit.h @@ -5,7 +5,7 @@ #include class DefaultMiiEditApplet final : - public IMiiEditFrontendApplet + public IMiiEditApplet { public: void Close() override; diff --git a/src/nxemu-os/applets/profile_select.h b/src/nxemu-os/applets/profile_select.h index 095c7283..187834ed 100644 --- a/src/nxemu-os/applets/profile_select.h +++ b/src/nxemu-os/applets/profile_select.h @@ -19,7 +19,7 @@ struct ProfileSelectParameters }; class DefaultProfileSelectApplet final : - public IProfileSelectFrontendApplet + public IProfileSelectApplet { public: void Close() override; diff --git a/src/nxemu-os/applets/software_keyboard.h b/src/nxemu-os/applets/software_keyboard.h index ab418677..5dc45ae7 100644 --- a/src/nxemu-os/applets/software_keyboard.h +++ b/src/nxemu-os/applets/software_keyboard.h @@ -14,7 +14,7 @@ struct InlineTextParameters }; class DefaultSoftwareKeyboardApplet final : - public ISoftwareKeyboardFrontendApplet + public ISoftwareKeyboardApplet { public: void Close() override; diff --git a/src/nxemu-os/applets/web_browser.h b/src/nxemu-os/applets/web_browser.h index 7ff870a5..481e1e28 100644 --- a/src/nxemu-os/applets/web_browser.h +++ b/src/nxemu-os/applets/web_browser.h @@ -5,7 +5,7 @@ #include class DefaultWebBrowserApplet final : - public IWebBrowserFrontendApplet + public IWebBrowserApplet { public: void Close() override; diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_controller.cpp b/src/nxemu-os/core/hle/service/am/frontend/applet_controller.cpp index 40992eea..a3802e57 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_controller.cpp +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_controller.cpp @@ -60,7 +60,7 @@ static ControllerHostParameters ConvertToFrontendParameters( return params; } -Controller::Controller(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IControllerFrontendApplet & frontend_) : +Controller::Controller(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IControllerApplet & frontend_) : FrontendApplet{system_, applet_, applet_mode_}, frontend{frontend_} { diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_controller.h b/src/nxemu-os/core/hle/service/am/frontend/applet_controller.h index f734526f..f16f18c5 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_controller.h +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_controller.h @@ -121,7 +121,7 @@ static_assert(sizeof(ControllerSupportResultInfo) == 0xC, class Controller final : public FrontendApplet { public: explicit Controller(Core::System& system_, std::shared_ptr 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; diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.cpp b/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.cpp index b7e2a281..0fe0f279 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.cpp +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.cpp @@ -15,7 +15,7 @@ namespace Service::AM::Frontend { -MiiEdit::MiiEdit(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IMiiEditFrontendApplet & frontend_) : +MiiEdit::MiiEdit(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IMiiEditApplet & frontend_) : FrontendApplet{system_, applet_, applet_mode_}, frontend{frontend_} { diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.h b/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.h index 7373213a..2da9176d 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.h +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_mii_edit.h @@ -24,7 +24,7 @@ namespace Service::AM::Frontend class MiiEdit final : public FrontendApplet { public: - explicit MiiEdit(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IMiiEditFrontendApplet & frontend_); + explicit MiiEdit(Core::System & system_, std::shared_ptr 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{}; diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.cpp b/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.cpp index f6cd0db6..be47fa55 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.cpp +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.cpp @@ -4,6 +4,7 @@ #include #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 & 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_, LibraryAppletMode applet_mode_) : - FrontendApplet{system_, applet_, applet_mode_} +ProfileSelect::ProfileSelect(Core::System & system_, std::shared_ptr 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(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, ¶meters); } void ProfileSelect::SelectionComplete(std::optional uuid) @@ -93,8 +145,23 @@ void ProfileSelect::SelectionComplete(std::optional 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 uuid{}; + std::memcpy(uuid.data(), uuid_bytes, uuid.size()); + self->SelectionComplete(Common::UUID{uuid}); + } + else + { + self->SelectionComplete(std::nullopt); + } +} + } // namespace Service::AM::Frontend diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.h b/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.h index 45f2f1be..dee2ae30 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.h +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_profile_select.h @@ -5,25 +5,29 @@ #include -#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 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 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_, - LibraryAppletMode applet_mode_); + explicit ProfileSelect(Core::System& system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IProfileSelectApplet& frontend_); ~ProfileSelect() override; void Initialize() override; @@ -127,6 +137,9 @@ public: void SelectionComplete(std::optional 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; diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.cpp b/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.cpp index ad0bf409..7d3c5ced 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.cpp +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.cpp @@ -76,7 +76,7 @@ void SetReplyBase(std::vector & reply, SwkbdState state, SwkbdReplyType repl } // Anonymous namespace -SoftwareKeyboard::SoftwareKeyboard(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, ISoftwareKeyboardFrontendApplet & frontend_) : +SoftwareKeyboard::SoftwareKeyboard(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, ISoftwareKeyboardApplet & frontend_) : FrontendApplet{system_, applet_, applet_mode_}, frontend{frontend_} { diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.h b/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.h index f5ce5922..a7ded1ca 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.h +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_software_keyboard.h @@ -21,7 +21,7 @@ class SoftwareKeyboard final : public FrontendApplet { public: - explicit SoftwareKeyboard(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, ISoftwareKeyboardFrontendApplet & frontend_); + explicit SoftwareKeyboard(Core::System & system_, std::shared_ptr 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; diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.cpp b/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.cpp index 6406424a..505726af 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.cpp +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.cpp @@ -345,7 +345,7 @@ void ExtractSharedFonts(Core::System & system) } // namespace -WebBrowser::WebBrowser(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IWebBrowserFrontendApplet & frontend_) : +WebBrowser::WebBrowser(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IWebBrowserApplet & frontend_) : FrontendApplet{system_, applet_, applet_mode_}, frontend(frontend_) { diff --git a/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.h b/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.h index 7b5d484b..0528ee91 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.h +++ b/src/nxemu-os/core/hle/service/am/frontend/applet_web_browser.h @@ -29,7 +29,7 @@ namespace Service::AM::Frontend class WebBrowser final : public FrontendApplet { public: - WebBrowser(Core::System & system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, IWebBrowserFrontendApplet & frontend_); + WebBrowser(Core::System & system_, std::shared_ptr 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}; diff --git a/src/nxemu-os/core/hle/service/am/frontend/applets.cpp b/src/nxemu-os/core/hle/service/am/frontend/applets.cpp index b83eef1e..020138b0 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applets.cpp +++ b/src/nxemu-os/core/hle/service/am/frontend/applets.cpp @@ -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 FrontendAppletHolder::GetApplet(std::shared_ptr< case AppletId::Error: return std::make_shared(system, applet, mode); case AppletId::ProfileSelect: - return std::make_shared(system, applet, mode); + return std::make_shared(system, applet, mode, *frontend.profile_select); case AppletId::SoftwareKeyboard: return std::make_shared(system, applet, mode, *frontend.software_keyboard); case AppletId::MiiEdit: diff --git a/src/nxemu-os/core/hle/service/am/frontend/applets.h b/src/nxemu-os/core/hle/service/am/frontend/applets.h index 976fece4..d528b706 100644 --- a/src/nxemu-os/core/hle/service/am/frontend/applets.h +++ b/src/nxemu-os/core/hle/service/am/frontend/applets.h @@ -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 diff --git a/src/nxemu-os/os_manager.cpp b/src/nxemu-os/os_manager.cpp index 757c70eb..c4fc879d 100644 --- a/src/nxemu-os/os_manager.cpp +++ b/src/nxemu-os/os_manager.cpp @@ -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; diff --git a/src/nxemu-os/os_manager.h b/src/nxemu-os/os_manager.h index a1ed7ae3..6d5b1c0c 100644 --- a/src/nxemu-os/os_manager.h +++ b/src/nxemu-os/os_manager.h @@ -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; diff --git a/src/nxemu/applets/profile_select.cpp b/src/nxemu/applets/profile_select.cpp new file mode 100644 index 00000000..050e9e65 --- /dev/null +++ b/src/nxemu/applets/profile_select.cpp @@ -0,0 +1,481 @@ +#include "profile_select.h" + +#include "user_interface/html_utils.h" +#include +#include + +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 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 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 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 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 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 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 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( + "
" + "
" + "
%s
" + "
", + 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 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:"; + } +} diff --git a/src/nxemu/applets/profile_select.h b/src/nxemu/applets/profile_select.h new file mode 100644 index 00000000..e867fa75 --- /dev/null +++ b/src/nxemu/applets/profile_select.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include + +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; +}; diff --git a/src/nxemu/applets/web_browser.h b/src/nxemu/applets/web_browser.h index e1fee0e6..07fbf928 100644 --- a/src/nxemu/applets/web_browser.h +++ b/src/nxemu/applets/web_browser.h @@ -3,7 +3,7 @@ #include class WebBrowserApplet final : - public IWebBrowserFrontendApplet + public IWebBrowserApplet { public: WebBrowserApplet(); diff --git a/src/nxemu/nxemu.vcxproj b/src/nxemu/nxemu.vcxproj index 64f5667d..478872f8 100644 --- a/src/nxemu/nxemu.vcxproj +++ b/src/nxemu/nxemu.vcxproj @@ -37,6 +37,7 @@ + @@ -69,6 +70,7 @@ + diff --git a/src/nxemu/nxemu.vcxproj.filters b/src/nxemu/nxemu.vcxproj.filters index a2ca6a4e..eb5a94e4 100644 --- a/src/nxemu/nxemu.vcxproj.filters +++ b/src/nxemu/nxemu.vcxproj.filters @@ -135,6 +135,9 @@ Source Files\user_interface\settings + + Source Files\applets + @@ -236,6 +239,9 @@ Header Files\user_interface\settings + + Header Files\applets + diff --git a/src/nxemu/user_interface/sciter_main_window.cpp b/src/nxemu/user_interface/sciter_main_window.cpp index 7b44f69a..19512e3f 100644 --- a/src/nxemu/user_interface/sciter_main_window.cpp +++ b/src/nxemu/user_interface/sciter_main_window.cpp @@ -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(); } diff --git a/src/nxemu/user_interface/sciter_main_window.h b/src/nxemu/user_interface/sciter_main_window.h index e013b049..1527ad4b 100644 --- a/src/nxemu/user_interface/sciter_main_window.h +++ b/src/nxemu/user_interface/sciter_main_window.h @@ -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 m_inputConfig; std::unique_ptr m_aboutDialog; + ProfileSelectApplet m_ProfileSelect; WebBrowserApplet m_WebBrowser; float m_resolutionUpFactor; bool m_useMultiCore;