aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/hle/service/am/window_system.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/service/am/window_system.cpp')
-rw-r--r--src/core/hle/service/am/window_system.cpp315
1 files changed, 315 insertions, 0 deletions
diff --git a/src/core/hle/service/am/window_system.cpp b/src/core/hle/service/am/window_system.cpp
new file mode 100644
index 000000000..5cf24007c
--- /dev/null
+++ b/src/core/hle/service/am/window_system.cpp
@@ -0,0 +1,315 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.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/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::Update() {
+ std::scoped_lock lk{m_lock};
+
+ // 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()) {
+ return;
+ }
+
+ // Recursively update each applet root.
+ this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu);
+ this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application);
+}
+
+void WindowSystem::TrackApplet(std::shared_ptr<Applet> 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 (is_application) {
+ ASSERT(m_application == nullptr);
+ m_application = applet.get();
+ }
+
+ m_event_observer->TrackAppletProcess(*applet);
+ m_applets.emplace(applet->aruid.pid, std::move(applet));
+}
+
+std::shared_ptr<Applet> 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<Applet> WindowSystem::GetMainApplet() {
+ std::scoped_lock lk{m_lock};
+
+ if (m_application) {
+ return m_applets.at(m_application->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();
+ }
+}
+
+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();
+ }
+}
+
+void WindowSystem::OnHomeButtonPressed(ButtonPressDuration type) {
+ std::scoped_lock lk{m_lock};
+
+ // If we don't have a home menu, nothing to do.
+ if (!m_home_menu) {
+ return;
+ }
+
+ // Lock.
+ std::scoped_lock lk2{m_home_menu->lock};
+
+ // Send home button press event to home menu.
+ if (type == ButtonPressDuration::ShortPressing) {
+ m_home_menu->lifecycle_manager.PushUnorderedMessage(
+ AppletMessage::DetectShortPressingHomeButton);
+ }
+}
+
+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;
+ }
+
+ // 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(
+ AppletMessage::ApplicationExited);
+ }
+ }
+
+ // 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::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();
+}
+
+void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground) {
+ // 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};
+ 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;
+ }();
+
+ // Update visibility state.
+ applet->display_layer_manager.SetWindowVisibility(is_foreground && applet->window_visible);
+
+ // Update interactibility state.
+ applet->SetInteractibleLocked(is_foreground && applet->window_visible);
+
+ // 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);
+ }
+
+ // Recurse into child applets.
+ for (const auto& child_applet : applet->child_applets) {
+ this->UpdateAppletStateLocked(child_applet.get(), is_foreground);
+ }
+}
+
+} // namespace Service::AM