diff --git a/src/graphics/presentation/systemOverlay.cpp b/src/graphics/presentation/systemOverlay.cpp index e3ceca842..56ed70617 100644 --- a/src/graphics/presentation/systemOverlay.cpp +++ b/src/graphics/presentation/systemOverlay.cpp @@ -29,9 +29,9 @@ namespace Libs::Graphics { namespace { -namespace CoreIme = Libs::Ime; -namespace DialogIme = Libs::Dialog::ImeDialog; -namespace ErrorDialog = Libs::Dialog::ErrorDialog; +namespace CoreIme = Libs::Ime; +namespace DialogIme = Libs::Dialog::ImeDialog; +namespace SystemDialog = Libs::Dialog::SystemDialog; namespace Ime { @@ -104,7 +104,7 @@ bool HostQueueExternalInput(uint64_t generation, ExternalInput input) { } // namespace Ime -enum class OverlayKind : uint8_t { None, Ime, Error }; +enum class OverlayKind : uint8_t { None, Ime, Dialog }; struct OverlaySession { OverlayKind kind = OverlayKind::None; @@ -114,14 +114,14 @@ struct OverlaySession { }; struct OverlaySnapshot { - OverlaySession session; - Ime::HostSnapshot ime; - ErrorDialog::HostSnapshot error; + OverlaySession session; + Ime::HostSnapshot ime; + SystemDialog::HostSnapshot dialog; }; bool GetOverlaySnapshot(OverlaySnapshot* snapshot) { - if (ErrorDialog::GetHostSnapshot(&snapshot->error)) { - snapshot->session = {OverlayKind::Error, snapshot->error.generation}; + if (SystemDialog::GetHostSnapshot(&snapshot->dialog)) { + snapshot->session = {OverlayKind::Dialog, snapshot->dialog.generation}; return true; } if (Ime::GetHostSnapshot(&snapshot->ime)) { @@ -234,7 +234,7 @@ void RefreshVisibility() { bool capture_keyboard = false; bool text_input = false; bool multiline = false; - if (snapshot.session.kind == OverlayKind::Error) { + if (snapshot.session.kind == OverlayKind::Dialog) { capture_controller = true; capture_keyboard = true; } else if (snapshot.session.kind == OverlayKind::Ime) { @@ -429,14 +429,14 @@ void InitializeSystemOverlayInput(SDL_Window* window) { } CoreIme::SetVisibilityCallback(OnCoreVisibilityChanged); DialogIme::SetVisibilityCallback(OnDialogVisibilityChanged); - ErrorDialog::SetVisibilityCallback(RefreshVisibility); + SystemDialog::SetVisibilityCallback(RefreshVisibility); RefreshVisibility(); } void ShutdownSystemOverlayInput() { CoreIme::SetVisibilityCallback(nullptr); DialogIme::SetVisibilityCallback(nullptr); - ErrorDialog::SetVisibilityCallback(nullptr); + SystemDialog::SetVisibilityCallback(nullptr); { std::scoped_lock lock(g_visibility_mutex); g_input_lifecycle_active = false; @@ -466,9 +466,9 @@ void ShutdownSystemOverlayInput() { SystemOverlayVisualState GetSystemOverlayVisualState() noexcept { const auto core = CoreIme::GetVisualState(); const auto dialog = DialogIme::GetVisualState(); - const auto error = ErrorDialog::GetVisualState(); - return {core.active || dialog.active || error.active, - core.revision + dialog.revision + error.revision}; + const auto system = SystemDialog::GetVisualState(); + return {core.active || dialog.active || system.active, + core.revision + dialog.revision + system.revision}; } bool ProcessSystemOverlayInput(const SDL_Event& event) { @@ -521,10 +521,11 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) { if (keyboard_event && !g_input_keyboard) { return false; } - if (keyboard_event && session.kind == OverlayKind::Error) { + if (keyboard_event && session.kind == OverlayKind::Dialog) { if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && - (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER)) { - ErrorDialog::HostAccept(generation); + (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER || + event.key.key == SDLK_ESCAPE)) { + SystemDialog::HostClose(generation); } return true; } @@ -904,7 +905,7 @@ struct SystemOverlay::Impl { ImGui::End(); } - void DrawError(const ErrorDialog::HostSnapshot& snapshot, vk::Extent2D extent) { + void DrawDialog(const SystemDialog::HostSnapshot& snapshot, vk::Extent2D extent) { const ImVec2 display(static_cast(extent.width), static_cast(extent.height)); const float scale = std::max(std::min(display.x / 1280.0f, display.y / 720.0f), 0.5f); ImGui::GetBackgroundDrawList()->AddRectFilled({0.0f, 0.0f}, display, @@ -922,17 +923,23 @@ struct SystemOverlay::Impl { constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize; - ImGui::Begin("##SystemError", nullptr, flags); - ImGui::TextUnformatted("Error"); + const bool signin = snapshot.kind == SystemDialog::Kind::Signin; + ImGui::Begin("##SystemDialog", nullptr, flags); + ImGui::TextUnformatted(signin ? "Sign in to PlayStation Network" : "Error"); ImGui::Separator(); const auto error_code = static_cast(snapshot.error_code); - ImGui::TextWrapped("%s", error_code == 0x80550006u + ImGui::TextWrapped("%s", signin ? "PlayStation Network sign-in is unavailable." + : error_code == 0x80550006u ? "You are not signed in to PlayStation Network." : "An error has occurred."); - ImGui::TextDisabled("Error code: 0x%08X", error_code); + if (!signin) { + ImGui::TextDisabled("Error code: 0x%08X", error_code); + } const float button_width = std::min(140.0f * scale, ImGui::GetContentRegionAvail().x); ImGui::SetCursorPosX((ImGui::GetWindowSize().x - button_width) * 0.5f); - const bool accepted = ImGui::Button("OK", {button_width, 44.0f * scale}); + const bool accepted = + ImGui::Button(signin ? "Cancel" : "OK", {button_width, 44.0f * scale}) || + (signin && ImGui::IsKeyPressed(ImGuiKey_GamepadFaceRight, false)); if (focus_pending) { ImGui::SetItemDefaultFocus(); focus_pending = false; @@ -941,7 +948,7 @@ struct SystemOverlay::Impl { ImGui::PopFont(); ImGui::PopStyleVar(2); if (accepted) { - ErrorDialog::HostAccept(snapshot.generation); + SystemDialog::HostClose(snapshot.generation); } } @@ -955,7 +962,8 @@ struct SystemOverlay::Impl { if (session != snapshot.session) { session = snapshot.session; focus_pending = true; - shift = (snapshot.ime.option & Ime::OPTION_NO_AUTO_CAPITALIZE) == 0; + shift = session.kind == OverlayKind::Ime && + (snapshot.ime.option & Ime::OPTION_NO_AUTO_CAPITALIZE) == 0; symbol_mode = false; panel_offset = {}; right_stick = {}; @@ -981,8 +989,8 @@ struct SystemOverlay::Impl { ImGui::EndFrame(); return false; } - if (snapshot.session.kind == OverlayKind::Error) { - DrawError(snapshot.error, frame_extent); + if (snapshot.session.kind == OverlayKind::Dialog) { + DrawDialog(snapshot.dialog, frame_extent); } else { DrawIme(snapshot.ime, frame_extent); } diff --git a/src/libs/dialog.cpp b/src/libs/dialog.cpp index cec494bf9..9173ebfa2 100644 --- a/src/libs/dialog.cpp +++ b/src/libs/dialog.cpp @@ -14,6 +14,81 @@ // NOLINTNEXTLINE(modernize-concat-nested-namespaces) namespace Libs::Dialog { +namespace { + +constexpr int DIALOG_STATUS_RUNNING = 2; +constexpr int DIALOG_STATUS_FINISHED = 3; + +struct DialogState { + int status = 0; + uint64_t generation = 0; +}; + +std::mutex g_dialog_mutex; +DialogState g_error; +DialogState g_signin; +uint64_t g_dialog_generation = 0; +uint64_t g_dialog_revision = 0; +int32_t g_error_code = 0; +void (*g_dialog_visibility_callback)() = nullptr; + +void SetDialogStatus(DialogState& dialog, int status, std::unique_lock& lock) { + const bool visibility_changed = + (dialog.status == DIALOG_STATUS_RUNNING) != (status == DIALOG_STATUS_RUNNING); + dialog.status = status; + if (visibility_changed) { + g_dialog_revision++; + if (status == DIALOG_STATUS_RUNNING) { + dialog.generation = ++g_dialog_generation; + } + } + const auto callback = visibility_changed ? g_dialog_visibility_callback : nullptr; + lock.unlock(); + if (callback != nullptr) { + callback(); + } +} + +} // namespace + +namespace SystemDialog { + +bool GetHostSnapshot(HostSnapshot* snapshot) { + std::scoped_lock lock(g_dialog_mutex); + if (g_signin.status == DIALOG_STATUS_RUNNING) { + *snapshot = {Kind::Signin, g_signin.generation, 0}; + return true; + } + if (g_error.status == DIALOG_STATUS_RUNNING) { + *snapshot = {Kind::Error, g_error.generation, g_error_code}; + return true; + } + return false; +} + +VisualState GetVisualState() noexcept { + std::scoped_lock lock(g_dialog_mutex); + const bool signin = g_signin.status == DIALOG_STATUS_RUNNING; + return {g_error.status == DIALOG_STATUS_RUNNING || signin, signin, g_dialog_revision}; +} + +void SetVisibilityCallback(void (*callback)()) { + std::scoped_lock lock(g_dialog_mutex); + g_dialog_visibility_callback = callback; +} + +bool HostClose(uint64_t generation) { + std::unique_lock lock(g_dialog_mutex); + auto& dialog = g_signin.status == DIALOG_STATUS_RUNNING ? g_signin : g_error; + if (dialog.status != DIALOG_STATUS_RUNNING || generation != dialog.generation) { + return false; + } + SetDialogStatus(dialog, DIALOG_STATUS_FINISHED, lock); + return true; +} + +} // namespace SystemDialog + namespace CommonDialog { LIB_NAME("CommonDialog", "CommonDialog"); @@ -207,6 +282,7 @@ constexpr int SIGNIN_ERROR_NOT_INITIALIZED = static_cast(0x81350001u); constexpr int SIGNIN_ERROR_ALREADY_INITIALIZED = static_cast(0x81350002u); constexpr int SIGNIN_ERROR_PARAM_INVALID = static_cast(0x81350003u); constexpr int SIGNIN_ERROR_INVALID_STATE = static_cast(0x81350005u); +constexpr int SIGNIN_ERROR_INVALID_USER_ID = static_cast(0x81350007u); struct SigninDialogResult { int32_t result; @@ -222,70 +298,78 @@ struct SigninDialogParam { static_assert(sizeof(SigninDialogResult) == 16); static_assert(sizeof(SigninDialogParam) == 16); -static int g_signin_status = SIGNIN_STATUS_NONE; - int KYTY_SYSV_ABI SigninDialogInitialize() { PRINT_NAME(); - if (g_signin_status != SIGNIN_STATUS_NONE) { + std::scoped_lock lock(g_dialog_mutex); + if (g_signin.status != SIGNIN_STATUS_NONE) { return SIGNIN_ERROR_ALREADY_INITIALIZED; } - g_signin_status = SIGNIN_STATUS_INITIALIZED; + g_signin.status = SIGNIN_STATUS_INITIALIZED; return OK; } int KYTY_SYSV_ABI SigninDialogTerminate() { PRINT_NAME(); - if (g_signin_status == SIGNIN_STATUS_NONE) { + std::unique_lock lock(g_dialog_mutex); + if (g_signin.status == SIGNIN_STATUS_NONE) { return SIGNIN_ERROR_NOT_INITIALIZED; } - g_signin_status = SIGNIN_STATUS_NONE; + SetDialogStatus(g_signin, SIGNIN_STATUS_NONE, lock); return OK; } int KYTY_SYSV_ABI SigninDialogOpen(const void* param) { PRINT_NAME(); - if (g_signin_status != SIGNIN_STATUS_INITIALIZED && g_signin_status != SIGNIN_STATUS_FINISHED) { + std::unique_lock lock(g_dialog_mutex); + if (g_signin.status == SIGNIN_STATUS_NONE) { + return SIGNIN_ERROR_NOT_INITIALIZED; + } + if (g_signin.status != SIGNIN_STATUS_INITIALIZED && g_signin.status != SIGNIN_STATUS_FINISHED) { return SIGNIN_ERROR_INVALID_STATE; } - if (param == nullptr || - static_cast(param)->size != sizeof(SigninDialogParam)) { + if (param == nullptr) { return SIGNIN_ERROR_PARAM_INVALID; } - g_signin_status = SIGNIN_STATUS_RUNNING; + const auto* p = static_cast(param); + if (p->size != sizeof(SigninDialogParam) || p->reserved[0] != 0 || p->reserved[1] != 0) { + return SIGNIN_ERROR_PARAM_INVALID; + } + if (p->user_id != Config::GetUserId()) { + return SIGNIN_ERROR_INVALID_USER_ID; + } + SetDialogStatus(g_signin, SIGNIN_STATUS_RUNNING, lock); return OK; } int KYTY_SYSV_ABI SigninDialogClose() { PRINT_NAME(); - if (g_signin_status == SIGNIN_STATUS_NONE) { + std::unique_lock lock(g_dialog_mutex); + if (g_signin.status == SIGNIN_STATUS_NONE) { return SIGNIN_ERROR_NOT_INITIALIZED; } - if (g_signin_status != SIGNIN_STATUS_RUNNING && g_signin_status != SIGNIN_STATUS_FINISHED) { - return SIGNIN_ERROR_INVALID_STATE; - } - g_signin_status = SIGNIN_STATUS_FINISHED; + SetDialogStatus(g_signin, SIGNIN_STATUS_FINISHED, lock); return OK; } int KYTY_SYSV_ABI SigninDialogUpdateStatus() { PRINT_NAME(); - if (g_signin_status == SIGNIN_STATUS_RUNNING) { - g_signin_status = SIGNIN_STATUS_FINISHED; - } - return g_signin_status; + std::scoped_lock lock(g_dialog_mutex); + return g_signin.status; } int KYTY_SYSV_ABI SigninDialogGetStatus() { PRINT_NAME(); - return g_signin_status; + std::scoped_lock lock(g_dialog_mutex); + return g_signin.status; } int KYTY_SYSV_ABI SigninDialogGetResult(void* result) { PRINT_NAME(); - if (g_signin_status == SIGNIN_STATUS_NONE) { + std::scoped_lock lock(g_dialog_mutex); + if (g_signin.status == SIGNIN_STATUS_NONE) { return SIGNIN_ERROR_NOT_INITIALIZED; } - if (g_signin_status != SIGNIN_STATUS_FINISHED) { + if (g_signin.status != SIGNIN_STATUS_FINISHED) { return SIGNIN_ERROR_INVALID_STATE; } if (result == nullptr) { @@ -661,64 +745,15 @@ struct ErrorDialogParam { static_assert(sizeof(ErrorDialogParam) == 16); -static std::mutex g_error_mutex; -static int g_error_status = STATUS_NONE; -static uint64_t g_error_generation = 0; -static uint64_t g_error_revision = 0; -static int32_t g_error_code = 0; -static void (*g_error_visibility_callback)() = nullptr; - -static void SetStatus(int status, std::unique_lock& lock) { - const bool visibility_changed = - (g_error_status == STATUS_RUNNING) != (status == STATUS_RUNNING); - g_error_status = status; - if (visibility_changed) { - g_error_revision++; - } - const auto callback = visibility_changed ? g_error_visibility_callback : nullptr; - lock.unlock(); - if (callback != nullptr) { - callback(); - } -} - -bool GetHostSnapshot(HostSnapshot* snapshot) { - std::scoped_lock lock(g_error_mutex); - if (g_error_status != STATUS_RUNNING) { - return false; - } - *snapshot = {g_error_generation, g_error_code}; - return true; -} - -VisualState GetVisualState() noexcept { - std::scoped_lock lock(g_error_mutex); - return {g_error_status == STATUS_RUNNING, g_error_revision}; -} - -void SetVisibilityCallback(void (*callback)()) { - std::scoped_lock lock(g_error_mutex); - g_error_visibility_callback = callback; -} - -bool HostAccept(uint64_t generation) { - std::unique_lock lock(g_error_mutex); - if (g_error_status != STATUS_RUNNING || generation != g_error_generation) { - return false; - } - SetStatus(STATUS_FINISHED, lock); - return true; -} - int KYTY_SYSV_ABI ErrorDialogInitialize() { PRINT_NAME(); - std::scoped_lock lock(g_error_mutex); - if (g_error_status != STATUS_NONE) { + std::scoped_lock lock(g_dialog_mutex); + if (g_error.status != STATUS_NONE) { return ERROR_ALREADY_INITIALIZED; } - g_error_status = STATUS_INITIALIZED; + g_error.status = STATUS_INITIALIZED; return OK; } @@ -726,8 +761,8 @@ int KYTY_SYSV_ABI ErrorDialogInitialize() { int KYTY_SYSV_ABI ErrorDialogOpen(const void* param) { PRINT_NAME(); - std::unique_lock lock(g_error_mutex); - if (g_error_status != STATUS_INITIALIZED && g_error_status != STATUS_FINISHED) { + std::unique_lock lock(g_dialog_mutex); + if (g_error.status != STATUS_INITIALIZED && g_error.status != STATUS_FINISHED) { return ERROR_INVALID_STATE; } if (param == nullptr) { @@ -745,8 +780,7 @@ int KYTY_SYSV_ABI ErrorDialogOpen(const void* param) { p->size, static_cast(p->error_code), p->user_id); g_error_code = p->error_code; - g_error_generation++; - SetStatus(STATUS_RUNNING, lock); + SetDialogStatus(g_error, STATUS_RUNNING, lock); return OK; } @@ -754,12 +788,12 @@ int KYTY_SYSV_ABI ErrorDialogOpen(const void* param) { int KYTY_SYSV_ABI ErrorDialogClose() { PRINT_NAME(); - std::unique_lock lock(g_error_mutex); - if (g_error_status != STATUS_RUNNING) { + std::unique_lock lock(g_dialog_mutex); + if (g_error.status != STATUS_RUNNING) { return ERROR_INVALID_STATE; } - SetStatus(STATUS_FINISHED, lock); + SetDialogStatus(g_error, STATUS_FINISHED, lock); return OK; } @@ -767,12 +801,12 @@ int KYTY_SYSV_ABI ErrorDialogClose() { int KYTY_SYSV_ABI ErrorDialogTerminate() { PRINT_NAME(); - std::unique_lock lock(g_error_mutex); - if (g_error_status == STATUS_NONE) { + std::unique_lock lock(g_dialog_mutex); + if (g_error.status == STATUS_NONE) { return ERROR_NOT_INITIALIZED; } - SetStatus(STATUS_NONE, lock); + SetDialogStatus(g_error, STATUS_NONE, lock); return OK; } @@ -780,15 +814,15 @@ int KYTY_SYSV_ABI ErrorDialogTerminate() { int KYTY_SYSV_ABI ErrorDialogUpdateStatus() { PRINT_NAME(); - std::scoped_lock lock(g_error_mutex); - return g_error_status; + std::scoped_lock lock(g_dialog_mutex); + return g_error.status; } int KYTY_SYSV_ABI ErrorDialogGetStatus() { PRINT_NAME(); - std::scoped_lock lock(g_error_mutex); - return g_error_status; + std::scoped_lock lock(g_dialog_mutex); + return g_error.status; } } // namespace ErrorDialog diff --git a/src/libs/dialog.h b/src/libs/dialog.h index 2604f12a4..945ce22e7 100644 --- a/src/libs/dialog.h +++ b/src/libs/dialog.h @@ -68,22 +68,30 @@ int KYTY_SYSV_ABI MsgDialogProgressBarSetMsg(int target, const char* msg); } // namespace MsgDialog -namespace ErrorDialog { +namespace SystemDialog { + +enum class Kind { Error, Signin }; struct HostSnapshot { + Kind kind; uint64_t generation; int32_t error_code; }; struct VisualState { bool active; + bool background; uint64_t revision; }; bool GetHostSnapshot(HostSnapshot* snapshot); VisualState GetVisualState() noexcept; void SetVisibilityCallback(void (*callback)()); -bool HostAccept(uint64_t generation); +bool HostClose(uint64_t generation); + +} // namespace SystemDialog + +namespace ErrorDialog { int KYTY_SYSV_ABI ErrorDialogInitialize(); int KYTY_SYSV_ABI ErrorDialogOpen(const void* param); diff --git a/src/libs/libSystemService.cpp b/src/libs/libSystemService.cpp index 8b04713e0..da4ffa296 100644 --- a/src/libs/libSystemService.cpp +++ b/src/libs/libSystemService.cpp @@ -163,8 +163,10 @@ static int KYTY_SYSV_ABI SystemServiceGetStatus(SystemServiceStatus* status) { return SYSTEM_SERVICE_ERROR_PARAMETER; } - *status = SystemServiceStatus(); - status->is_system_ui_overlaid = Dialog::ErrorDialog::GetVisualState().active; + const auto dialog = Dialog::SystemDialog::GetVisualState(); + *status = SystemServiceStatus(); + status->is_system_ui_overlaid = dialog.active; + status->is_in_background_execution = dialog.background; return OK; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 5de1d24ca..7d9512d79 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -977,6 +977,7 @@ void Require(const char *shader_name, const char *stage, bool value, void CheckErrorDialogLifecycle() { namespace ErrorDialog = Libs::Dialog::ErrorDialog; + namespace SystemDialog = Libs::Dialog::SystemDialog; struct Param { int32_t size = 16; int32_t error_code = static_cast(0x80550006u); @@ -986,27 +987,27 @@ void CheckErrorDialogLifecycle() { static_assert(sizeof(Param) == 16); constexpr int invalid_state = static_cast(0x80ed0005u); constexpr int invalid_param = static_cast(0x80ed0003u); - static std::vector notifications; + static std::vector notifications; notifications.clear(); - ErrorDialog::SetVisibilityCallback([] { - const auto visual = ErrorDialog::GetVisualState(); - ErrorDialog::HostSnapshot snapshot{}; + SystemDialog::SetVisibilityCallback([] { + const auto visual = SystemDialog::GetVisualState(); + SystemDialog::HostSnapshot snapshot{}; Require("ErrorDialog", "callback reentry", - ErrorDialog::GetHostSnapshot(&snapshot) == visual.active && + SystemDialog::GetHostSnapshot(&snapshot) == visual.active && (ErrorDialog::ErrorDialogGetStatus() == 2) == visual.active, "visibility callback observed inconsistent dialog state"); notifications.push_back(visual); }); - const auto initial_revision = ErrorDialog::GetVisualState().revision; + const auto initial_revision = SystemDialog::GetVisualState().revision; const auto check_state = [&](const char *stage, int status, size_t visibility_changes) { - const auto visual = ErrorDialog::GetVisualState(); - ErrorDialog::HostSnapshot snapshot{}; + const auto visual = SystemDialog::GetVisualState(); + SystemDialog::HostSnapshot snapshot{}; Require("ErrorDialog", stage, ErrorDialog::ErrorDialogGetStatus() == status && ErrorDialog::ErrorDialogUpdateStatus() == status && - visual.active == (status == 2) && - ErrorDialog::GetHostSnapshot(&snapshot) == visual.active && + visual.active == (status == 2) && !visual.background && + SystemDialog::GetHostSnapshot(&snapshot) == visual.active && visual.revision == initial_revision + visibility_changes && notifications.size() == visibility_changes && (notifications.empty() || @@ -1036,9 +1037,9 @@ void CheckErrorDialogLifecycle() { check_state("failed open preserves initialized state", 1, 0); Require("ErrorDialog", "open", ErrorDialog::ErrorDialogOpen(¶m) == 0, "valid error dialog did not open"); - ErrorDialog::HostSnapshot first{}; + SystemDialog::HostSnapshot first{}; Require("ErrorDialog", "copied error code", - ErrorDialog::GetHostSnapshot(&first) && + SystemDialog::GetHostSnapshot(&first) && first.error_code == param.error_code, "host dialog did not expose the guest error code"); param.error_code = static_cast(0x8055000au); @@ -1048,31 +1049,31 @@ void CheckErrorDialogLifecycle() { Require( "ErrorDialog", "running failures", ErrorDialog::ErrorDialogOpen(¶m) == invalid_state && - !ErrorDialog::HostAccept(first.generation + 1), + !SystemDialog::HostClose(first.generation + 1), "an invalid open or stale acknowledgement replaced the active dialog"); - ErrorDialog::HostSnapshot current{}; + SystemDialog::HostSnapshot current{}; Require("ErrorDialog", "failed open preserves active dialog", - ErrorDialog::GetHostSnapshot(¤t) && + SystemDialog::GetHostSnapshot(¤t) && current.generation == first.generation && current.error_code == first.error_code, "active error code or generation changed after a failed open"); check_state("failed operations preserve running state", 2, 1); Require("ErrorDialog", "acknowledge", - ErrorDialog::HostAccept(first.generation), + SystemDialog::HostClose(first.generation), "current host acknowledgement did not finish the dialog"); check_state("acknowledged", 3, 2); Require("ErrorDialog", "finished operations", - !ErrorDialog::HostAccept(first.generation) && + !SystemDialog::HostClose(first.generation) && ErrorDialog::ErrorDialogClose() == invalid_state && ErrorDialog::ErrorDialogOpen(&invalid) == invalid_param, "finished dialog accepted duplicate completion or an invalid open"); check_state("failed operations preserve finished state", 3, 2); Require("ErrorDialog", "reopen and stale acknowledgement", ErrorDialog::ErrorDialogOpen(¶m) == 0 && - ErrorDialog::GetHostSnapshot(¤t) && + SystemDialog::GetHostSnapshot(¤t) && current.generation != first.generation && current.error_code == param.error_code && - !ErrorDialog::HostAccept(first.generation), + !SystemDialog::HostClose(first.generation), "reopened dialog accepted acknowledgement from its predecessor"); check_state("reopened", 2, 3); Require("ErrorDialog", "guest close", ErrorDialog::ErrorDialogClose() == 0, @@ -1080,15 +1081,15 @@ void CheckErrorDialogLifecycle() { check_state("closed", 3, 4); Require("ErrorDialog", "terminate running dialog", ErrorDialog::ErrorDialogOpen(¶m) == 0 && - ErrorDialog::GetHostSnapshot(¤t) && + SystemDialog::GetHostSnapshot(¤t) && ErrorDialog::ErrorDialogTerminate() == 0 && - !ErrorDialog::HostAccept(current.generation), + !SystemDialog::HostClose(current.generation), "termination failed to remove the active dialog"); check_state("terminated", 0, 6); Require("ErrorDialog", "reinitialize and stale acknowledgement", ErrorDialog::ErrorDialogInitialize() == 0 && ErrorDialog::ErrorDialogOpen(¶m) == 0 && - !ErrorDialog::HostAccept(current.generation), + !SystemDialog::HostClose(current.generation), "reinitialization reused an old dialog generation"); check_state("reinitialized", 2, 7); Require("ErrorDialog", "final termination", @@ -1097,20 +1098,131 @@ void CheckErrorDialogLifecycle() { static_cast(0x80ed0001u), "termination did not restore the uninitialized state"); check_state("final state", 0, 8); - ErrorDialog::SetVisibilityCallback(nullptr); + SystemDialog::SetVisibilityCallback(nullptr); Require("ErrorDialog", "lifecycle without a visibility listener", ErrorDialog::ErrorDialogInitialize() == 0 && ErrorDialog::ErrorDialogOpen(¶m) == 0 && ErrorDialog::ErrorDialogClose() == 0 && ErrorDialog::ErrorDialogTerminate() == 0 && ErrorDialog::ErrorDialogGetStatus() == 0 && - !ErrorDialog::GetVisualState().active && - ErrorDialog::GetVisualState().revision == initial_revision + 10 && + !SystemDialog::GetVisualState().active && + SystemDialog::GetVisualState().revision == + initial_revision + 10 && notifications.size() == 8, "guest lifecycle depended on a registered host visibility listener"); std::printf("[host] %-32s ok\n", "ErrorDialogLifecycle"); } +void CheckSigninDialogLifecycle() { + namespace Signin = Libs::Dialog::SigninDialog; + namespace Error = Libs::Dialog::ErrorDialog; + namespace Host = Libs::Dialog::SystemDialog; + struct Param { + int32_t size = 16; + int32_t user_id = Config::GetUserId(); + int32_t reserved[2] = {}; + } param; + struct Result { + int32_t result = -1; + int32_t reserved[3] = {-1, -1, -1}; + } result; + constexpr int not_initialized = static_cast(0x81350001u); + constexpr int already_initialized = static_cast(0x81350002u); + constexpr int invalid_param = static_cast(0x81350003u); + constexpr int invalid_state = static_cast(0x81350005u); + Require("SigninDialog", "uninitialized", + Signin::SigninDialogOpen(¶m) == not_initialized && + Signin::SigninDialogClose() == not_initialized && + Signin::SigninDialogGetResult(&result) == not_initialized, + "uninitialized operations returned the wrong error"); + Require("SigninDialog", "initialize and close", + Signin::SigninDialogInitialize() == 0 && + Signin::SigninDialogClose() == 0 && + Signin::SigninDialogUpdateStatus() == 3, + "close must also succeed from INITIALIZED"); + Param invalid = param; + invalid.reserved[0] = 1; + Require("SigninDialog", "parameter validation", + Signin::SigninDialogOpen(nullptr) == invalid_param && + Signin::SigninDialogOpen(&invalid) == invalid_param, + "invalid parameters were accepted"); + invalid = param; + invalid.user_id = -1; + Require("SigninDialog", "user validation", + Signin::SigninDialogOpen(&invalid) == static_cast(0x81350007u), + "an invalid user was accepted"); + Host::SetVisibilityCallback([] { + const auto visual = Host::GetVisualState(); + Require("SigninDialog", "callback reentry", + visual.background == (Signin::SigninDialogGetStatus() == 2), + "foreground state did not follow the dialog lifetime"); + }); + Require("SigninDialog", "open", Signin::SigninDialogOpen(¶m) == 0, + "valid sign-in dialog did not open"); + Host::HostSnapshot first{}; + Require( + "SigninDialog", "visible and backgrounded", + Host::GetHostSnapshot(&first) && first.kind == Host::Kind::Signin && + Host::GetVisualState().active && Host::GetVisualState().background, + "sign-in dialog was not visible or did not background the application"); + for (int poll = 0; poll < 8; ++poll) { + Require( + "SigninDialog", "polling waits for the user", + Signin::SigninDialogUpdateStatus() == 2 && + Signin::SigninDialogGetStatus() == 2 && + Signin::SigninDialogInitialize() == already_initialized && + Signin::SigninDialogGetResult(&result) == invalid_state, + "polling silently finished sign-in or lost the active initialization"); + } + Require( + "SigninDialog", "user cancellation", + Host::HostClose(first.generation) && + Signin::SigninDialogGetResult(&result) == 0 && result.result == 1 && + result.reserved[0] == 0 && result.reserved[1] == 0 && + result.reserved[2] == 0 && !Host::GetVisualState().background && + !Host::GetVisualState().active && Signin::SigninDialogClose() == 0, + "user cancellation did not finish sign-in and restore the foreground"); + const int32_t error_param[4] = {16, static_cast(0x80550006u), 1, 0}; + Host::HostSnapshot error{}; + Require("SigninDialog", "shared presentation generations", + Error::ErrorDialogInitialize() == 0 && + Error::ErrorDialogOpen(error_param) == 0 && + Host::GetHostSnapshot(&error) && + error.kind == Host::Kind::Error && + error.generation != first.generation && + !Host::HostClose(first.generation), + "an old sign-in action affected an error dialog"); + Host::HostSnapshot overlay{}; + Require("SigninDialog", "overlapping dialogs", + Signin::SigninDialogOpen(¶m) == 0 && + Host::GetHostSnapshot(&overlay) && + overlay.kind == Host::Kind::Signin && + !Host::HostClose(error.generation) && + Host::HostClose(overlay.generation) && + Host::GetHostSnapshot(&overlay) && + overlay.kind == Host::Kind::Error && + overlay.generation == error.generation && + !Host::GetVisualState().background && + Host::HostClose(error.generation) && + Error::ErrorDialogTerminate() == 0, + "closing sign-in did not restore the pending error dialog"); + Host::HostSnapshot reopened{}; + Require( + "SigninDialog", "reopen and abort", + Signin::SigninDialogOpen(¶m) == 0 && + Host::GetHostSnapshot(&reopened) && + !Host::HostClose(first.generation) && + !Host::HostClose(error.generation) && + Signin::SigninDialogTerminate() == 0 && + !Host::HostClose(reopened.generation) && + !Host::GetVisualState().active && + !Host::GetVisualState().background && + Signin::SigninDialogTerminate() == not_initialized, + "abort did not remove the dialog or a stale host action was accepted"); + Host::SetVisibilityCallback(nullptr); + std::printf("[host] %-32s ok\n", "SigninDialogLifecycle"); +} + #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS template void ExpectFatal(const char *name, Function function) { @@ -37149,8 +37261,9 @@ int main(int argc, char **argv) { vulkan.CheckPackedTextureComponents(); return 0; } - if (argc == 2 && std::strcmp(argv[1], "--error-dialog-only") == 0) { + if (argc == 2 && std::strcmp(argv[1], "--system-dialog-only") == 0) { CheckErrorDialogLifecycle(); + CheckSigninDialogLifecycle(); return 0; } if (argc == 2 && std::strcmp(argv[1], "--fmask-only") == 0) { @@ -37716,6 +37829,7 @@ int main(int argc, char **argv) { CheckClipControlDepthClipState(); CheckReferenceClockScale(); CheckErrorDialogLifecycle(); + CheckSigninDialogLifecycle(); CheckVulkan13FeatureRequirements(); CheckPm4AcquireMemNoOp(vulkan.RuntimeRenderer()); CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer());