// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include "common/settings.h" #include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/am/am_results.h" #include "core/hle/service/am/applet.h" #include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/event_observer.h" #include "core/hle/service/am/process_creation.h" #include "core/hle/service/am/window_system.h" namespace Service::AM { WindowSystem::WindowSystem(Core::System& system) : m_system(system) {} WindowSystem::~WindowSystem() { m_system.GetAppletManager().SetWindowSystem(nullptr); } void WindowSystem::SetEventObserver(EventObserver* observer) { m_event_observer = observer; m_system.GetAppletManager().SetWindowSystem(this); } void WindowSystem::RequestUpdate() { if (m_event_observer) { m_event_observer->RequestUpdate(); } } void WindowSystem::Update() { { std::scoped_lock lk{m_lock}; LOG_DEBUG(Service_AM, "called, home_menu={} application={} overlay={}", m_home_menu != nullptr, m_application != nullptr, m_overlay_display != nullptr); // Loop through all applets and remove terminated applets. this->PruneTerminatedAppletsLocked(); // If the home menu is being locked into the foreground, handle that. if (!this->LockHomeMenuIntoForegroundLocked()) { const bool overlay_takes_input = this->DoesOverlayTakeInputLocked(); this->UpdateAppletStateLocked(m_overlay_display, true, overlay_takes_input); this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_takes_input); this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_takes_input); } } this->NotifyApplicationChangedIfNeeded(); } void WindowSystem::TrackApplet(std::shared_ptr applet, bool is_application) { { std::scoped_lock lk{m_lock}; if (applet->applet_id == AppletId::QLaunch) { ASSERT(m_home_menu == nullptr); m_home_menu = applet.get(); } else if (applet->applet_id == AppletId::OverlayDisplay) { m_overlay_display = applet.get(); } else if (is_application) { ASSERT(m_application == nullptr); m_application = applet.get(); } this->UpdateCurrentApplicationLocked(); m_event_observer->TrackAppletProcess(*applet); m_applets.emplace(applet->aruid.pid, std::move(applet)); } this->NotifyApplicationChangedIfNeeded(); } void WindowSystem::UpdateCurrentApplicationLocked() { const Applet* const candidate = m_application != nullptr ? m_application : m_home_menu; if (candidate == nullptr) { return; } auto* const process = candidate->process->GetHandle(); if (process == nullptr || process == m_system.Kernel().ApplicationProcess()) { return; } LOG_INFO(Service_AM, "Current application is now {:016X}", candidate->program_id); m_system.Kernel().SetApplicationProcess(process); Settings::SetCurrentProgramID(candidate->program_id); m_pending_application_notification = candidate->program_id; } void WindowSystem::NotifyApplicationChangedIfNeeded() { std::optional program_id; { std::scoped_lock lk{m_lock}; program_id = std::exchange(m_pending_application_notification, std::nullopt); } if (!program_id.has_value()) { return; } m_system.NotifyApplicationChanged(*program_id); } std::shared_ptr WindowSystem::GetByAppletResourceUserId(u64 aruid) { std::scoped_lock lk{m_lock}; const auto it = m_applets.find(aruid); if (it == m_applets.end()) { return nullptr; } return it->second; } std::shared_ptr WindowSystem::GetMainApplet() { std::scoped_lock lk{m_lock}; if (m_application) { return m_applets.at(m_application->aruid.pid); } return nullptr; } std::shared_ptr WindowSystem::GetOverlayDisplayApplet() { std::scoped_lock lk{m_lock}; if (m_overlay_display) { return m_applets.at(m_overlay_display->aruid.pid); } return nullptr; } void WindowSystem::RequestHomeMenuToGetForeground() { { std::scoped_lock lk{m_lock}; m_foreground_requested_applet = m_home_menu; } m_event_observer->RequestUpdate(); } void WindowSystem::RequestApplicationToGetForeground() { { std::scoped_lock lk{m_lock}; m_foreground_requested_applet = m_application; } m_event_observer->RequestUpdate(); } void WindowSystem::RequestLockHomeMenuIntoForeground() { { std::scoped_lock lk{m_lock}; m_home_menu_foreground_locked = true; } m_event_observer->RequestUpdate(); } void WindowSystem::RequestUnlockHomeMenuIntoForeground() { { std::scoped_lock lk{m_lock}; m_home_menu_foreground_locked = false; } m_event_observer->RequestUpdate(); } void WindowSystem::RequestAppletVisibilityState(Applet& applet, bool visible) { { std::scoped_lock lk{applet.lock}; applet.window_visible = visible; } m_event_observer->RequestUpdate(); } void WindowSystem::OnOperationModeChanged() { std::scoped_lock lk{m_lock}; for (const auto& [aruid, applet] : m_applets) { std::scoped_lock lk2{applet->lock}; applet->lifecycle_manager.OnOperationAndPerformanceModeChanged(m_system.Kernel()); } } void WindowSystem::OnExitRequested() { std::scoped_lock lk{m_lock}; for (const auto& [aruid, applet] : m_applets) { std::scoped_lock lk2{applet->lock}; applet->lifecycle_manager.RequestExit(m_system.Kernel()); } } void WindowSystem::SendButtonAppletMessageLocked(AppletMessage message) { const auto is_blocked = [message](const Applet& applet) { if (message == AppletMessage::DetectShortPressingHomeButton && applet.applet_id == AppletId::OverlayDisplay && !applet.overlay_watching_short_home_button) { return true; } switch (message) { case AppletMessage::DetectShortPressingHomeButton: return applet.home_button_short_pressed_blocked; case AppletMessage::DetectLongPressingHomeButton: return applet.home_button_long_pressed_blocked; default: return false; } }; const auto send_to = [&](Applet* applet) { if (!applet) { return; } std::scoped_lock lk{applet->lock}; if (is_blocked(*applet)) { LOG_DEBUG(Service_AM, "Applet {} is blocking message {}", static_cast(applet->applet_id), static_cast(message)); return; } applet->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message); }; send_to(m_home_menu); send_to(m_overlay_display); send_to(m_application); if (m_event_observer) { m_event_observer->RequestUpdate(); } } void WindowSystem::OnSystemButtonPress(SystemButtonType type) { std::scoped_lock lk{m_lock}; switch (type) { case SystemButtonType::HomeButtonShortPressing: SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingHomeButton); break; case SystemButtonType::HomeButtonLongPressing: SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton); break; case SystemButtonType::CaptureButtonShortPressing: SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingCaptureButton); break; case SystemButtonType::CaptureButtonLongPressing: SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingCaptureButton); break; default: break; } } void WindowSystem::OnHomeButtonPressed(ButtonPressDuration type) { // Map duration to SystemButtonType for legacy callers switch (type) { case ButtonPressDuration::ShortPressing: OnSystemButtonPress(SystemButtonType::HomeButtonShortPressing); break; case ButtonPressDuration::MiddlePressing: case ButtonPressDuration::LongPressing: OnSystemButtonPress(SystemButtonType::HomeButtonLongPressing); break; } } void WindowSystem::PruneTerminatedAppletsLocked() { for (auto it = m_applets.begin(); it != m_applets.end(); /* ... */) { const auto& [aruid, applet] = *it; std::scoped_lock lk{applet->lock}; if (!applet->process->IsTerminated()) { // Not terminated. it = std::next(it); continue; } // A winding applet has had its own process killed but is kept alive as a transparent slot // while the reserved applet running in its place finishes (see WindAndDoReserved()). if (applet->is_winding) { if (!applet->child_applets.empty()) { it = std::next(it); continue; } const bool unwind = applet->unwind_after_reserved; applet->is_winding = false; applet->unwind_after_reserved = false; if (unwind && this->RestartAppletProcessLocked(applet.get())) { const u64 new_aruid = applet->aruid.pid; const auto next = std::next(it); if (new_aruid != aruid) { auto node = m_applets.extract(it); node.key() = new_aruid; m_applets.insert(std::move(node)); } applet->process->Run(); m_event_observer->RequestUpdate(); it = next; continue; } applet->reserved_applet.reset(); } // Terminated, so ensure all child applets are terminated. if (!applet->child_applets.empty()) { this->TerminateChildAppletsLocked(applet.get()); // Not ready to unlink until all child applets are terminated. it = std::next(it); continue; } // Erase from caller applet's list of children. if (auto caller_applet = applet->caller_applet.lock(); caller_applet) { std::scoped_lock lk2{caller_applet->lock}; std::erase(caller_applet->child_applets, applet); applet->caller_applet.reset(); // We don't need to update the activity state of the caller applet yet. // It will be recalculated once we fall out of the termination handling path. } // If this applet was foreground, it no longer is. if (applet.get() == m_foreground_requested_applet) { m_foreground_requested_applet = nullptr; } // If this was the home menu, we should clean up. if (applet.get() == m_home_menu) { m_home_menu = nullptr; m_foreground_requested_applet = m_application; } // If this was the application, we should try to switch to the home menu. if (applet.get() == m_application) { m_application = nullptr; m_foreground_requested_applet = m_home_menu; // If we have a home menu, send it the application exited message. if (m_home_menu) { m_home_menu->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), AppletMessage::ApplicationExited); } } if (applet.get() == m_overlay_display) { m_overlay_display = nullptr; } this->UpdateCurrentApplicationLocked(); // Finalize applet. applet->OnProcessTerminatedLocked(); // Request update to ensure quiescence. m_event_observer->RequestUpdate(); // Unlink and advance. it = m_applets.erase(it); } // If the last applet has exited, exit the system. if (m_applets.empty()) { m_system.Exit(); } } bool WindowSystem::RestartAppletProcessLocked(Applet* applet) { if (!ReinitializeProcess(m_system, *applet->process, applet->program_id)) { LOG_ERROR(Service_AM, "Failed to restart winding applet_id={}", static_cast(applet->applet_id)); return false; } applet->aruid.pid = applet->process->GetProcessId(); applet->is_process_running = false; applet->is_completed = false; applet->hid_registration.RegisterCurrentProcess(); applet->lifecycle_manager.ResetForRelaunch(); applet->is_activity_runnable = false; applet->launch_reason.flag = 1; m_event_observer->TrackAppletProcess(*applet); return true; } bool WindowSystem::LockHomeMenuIntoForegroundLocked() { // If the home menu is not locked into foreground, then there's nothing to do. if (m_home_menu == nullptr || !m_home_menu_foreground_locked) { m_home_menu_foreground_locked = false; return false; } // Terminate any direct child applets of the home menu. std::scoped_lock lk{m_home_menu->lock}; this->TerminateChildAppletsLocked(m_home_menu); // When there are zero child applets left, we can proceed with the update. if (m_home_menu->child_applets.empty()) { m_home_menu->window_visible = true; m_foreground_requested_applet = m_home_menu; return false; } return true; } void WindowSystem::TerminateChildAppletsLocked(Applet* applet) { auto child_applets = applet->child_applets; applet->lock.unlock(); for (const auto& child_applet : child_applets) { std::scoped_lock lk{child_applet->lock}; child_applet->process->Terminate(); child_applet->terminate_result = AM::ResultLibraryAppletTerminated; } applet->lock.lock(); } bool WindowSystem::IsOverlayOpenLocked(const Applet& overlay) const { return overlay.window_visible && overlay.overlay_watching_short_home_button; } bool WindowSystem::DoesOverlayTakeInputLocked() const { if (m_overlay_display == nullptr) { return false; } std::scoped_lock lk{m_overlay_display->lock}; return this->IsOverlayOpenLocked(*m_overlay_display); } void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_takes_input) { // With no applet, we don't have anything to do. if (!applet) { return; } std::scoped_lock lk{applet->lock}; const bool inherited_foreground = applet->is_process_running && is_foreground; const auto visible_state = inherited_foreground ? ActivityState::ForegroundVisible : ActivityState::BackgroundVisible; const auto obscured_state = inherited_foreground ? ActivityState::ForegroundObscured : ActivityState::BackgroundObscured; const bool has_obscuring_child_applets = [&] { for (const auto& child_applet : applet->child_applets) { std::scoped_lock lk2{child_applet->lock}; if (child_applet->is_winding) { return true; } const auto mode = child_applet->library_applet_mode; if (child_applet->is_process_running && child_applet->window_visible && (mode == LibraryAppletMode::AllForeground || mode == LibraryAppletMode::AllForegroundInitiallyHidden)) { return true; } } return false; }(); const bool is_overlay = applet->applet_id == AppletId::OverlayDisplay; // Update visibility state. const bool should_be_visible = is_overlay ? applet->window_visible : (is_foreground && applet->window_visible); applet->display_layer_manager.SetWindowVisibility(should_be_visible); const bool needs_hid_input = is_overlay ? this->IsOverlayOpenLocked(*applet) : (is_foreground && applet->window_visible && !overlay_takes_input); applet->SetInteractibleLocked(needs_hid_input, needs_hid_input); // Update focus state and suspension. const bool is_obscured = has_obscuring_child_applets || !applet->window_visible; const auto state = applet->lifecycle_manager.GetActivityState(); if (is_obscured && state != obscured_state) { // Set obscured state. applet->lifecycle_manager.SetActivityState(obscured_state); applet->UpdateSuspensionStateLocked(true); } else if (!is_obscured && state != visible_state) { // Set visible state. applet->lifecycle_manager.SetActivityState(visible_state); applet->UpdateSuspensionStateLocked(true); } // Layer ordering. Composition sorts back-to-front. Now with enums for calrity. s32 z_index = Background; if (is_overlay) { z_index = this->IsOverlayOpenLocked(*applet) ? Overlay : Background; } else if (inherited_foreground) { z_index = is_obscured ? Foreground : ForegroundVisible; } applet->display_layer_manager.SetOverlayZIndex(z_index); // Recurse into child applets. for (const auto& child_applet : applet->child_applets) { this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_takes_input); } } } // namespace Service::AM