miroir de https://git.suyu.dev/suyu/suyu.git
316 lignes
9.6 KiB
C++
316 lignes
9.6 KiB
C++
// 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& [_, 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& [_, 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& [_, 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
|