mirror of https://git.suyu.dev/suyu/suyu
Port changes from Early Access
parent
27b3044879
commit
362f2047b4
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit a1ac3750f9a639b5a6c6d6c7da4259b8d6790989
|
||||
Subproject commit 9c0f5d3ecb06d2c93c2b59becb9b3b763213e74e
|
@ -1,27 +1,71 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/scope_exit.h"
|
||||
|
||||
#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"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
Applet::Applet(Core::System& system, std::unique_ptr<Process> process_)
|
||||
: context(system, "Applet"), message_queue(system), process(std::move(process_)),
|
||||
hid_registration(system, *process), gpu_error_detected_event(context),
|
||||
friend_invitation_storage_channel_event(context), notification_storage_channel_event(context),
|
||||
health_warning_disappeared_system_event(context), acquired_sleep_lock_event(context),
|
||||
pop_from_general_channel_event(context), library_applet_launchable_event(context),
|
||||
accumulated_suspended_tick_changed_event(context), sleep_lock_event(context) {
|
||||
Applet::Applet(Core::System& system, std::unique_ptr<Process> process_, bool is_application)
|
||||
: context(system, "Applet"), lifecycle_manager(system, context, is_application),
|
||||
process(std::move(process_)), hid_registration(system, *process),
|
||||
gpu_error_detected_event(context), friend_invitation_storage_channel_event(context),
|
||||
notification_storage_channel_event(context), health_warning_disappeared_system_event(context),
|
||||
acquired_sleep_lock_event(context), pop_from_general_channel_event(context),
|
||||
library_applet_launchable_event(context), accumulated_suspended_tick_changed_event(context),
|
||||
sleep_lock_event(context), state_changed_event(context) {
|
||||
|
||||
aruid = process->GetProcessId();
|
||||
aruid.pid = process->GetProcessId();
|
||||
program_id = process->GetProgramId();
|
||||
}
|
||||
|
||||
Applet::~Applet() = default;
|
||||
|
||||
void Applet::UpdateSuspensionStateLocked(bool force_message) {
|
||||
// Remove any forced resumption.
|
||||
lifecycle_manager.RemoveForceResumeIfPossible();
|
||||
|
||||
// Check if we're runnable.
|
||||
const bool curr_activity_runnable = lifecycle_manager.IsRunnable();
|
||||
const bool prev_activity_runnable = is_activity_runnable;
|
||||
const bool was_changed = curr_activity_runnable != prev_activity_runnable;
|
||||
|
||||
if (was_changed) {
|
||||
if (curr_activity_runnable) {
|
||||
process->Suspend(false);
|
||||
} else {
|
||||
process->Suspend(true);
|
||||
lifecycle_manager.RequestResumeNotification();
|
||||
}
|
||||
|
||||
is_activity_runnable = curr_activity_runnable;
|
||||
}
|
||||
|
||||
if (lifecycle_manager.GetForcedSuspend()) {
|
||||
// TODO: why is this allowed?
|
||||
return;
|
||||
}
|
||||
|
||||
// Signal if the focus state was changed or the process state was changed.
|
||||
if (lifecycle_manager.UpdateRequestedFocusState() || was_changed || force_message) {
|
||||
lifecycle_manager.SignalSystemEventIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
void Applet::SetInteractibleLocked(bool interactible) {
|
||||
if (is_interactible == interactible) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_interactible = interactible;
|
||||
|
||||
hid_registration.EnableAppletToGetInput(interactible && !lifecycle_manager.GetExitRequested());
|
||||
}
|
||||
|
||||
void Applet::OnProcessTerminatedLocked() {
|
||||
is_completed = true;
|
||||
state_changed_event.Signal();
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
||||
|
@ -1,73 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/am/applet_message_queue.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
AppletMessageQueue::AppletMessageQueue(Core::System& system)
|
||||
: service_context{system, "AppletMessageQueue"} {
|
||||
on_new_message = service_context.CreateEvent("AMMessageQueue:OnMessageReceived");
|
||||
on_operation_mode_changed = service_context.CreateEvent("AMMessageQueue:OperationModeChanged");
|
||||
}
|
||||
|
||||
AppletMessageQueue::~AppletMessageQueue() {
|
||||
service_context.CloseEvent(on_new_message);
|
||||
service_context.CloseEvent(on_operation_mode_changed);
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& AppletMessageQueue::GetMessageReceiveEvent() {
|
||||
return on_new_message->GetReadableEvent();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& AppletMessageQueue::GetOperationModeChangedEvent() {
|
||||
return on_operation_mode_changed->GetReadableEvent();
|
||||
}
|
||||
|
||||
void AppletMessageQueue::PushMessage(AppletMessage msg) {
|
||||
{
|
||||
std::scoped_lock lk{lock};
|
||||
messages.push(msg);
|
||||
}
|
||||
on_new_message->Signal();
|
||||
}
|
||||
|
||||
AppletMessage AppletMessageQueue::PopMessage() {
|
||||
std::scoped_lock lk{lock};
|
||||
if (messages.empty()) {
|
||||
on_new_message->Clear();
|
||||
return AppletMessage::None;
|
||||
}
|
||||
auto msg = messages.front();
|
||||
messages.pop();
|
||||
if (messages.empty()) {
|
||||
on_new_message->Clear();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::size_t AppletMessageQueue::GetMessageCount() const {
|
||||
std::scoped_lock lk{lock};
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
void AppletMessageQueue::RequestExit() {
|
||||
PushMessage(AppletMessage::Exit);
|
||||
}
|
||||
|
||||
void AppletMessageQueue::RequestResume() {
|
||||
PushMessage(AppletMessage::Resume);
|
||||
}
|
||||
|
||||
void AppletMessageQueue::FocusStateChanged() {
|
||||
PushMessage(AppletMessage::FocusStateChanged);
|
||||
}
|
||||
|
||||
void AppletMessageQueue::OperationModeChanged() {
|
||||
PushMessage(AppletMessage::OperationModeChanged);
|
||||
PushMessage(AppletMessage::PerformanceModeChanged);
|
||||
on_operation_mode_changed->Signal();
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
class AppletMessageQueue {
|
||||
public:
|
||||
explicit AppletMessageQueue(Core::System& system);
|
||||
~AppletMessageQueue();
|
||||
|
||||
Kernel::KReadableEvent& GetMessageReceiveEvent();
|
||||
Kernel::KReadableEvent& GetOperationModeChangedEvent();
|
||||
void PushMessage(AppletMessage msg);
|
||||
AppletMessage PopMessage();
|
||||
std::size_t GetMessageCount() const;
|
||||
void RequestExit();
|
||||
void RequestResume();
|
||||
void FocusStateChanged();
|
||||
void OperationModeChanged();
|
||||
|
||||
private:
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
Kernel::KEvent* on_new_message;
|
||||
Kernel::KEvent* on_operation_mode_changed;
|
||||
|
||||
mutable std::mutex lock;
|
||||
std::queue<AppletMessage> messages;
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,89 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/am/button_poller.h"
|
||||
#include "core/hle/service/am/window_system.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
namespace {
|
||||
|
||||
ButtonPressDuration ClassifyPressDuration(std::chrono::steady_clock::time_point start) {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
const auto dur = std::chrono::steady_clock::now() - start;
|
||||
|
||||
// TODO: determine actual thresholds
|
||||
// TODO: these are likely different for each button
|
||||
if (dur < 500ms) {
|
||||
return ButtonPressDuration::ShortPressing;
|
||||
} else if (dur < 1000ms) {
|
||||
return ButtonPressDuration::MiddlePressing;
|
||||
} else {
|
||||
return ButtonPressDuration::LongPressing;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system)
|
||||
: m_window_system(window_system) {
|
||||
// TODO: am reads this from the home button state in hid, which is controller-agnostic.
|
||||
Core::HID::ControllerUpdateCallback engine_callback{
|
||||
.on_change =
|
||||
[this](Core::HID::ControllerTriggerType type) {
|
||||
if (type == Core::HID::ControllerTriggerType::Button) {
|
||||
this->OnButtonStateChanged();
|
||||
}
|
||||
},
|
||||
.is_npad_service = true,
|
||||
};
|
||||
|
||||
m_handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
m_handheld_key = m_handheld->SetCallback(engine_callback);
|
||||
m_player1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
m_player1_key = m_player1->SetCallback(engine_callback);
|
||||
}
|
||||
|
||||
ButtonPoller::~ButtonPoller() {
|
||||
m_handheld->DeleteCallback(m_handheld_key);
|
||||
m_player1->DeleteCallback(m_player1_key);
|
||||
}
|
||||
|
||||
void ButtonPoller::OnButtonStateChanged() {
|
||||
const bool home_button =
|
||||
m_handheld->GetHomeButtons().home.Value() || m_player1->GetHomeButtons().home.Value();
|
||||
const bool capture_button = m_handheld->GetCaptureButtons().capture.Value() ||
|
||||
m_player1->GetCaptureButtons().capture.Value();
|
||||
|
||||
// Buttons pressed which were not previously pressed
|
||||
if (home_button && !m_home_button_press_start) {
|
||||
m_home_button_press_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
if (capture_button && !m_capture_button_press_start) {
|
||||
m_capture_button_press_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
// if (power_button && !m_power_button_press_start) {
|
||||
// m_power_button_press_start = std::chrono::steady_clock::now();
|
||||
// }
|
||||
|
||||
// Buttons released which were previously held
|
||||
if (!home_button && m_home_button_press_start) {
|
||||
m_window_system.OnHomeButtonPressed(ClassifyPressDuration(*m_home_button_press_start));
|
||||
m_home_button_press_start = std::nullopt;
|
||||
}
|
||||
if (!capture_button && m_capture_button_press_start) {
|
||||
// TODO
|
||||
m_capture_button_press_start = std::nullopt;
|
||||
}
|
||||
// if (!power_button && m_power_button_press_start) {
|
||||
// // TODO
|
||||
// m_power_button_press_start = std::nullopt;
|
||||
// }
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
|
||||
namespace Core {
|
||||
namespace HID {
|
||||
class EmulatedController;
|
||||
}
|
||||
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
class WindowSystem;
|
||||
|
||||
class ButtonPoller {
|
||||
public:
|
||||
explicit ButtonPoller(Core::System& system, WindowSystem& window_system);
|
||||
~ButtonPoller();
|
||||
|
||||
private:
|
||||
void OnButtonStateChanged();
|
||||
|
||||
private:
|
||||
WindowSystem& m_window_system;
|
||||
|
||||
Core::HID::EmulatedController* m_handheld{};
|
||||
int m_handheld_key{};
|
||||
Core::HID::EmulatedController* m_player1{};
|
||||
int m_player1_key{};
|
||||
|
||||
std::optional<std::chrono::steady_clock::time_point> m_home_button_press_start{};
|
||||
std::optional<std::chrono::steady_clock::time_point> m_capture_button_press_start{};
|
||||
std::optional<std::chrono::steady_clock::time_point> m_power_button_press_start{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,162 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/am/applet.h"
|
||||
#include "core/hle/service/am/event_observer.h"
|
||||
#include "core/hle/service/am/window_system.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
enum class UserDataTag : u32 {
|
||||
WakeupEvent,
|
||||
AppletProcess,
|
||||
};
|
||||
|
||||
EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
|
||||
: m_system(system), m_context(system, "am:EventObserver"), m_window_system(window_system),
|
||||
m_wakeup_event(m_context), m_wakeup_holder(m_wakeup_event.GetHandle()) {
|
||||
m_window_system.SetEventObserver(this);
|
||||
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
|
||||
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
|
||||
m_thread = std::thread([&] { this->ThreadFunc(); });
|
||||
}
|
||||
|
||||
EventObserver::~EventObserver() {
|
||||
// Signal thread and wait for processing to finish.
|
||||
m_stop_source.request_stop();
|
||||
m_wakeup_event.Signal();
|
||||
m_thread.join();
|
||||
|
||||
// Free remaining owned sessions.
|
||||
auto it = m_process_holder_list.begin();
|
||||
while (it != m_process_holder_list.end()) {
|
||||
// Get the holder.
|
||||
auto* const holder = std::addressof(*it);
|
||||
|
||||
// Remove from the list.
|
||||
it = m_process_holder_list.erase(it);
|
||||
|
||||
// Free the holder.
|
||||
delete holder;
|
||||
}
|
||||
}
|
||||
|
||||
void EventObserver::TrackAppletProcess(Applet& applet) {
|
||||
// Don't observe dummy processes.
|
||||
if (!applet.process->IsInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate new holder.
|
||||
auto* holder = new ProcessHolder(applet, *applet.process);
|
||||
holder->SetUserData(static_cast<uintptr_t>(UserDataTag::AppletProcess));
|
||||
|
||||
// Insert into list.
|
||||
{
|
||||
std::scoped_lock lk{m_lock};
|
||||
m_process_holder_list.push_back(*holder);
|
||||
holder->LinkToMultiWait(std::addressof(m_deferred_wait_list));
|
||||
}
|
||||
|
||||
// Signal wakeup.
|
||||
m_wakeup_event.Signal();
|
||||
}
|
||||
|
||||
void EventObserver::RequestUpdate() {
|
||||
m_wakeup_event.Signal();
|
||||
}
|
||||
|
||||
void EventObserver::LinkDeferred() {
|
||||
std::scoped_lock lk{m_lock};
|
||||
m_multi_wait.MoveAll(std::addressof(m_deferred_wait_list));
|
||||
}
|
||||
|
||||
MultiWaitHolder* EventObserver::WaitSignaled() {
|
||||
while (true) {
|
||||
this->LinkDeferred();
|
||||
|
||||
// If we're done, return before we start waiting.
|
||||
if (m_stop_source.stop_requested()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
|
||||
if (selected != std::addressof(m_wakeup_holder)) {
|
||||
// Unlink the process.
|
||||
selected->UnlinkFromMultiWait();
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
|
||||
void EventObserver::Process(MultiWaitHolder* holder) {
|
||||
switch (static_cast<UserDataTag>(holder->GetUserData())) {
|
||||
case UserDataTag::WakeupEvent:
|
||||
this->OnWakeupEvent(holder);
|
||||
break;
|
||||
case UserDataTag::AppletProcess:
|
||||
this->OnProcessEvent(static_cast<ProcessHolder*>(holder));
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void EventObserver::OnWakeupEvent(MultiWaitHolder* holder) {
|
||||
m_wakeup_event.Clear();
|
||||
|
||||
// Perform recalculation.
|
||||
m_window_system.Update();
|
||||
}
|
||||
|
||||
void EventObserver::OnProcessEvent(ProcessHolder* holder) {
|
||||
// Check process state.
|
||||
auto& applet = holder->GetApplet();
|
||||
auto& process = holder->GetProcess();
|
||||
|
||||
{
|
||||
std::scoped_lock lk{m_lock, applet.lock};
|
||||
if (process.IsTerminated()) {
|
||||
// Destroy the holder.
|
||||
this->DestroyAppletProcessHolderLocked(holder);
|
||||
} else {
|
||||
// Reset signaled state.
|
||||
process.ResetSignal();
|
||||
|
||||
// Relink wakeup event.
|
||||
holder->LinkToMultiWait(std::addressof(m_deferred_wait_list));
|
||||
}
|
||||
|
||||
// Set running.
|
||||
applet.is_process_running = process.IsRunning();
|
||||
}
|
||||
|
||||
// Perform recalculation.
|
||||
m_window_system.Update();
|
||||
}
|
||||
|
||||
void EventObserver::DestroyAppletProcessHolderLocked(ProcessHolder* holder) {
|
||||
// Remove from owned list.
|
||||
m_process_holder_list.erase(m_process_holder_list.iterator_to(*holder));
|
||||
|
||||
// Destroy and free.
|
||||
delete holder;
|
||||
}
|
||||
|
||||
void EventObserver::ThreadFunc() {
|
||||
Common::SetCurrentThreadName("am:EventObserver");
|
||||
|
||||
while (true) {
|
||||
auto* signaled_holder = this->WaitSignaled();
|
||||
if (!signaled_holder) {
|
||||
break;
|
||||
}
|
||||
|
||||
this->Process(signaled_holder);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,74 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/os/event.h"
|
||||
#include "core/hle/service/os/multi_wait.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
struct Applet;
|
||||
class ProcessHolder;
|
||||
class WindowSystem;
|
||||
|
||||
class EventObserver {
|
||||
public:
|
||||
explicit EventObserver(Core::System& system, WindowSystem& window_system);
|
||||
~EventObserver();
|
||||
|
||||
void TrackAppletProcess(Applet& applet);
|
||||
void RequestUpdate();
|
||||
|
||||
private:
|
||||
void LinkDeferred();
|
||||
MultiWaitHolder* WaitSignaled();
|
||||
void Process(MultiWaitHolder* holder);
|
||||
bool WaitAndProcessImpl();
|
||||
void LoopProcess();
|
||||
|
||||
private:
|
||||
void OnWakeupEvent(MultiWaitHolder* holder);
|
||||
void OnProcessEvent(ProcessHolder* holder);
|
||||
|
||||
private:
|
||||
void DestroyAppletProcessHolderLocked(ProcessHolder* holder);
|
||||
|
||||
private:
|
||||
void ThreadFunc();
|
||||
|
||||
private:
|
||||
// System reference and context.
|
||||
Core::System& m_system;
|
||||
KernelHelpers::ServiceContext m_context;
|
||||
|
||||
// Window manager.
|
||||
WindowSystem& m_window_system;
|
||||
|
||||
// Guest event handle to wake up the event loop processor.
|
||||
Event m_wakeup_event;
|
||||
MultiWaitHolder m_wakeup_holder;
|
||||
|
||||
// Mutex to protect remaining members.
|
||||
std::mutex m_lock{};
|
||||
|
||||
// List of owned process holders.
|
||||
Common::IntrusiveListBaseTraits<ProcessHolder>::ListType m_process_holder_list;
|
||||
|
||||
// Multi-wait objects for new tasks.
|
||||
MultiWait m_multi_wait;
|
||||
MultiWait m_deferred_wait_list;
|
||||
|
||||
// Processing thread.
|
||||
std::thread m_thread{};
|
||||
std::stop_source m_stop_source{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,379 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/service/am/lifecycle_manager.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
LifecycleManager::LifecycleManager(Core::System& system, KernelHelpers::ServiceContext& context,
|
||||
bool is_application)
|
||||
: m_system_event(context), m_operation_mode_changed_system_event(context),
|
||||
m_is_application(is_application) {}
|
||||
|
||||
LifecycleManager::~LifecycleManager() = default;
|
||||
|
||||
Event& LifecycleManager::GetSystemEvent() {
|
||||
return m_system_event;
|
||||
}
|
||||
|
||||
Event& LifecycleManager::GetOperationModeChangedSystemEvent() {
|
||||
return m_operation_mode_changed_system_event;
|
||||
}
|
||||
|
||||
void LifecycleManager::PushUnorderedMessage(AppletMessage message) {
|
||||
m_unordered_messages.push_back(message);
|
||||
this->SignalSystemEventIfNeeded();
|
||||
}
|
||||
|
||||
AppletMessage LifecycleManager::PopMessageInOrderOfPriority() {
|
||||
if (m_has_resume) {
|
||||
m_has_resume = false;
|
||||
return AppletMessage::Resume;
|
||||
}
|
||||
|
||||
if (m_has_acknowledged_exit != m_has_requested_exit) {
|
||||
m_has_acknowledged_exit = m_has_requested_exit;
|
||||
return AppletMessage::Exit;
|
||||
}
|
||||
|
||||
if (m_focus_state_changed_notification_enabled) {
|
||||
if (!m_is_application) {
|
||||
if (m_requested_focus_state != m_acknowledged_focus_state) {
|
||||
m_acknowledged_focus_state = m_requested_focus_state;
|
||||
switch (m_requested_focus_state) {
|
||||
case FocusState::InFocus:
|
||||
return AppletMessage::ChangeIntoForeground;
|
||||
case FocusState::NotInFocus:
|
||||
return AppletMessage::ChangeIntoBackground;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
}
|
||||
} else if (m_has_focus_state_changed) {
|
||||
m_has_focus_state_changed = false;
|
||||
return AppletMessage::FocusStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_has_requested_request_to_prepare_sleep != m_has_acknowledged_request_to_prepare_sleep) {
|
||||
m_has_acknowledged_request_to_prepare_sleep = true;
|
||||
return AppletMessage::RequestToPrepareSleep;
|
||||
}
|
||||
|
||||
if (m_requested_request_to_display_state != m_acknowledged_request_to_display_state) {
|
||||
m_acknowledged_request_to_display_state = m_requested_request_to_display_state;
|
||||
return AppletMessage::RequestToDisplay;
|
||||
}
|
||||
|
||||
if (m_has_operation_mode_changed) {
|
||||
m_has_operation_mode_changed = false;
|
||||
return AppletMessage::OperationModeChanged;
|
||||
}
|
||||
|
||||
if (m_has_performance_mode_changed) {
|
||||
m_has_performance_mode_changed = false;
|
||||
return AppletMessage::PerformanceModeChanged;
|
||||
}
|
||||
|
||||
if (m_has_sd_card_removed) {
|
||||
m_has_sd_card_removed = false;
|
||||
return AppletMessage::SdCardRemoved;
|
||||
}
|
||||
|
||||
if (m_has_sleep_required_by_high_temperature) {
|
||||
m_has_sleep_required_by_high_temperature = false;
|
||||
return AppletMessage::SleepRequiredByHighTemperature;
|
||||
}
|
||||
|
||||
if (m_has_sleep_required_by_low_battery) {
|
||||
m_has_sleep_required_by_low_battery = false;
|
||||
return AppletMessage::SleepRequiredByLowBattery;
|
||||
}
|
||||
|
||||
if (m_has_auto_power_down) {
|
||||
m_has_auto_power_down = false;
|
||||
return AppletMessage::AutoPowerDown;
|
||||
}
|
||||
|
||||
if (m_has_album_screen_shot_taken) {
|
||||
m_has_album_screen_shot_taken = false;
|
||||
return AppletMessage::AlbumScreenShotTaken;
|
||||
}
|
||||
|
||||
if (m_has_album_recording_saved) {
|
||||
m_has_album_recording_saved = false;
|
||||
return AppletMessage::AlbumRecordingSaved;
|
||||
}
|
||||
|
||||
if (!m_unordered_messages.empty()) {
|
||||
const auto message = m_unordered_messages.front();
|
||||
m_unordered_messages.pop_front();
|
||||
return message;
|
||||
}
|
||||
|
||||
return AppletMessage::None;
|
||||
}
|
||||
|
||||
bool LifecycleManager::ShouldSignalSystemEvent() {
|
||||
if (m_focus_state_changed_notification_enabled) {
|
||||
if (!m_is_application) {
|
||||
if (m_requested_focus_state != m_acknowledged_focus_state) {
|
||||
return true;
|
||||
}
|
||||
} else if (m_has_focus_state_changed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return !m_unordered_messages.empty() || m_has_resume ||
|
||||
(m_has_requested_exit != m_has_acknowledged_exit) ||
|
||||
(m_has_requested_request_to_prepare_sleep !=
|
||||
m_has_acknowledged_request_to_prepare_sleep) ||
|
||||
m_has_operation_mode_changed || m_has_performance_mode_changed ||
|
||||
m_has_sd_card_removed || m_has_sleep_required_by_high_temperature ||
|
||||
m_has_sleep_required_by_low_battery || m_has_auto_power_down ||
|
||||
(m_requested_request_to_display_state != m_acknowledged_request_to_display_state) ||
|
||||
m_has_album_screen_shot_taken || m_has_album_recording_saved;
|
||||
}
|
||||
|
||||
void LifecycleManager::OnOperationAndPerformanceModeChanged() {
|
||||
if (m_operation_mode_changed_notification_enabled) {
|
||||
m_has_operation_mode_changed = true;
|
||||
}
|
||||
if (m_performance_mode_changed_notification_enabled) {
|
||||
m_has_performance_mode_changed = true;
|
||||
}
|
||||
m_operation_mode_changed_system_event.Signal();
|
||||
this->SignalSystemEventIfNeeded();
|
||||
}
|
||||
|
||||
void LifecycleManager::SignalSystemEventIfNeeded() {
|
||||
// Check our cached value for the system event.
|
||||
const bool applet_message_available = m_applet_message_available;
|
||||
|
||||
// If it's not current, we need to do an update, either clearing or signaling.
|
||||
if (applet_message_available != this->ShouldSignalSystemEvent()) {
|
||||
if (!applet_message_available) {
|
||||
m_system_event.Signal();
|
||||
m_applet_message_available = true;
|
||||
} else {
|
||||
m_system_event.Clear();
|
||||
m_applet_message_available = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LifecycleManager::PopMessage(AppletMessage* out_message) {
|
||||
const auto message = this->PopMessageInOrderOfPriority();
|
||||
this->SignalSystemEventIfNeeded();
|
||||
|
||||
*out_message = message;
|
||||
return message != AppletMessage::None;
|
||||
}
|
||||
|
||||
void LifecycleManager::SetFocusHandlingMode(bool suspend) {
|
||||
switch (m_focus_handling_mode) {
|
||||
case FocusHandlingMode::AlwaysSuspend:
|
||||
case FocusHandlingMode::SuspendHomeSleep:
|
||||
if (!suspend) {
|
||||
// Disallow suspension.
|
||||
m_focus_handling_mode = FocusHandlingMode::NoSuspend;
|
||||
}
|
||||
break;
|
||||
case FocusHandlingMode::NoSuspend:
|
||||
if (suspend) {
|
||||
// Allow suspension temporally.
|
||||
m_focus_handling_mode = FocusHandlingMode::SuspendHomeSleep;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LifecycleManager::SetOutOfFocusSuspendingEnabled(bool enabled) {
|
||||
switch (m_focus_handling_mode) {
|
||||
case FocusHandlingMode::AlwaysSuspend:
|
||||
if (!enabled) {
|
||||
// Allow suspension temporally.
|
||||
m_focus_handling_mode = FocusHandlingMode::SuspendHomeSleep;
|
||||
}
|
||||
break;
|
||||
case FocusHandlingMode::SuspendHomeSleep:
|
||||
case FocusHandlingMode::NoSuspend:
|
||||
if (enabled) {
|
||||
// Allow suspension.
|
||||
m_focus_handling_mode = FocusHandlingMode::AlwaysSuspend;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LifecycleManager::RemoveForceResumeIfPossible() {
|
||||
// If resume is not forced, we have nothing to do.
|
||||
if (m_suspend_mode != SuspendMode::ForceResume) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check activity state.
|
||||
// If we are already resumed, we can remove the forced state.
|
||||
switch (m_activity_state) {
|
||||
case ActivityState::ForegroundVisible:
|
||||
case ActivityState::ForegroundObscured:
|
||||
m_suspend_mode = SuspendMode::NoOverride;
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check focus handling mode.
|
||||
switch (m_focus_handling_mode) {
|
||||
case FocusHandlingMode::AlwaysSuspend:
|
||||
case FocusHandlingMode::SuspendHomeSleep:
|
||||
// If the applet allows suspension, we can remove the forced state.
|
||||
m_suspend_mode = SuspendMode::NoOverride;
|
||||
break;
|
||||
|
||||
case FocusHandlingMode::NoSuspend:
|
||||
// If the applet is not an application, we can remove the forced state.
|
||||
// Only applications can be forced to resume.
|
||||
if (!m_is_application) {
|
||||
m_suspend_mode = SuspendMode::NoOverride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LifecycleManager::IsRunnable() const {
|
||||
// If suspend is forced, return that.
|
||||
if (m_forced_suspend) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check suspend mode override.
|
||||
switch (m_suspend_mode) {
|
||||
case SuspendMode::NoOverride:
|
||||
// Continue processing.
|
||||
break;
|
||||
|
||||
case SuspendMode::ForceResume:
|
||||
// The applet is runnable during forced resumption when its exit is requested.
|
||||
return m_has_requested_exit;
|
||||
|
||||
case SuspendMode::ForceSuspend:
|
||||
// The applet is never runnable during forced suspension.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always run if exit is requested.
|
||||
if (m_has_requested_exit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_activity_state == ActivityState::ForegroundVisible) {
|
||||
// The applet is runnable now.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_activity_state == ActivityState::ForegroundObscured) {
|
||||
switch (m_focus_handling_mode) {
|
||||
case FocusHandlingMode::AlwaysSuspend:
|
||||
// The applet is not runnable while running the applet.
|
||||
return false;
|
||||
|
||||
case FocusHandlingMode::SuspendHomeSleep:
|
||||
// The applet is runnable while running the applet.
|
||||
return true;
|
||||
|
||||
case FocusHandlingMode::NoSuspend:
|
||||
// The applet is always runnable.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The activity is a suspended one.
|
||||
// The applet should be suspended unless it has disabled suspension.
|
||||
return m_focus_handling_mode == FocusHandlingMode::NoSuspend;
|
||||
}
|
||||
|
||||
FocusState LifecycleManager::GetFocusStateWhileForegroundObscured() const {
|
||||
switch (m_focus_handling_mode) {
|
||||
case FocusHandlingMode::AlwaysSuspend:
|
||||
// The applet never learns it has lost focus.
|
||||
return FocusState::InFocus;
|
||||
|
||||
case FocusHandlingMode::SuspendHomeSleep:
|
||||
// The applet learns it has lost focus when launching a child applet.
|
||||
return FocusState::NotInFocus;
|
||||
|
||||
case FocusHandlingMode::NoSuspend:
|
||||
// The applet always learns it has lost focus.
|
||||
return FocusState::NotInFocus;
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
FocusState LifecycleManager::GetFocusStateWhileBackground(bool is_obscured) const {
|
||||
switch (m_focus_handling_mode) {
|
||||
case FocusHandlingMode::AlwaysSuspend:
|
||||
// The applet never learns it has lost focus.
|
||||
return FocusState::InFocus;
|
||||
|
||||
case FocusHandlingMode::SuspendHomeSleep:
|
||||
// The applet learns it has lost focus when launching a child applet.
|
||||
return is_obscured ? FocusState::NotInFocus : FocusState::InFocus;
|
||||
|
||||
case FocusHandlingMode::NoSuspend:
|
||||
// The applet always learns it has lost focus.
|
||||
return m_is_application ? FocusState::Background : FocusState::NotInFocus;
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
bool LifecycleManager::UpdateRequestedFocusState() {
|
||||
FocusState new_state{};
|
||||
|
||||
if (m_suspend_mode == SuspendMode::NoOverride) {
|
||||
// With no forced suspend or resume, we take the focus state designated
|
||||
// by the combination of the activity flag and the focus handling mode.
|
||||
switch (m_activity_state) {
|
||||
case ActivityState::ForegroundVisible:
|
||||
new_state = FocusState::InFocus;
|
||||
break;
|
||||
|
||||
case ActivityState::ForegroundObscured:
|
||||
new_state = this->GetFocusStateWhileForegroundObscured();
|
||||
break;
|
||||
|
||||
case ActivityState::BackgroundVisible:
|
||||
new_state = this->GetFocusStateWhileBackground(false);
|
||||
break;
|
||||
|
||||
case ActivityState::BackgroundObscured:
|
||||
new_state = this->GetFocusStateWhileBackground(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
} else {
|
||||
// With forced suspend or resume, the applet is guaranteed to be background.
|
||||
new_state = this->GetFocusStateWhileBackground(false);
|
||||
}
|
||||
|
||||
if (new_state != m_requested_focus_state) {
|
||||
// Mark the focus state as ready for update.
|
||||
m_requested_focus_state = new_state;
|
||||
|
||||
// We changed the focus state.
|
||||
return true;
|
||||
}
|
||||
|
||||
// We didn't change the focus state.
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,183 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
#include "core/hle/service/os/event.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
enum class ActivityState : u32 {
|
||||
ForegroundVisible = 0,
|
||||
ForegroundObscured = 1,
|
||||
BackgroundVisible = 2,
|
||||
BackgroundObscured = 3,
|
||||
};
|
||||
|
||||
enum class FocusHandlingMode : u32 {
|
||||
AlwaysSuspend = 0,
|
||||
SuspendHomeSleep = 1,
|
||||
NoSuspend = 2,
|
||||
};
|
||||
|
||||
enum class SuspendMode : u32 {
|
||||
NoOverride = 0,
|
||||
ForceResume = 1,
|
||||
ForceSuspend = 2,
|
||||
};
|
||||
|
||||
class LifecycleManager {
|
||||
public:
|
||||
explicit LifecycleManager(Core::System& system, KernelHelpers::ServiceContext& context,
|
||||
bool is_application);
|
||||
~LifecycleManager();
|
||||
|
||||
public:
|
||||
Event& GetSystemEvent();
|
||||
Event& GetOperationModeChangedSystemEvent();
|
||||
|
||||
public:
|
||||
bool IsApplication() {
|
||||
return m_is_application;
|
||||
}
|
||||
|
||||
bool GetForcedSuspend() {
|
||||
return m_forced_suspend;
|
||||
}
|
||||
|
||||
bool GetExitRequested() {
|
||||
return m_has_requested_exit;
|
||||
}
|
||||
|
||||
ActivityState GetActivityState() {
|
||||
return m_activity_state;
|
||||
}
|
||||
|
||||
FocusState GetAndClearFocusState() {
|
||||
m_acknowledged_focus_state = m_requested_focus_state;
|
||||
return m_acknowledged_focus_state;
|
||||
}
|
||||
|
||||
void SetFocusState(FocusState state) {
|
||||
if (m_requested_focus_state != state) {
|
||||
m_has_focus_state_changed = true;
|
||||
}
|
||||
m_requested_focus_state = state;
|
||||
this->SignalSystemEventIfNeeded();
|
||||
}
|
||||
|
||||
void RequestExit() {
|
||||
m_has_requested_exit = true;
|
||||
this->SignalSystemEventIfNeeded();
|
||||
}
|
||||
|
||||
void RequestResumeNotification() {
|
||||
// NOTE: this appears to be a bug in am.
|
||||
// If an applet makes a concurrent request to receive resume notifications
|
||||
// while it is being suspended, the first resume notification will be lost.
|
||||
// This is not the case with other notification types.
|
||||
if (m_resume_notification_enabled) {
|
||||
m_has_resume = true;
|
||||
}
|
||||
}
|
||||
|
||||
void OnOperationAndPerformanceModeChanged();
|
||||
|
||||
public:
|
||||
void SetFocusStateChangedNotificationEnabled(bool enabled) {
|
||||
m_focus_state_changed_notification_enabled = enabled;
|
||||
this->SignalSystemEventIfNeeded();
|
||||
}
|
||||
|
||||
void SetOperationModeChangedNotificationEnabled(bool enabled) {
|
||||
m_operation_mode_changed_notification_enabled = enabled;
|
||||
this->SignalSystemEventIfNeeded();
|
||||
}
|
||||
|
||||
void SetPerformanceModeChangedNotificationEnabled(bool enabled) {
|
||||
m_performance_mode_changed_notification_enabled = enabled;
|
||||
this->SignalSystemEventIfNeeded();
|
||||
}
|
||||
|
||||
void SetResumeNotificationEnabled(bool enabled) {
|
||||
m_resume_notification_enabled = enabled;
|
||||
}
|
||||
|
||||
void SetActivityState(ActivityState state) {
|
||||
m_activity_state = state;
|
||||
}
|
||||
|
||||
void SetSuspendMode(SuspendMode mode) {
|
||||
m_suspend_mode = mode;
|
||||
}
|
||||
|
||||
void SetForcedSuspend(bool enabled) {
|
||||
m_forced_suspend = enabled;
|
||||
}
|
||||
|
||||
public:
|
||||
void SetFocusHandlingMode(bool suspend);
|
||||
void SetOutOfFocusSuspendingEnabled(bool enabled);
|
||||
void RemoveForceResumeIfPossible();
|
||||
bool IsRunnable() const;
|
||||
bool UpdateRequestedFocusState();
|
||||
void SignalSystemEventIfNeeded();
|
||||
|
||||
public:
|
||||
void PushUnorderedMessage(AppletMessage message);
|
||||
bool PopMessage(AppletMessage* out_message);
|
||||
|
||||
private:
|
||||
FocusState GetFocusStateWhileForegroundObscured() const;
|
||||
FocusState GetFocusStateWhileBackground(bool is_obscured) const;
|
||||
|
||||
private:
|
||||
AppletMessage PopMessageInOrderOfPriority();
|
||||
bool ShouldSignalSystemEvent();
|
||||
|
||||
private:
|
||||
Event m_system_event;
|
||||
Event m_operation_mode_changed_system_event;
|
||||
|
||||
std::list<AppletMessage> m_unordered_messages{};
|
||||
|
||||
bool m_is_application{};
|
||||
bool m_focus_state_changed_notification_enabled{true};
|
||||
bool m_operation_mode_changed_notification_enabled{true};
|
||||
bool m_performance_mode_changed_notification_enabled{true};
|
||||
bool m_resume_notification_enabled{};
|
||||
|
||||
bool m_requested_request_to_display_state{};
|
||||
bool m_acknowledged_request_to_display_state{};
|
||||
bool m_has_resume{};
|
||||
bool m_has_focus_state_changed{true};
|
||||
bool m_has_album_recording_saved{};
|
||||
bool m_has_album_screen_shot_taken{};
|
||||
bool m_has_auto_power_down{};
|
||||
bool m_has_sleep_required_by_low_battery{};
|
||||
bool m_has_sleep_required_by_high_temperature{};
|
||||
bool m_has_sd_card_removed{};
|
||||
bool m_has_performance_mode_changed{};
|
||||
bool m_has_operation_mode_changed{};
|
||||
bool m_has_requested_request_to_prepare_sleep{};
|
||||
bool m_has_acknowledged_request_to_prepare_sleep{};
|
||||
bool m_has_requested_exit{};
|
||||
bool m_has_acknowledged_exit{};
|
||||
bool m_applet_message_available{};
|
||||
|
||||
bool m_forced_suspend{};
|
||||
FocusHandlingMode m_focus_handling_mode{FocusHandlingMode::SuspendHomeSleep};
|
||||
ActivityState m_activity_state{ActivityState::ForegroundVisible};
|
||||
SuspendMode m_suspend_mode{SuspendMode::NoOverride};
|
||||
FocusState m_requested_focus_state{};
|
||||
FocusState m_acknowledged_focus_state{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,130 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/hle/service/am/process_creation.h"
|
||||
#include "core/hle/service/glue/glue_manager.h"
|
||||
#include "core/hle/service/os/process.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
namespace {
|
||||
|
||||
FileSys::StorageId GetStorageIdForFrontendSlot(
|
||||
std::optional<FileSys::ContentProviderUnionSlot> slot) {
|
||||
if (!slot.has_value()) {
|
||||
return FileSys::StorageId::None;
|
||||
}
|
||||
|
||||
switch (*slot) {
|
||||
case FileSys::ContentProviderUnionSlot::UserNAND:
|
||||
return FileSys::StorageId::NandUser;
|
||||
case FileSys::ContentProviderUnionSlot::SysNAND:
|
||||
return FileSys::StorageId::NandSystem;
|
||||
case FileSys::ContentProviderUnionSlot::SDMC:
|
||||
return FileSys::StorageId::SdCard;
|
||||
case FileSys::ContentProviderUnionSlot::FrontendManual:
|
||||
return FileSys::StorageId::Host;
|
||||
default:
|
||||
return FileSys::StorageId::None;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& out_loader,
|
||||
Loader::ResultStatus& out_load_result,
|
||||
Core::System& system, FileSys::VirtualFile file,
|
||||
u64 program_id, u64 program_index) {
|
||||
// Get the appropriate loader to parse this NCA.
|
||||
out_loader = Loader::GetLoader(system, file, program_id, program_index);
|
||||
|
||||
// Ensure we have a loader which can parse the NCA.
|
||||
if (!out_loader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try to load the process.
|
||||
auto process = std::make_unique<Process>(system);
|
||||
if (process->Initialize(*out_loader, out_load_result)) {
|
||||
return process;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
|
||||
u8 minimum_key_generation, u8 maximum_key_generation) {
|
||||
// Attempt to load program NCA.
|
||||
FileSys::VirtualFile nca_raw{};
|
||||
|
||||
// Get the program NCA from storage.
|
||||
auto& storage = system.GetContentProviderUnion();
|
||||
nca_raw = storage.GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
|
||||
|
||||
// Ensure we retrieved a program NCA.
|
||||
if (!nca_raw) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure we have a suitable version.
|
||||
if (minimum_key_generation > 0) {
|
||||
FileSys::NCA nca(nca_raw);
|
||||
if (nca.GetStatus() == Loader::ResultStatus::Success &&
|
||||
(nca.GetKeyGeneration() < minimum_key_generation ||
|
||||
nca.GetKeyGeneration() > maximum_key_generation)) {
|
||||
LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id,
|
||||
nca.GetKeyGeneration());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Loader::AppLoader> loader;
|
||||
Loader::ResultStatus status;
|
||||
return CreateProcessImpl(loader, status, system, nca_raw, program_id, 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
|
||||
std::unique_ptr<Loader::AppLoader>& out_loader,
|
||||
Loader::ResultStatus& out_load_result,
|
||||
Core::System& system, FileSys::VirtualFile file,
|
||||
u64 program_id, u64 program_index) {
|
||||
auto process =
|
||||
CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index);
|
||||
if (!process) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileSys::NACP nacp;
|
||||
if (out_loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
|
||||
out_control = nacp.GetRawBytes();
|
||||
} else {
|
||||
out_control.resize(sizeof(FileSys::RawNACP));
|
||||
}
|
||||
|
||||
auto& storage = system.GetContentProviderUnion();
|
||||
Service::Glue::ApplicationLaunchProperty launch{};
|
||||
launch.title_id = process->GetProgramId();
|
||||
|
||||
FileSys::PatchManager pm{launch.title_id, system.GetFileSystemController(), storage};
|
||||
launch.version = pm.GetGameVersion().value_or(0);
|
||||
|
||||
// TODO(DarkLordZach): When FSController/Game Card Support is added, if
|
||||
// current_process_game_card use correct StorageId
|
||||
launch.base_game_storage_id = GetStorageIdForFrontendSlot(
|
||||
storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program));
|
||||
launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(
|
||||
FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
|
||||
|
||||
system.GetARPManager().Register(launch.title_id, launch, out_control);
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
class AppLoader;
|
||||
enum class ResultStatus : u16;
|
||||
} // namespace Loader
|
||||
|
||||
namespace Service {
|
||||
class Process;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
|
||||
u8 minimum_key_generation, u8 maximum_key_generation);
|
||||
std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
|
||||
std::unique_ptr<Loader::AppLoader>& out_loader,
|
||||
Loader::ResultStatus& out_load_result,
|
||||
Core::System& system, FileSys::VirtualFile file,
|
||||
u64 program_id, u64 program_index);
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/am/process_holder.h"
|
||||
#include "core/hle/service/os/process.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
ProcessHolder::ProcessHolder(Applet& applet, Process& process)
|
||||
: MultiWaitHolder(process.GetHandle()), m_applet(applet), m_process(process) {}
|
||||
|
||||
ProcessHolder::~ProcessHolder() = default;
|
||||
|
||||
} // namespace Service::AM
|
@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/os/multi_wait_holder.h"
|
||||
|
||||
namespace Service {
|
||||
class Process;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
struct Applet;
|
||||
|
||||
class ProcessHolder : public MultiWaitHolder, public Common::IntrusiveListBaseNode<ProcessHolder> {
|
||||
public:
|
||||
explicit ProcessHolder(Applet& applet, Process& process);
|
||||
~ProcessHolder();
|
||||
|
||||
Applet& GetApplet() const {
|
||||
return m_applet;
|
||||
}
|
||||
|
||||
Process& GetProcess() const {
|
||||
return m_process;
|
||||
}
|
||||
|
||||
private:
|
||||
Applet& m_applet;
|
||||
Process& m_process;
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
@ -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
|
@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
struct Applet;
|
||||
class EventObserver;
|
||||
|
||||
enum class ButtonPressDuration {
|
||||
ShortPressing,
|
||||
MiddlePressing,
|
||||
LongPressing,
|
||||
};
|
||||
|
||||
class WindowSystem {
|
||||
public:
|
||||
explicit WindowSystem(Core::System& system);
|
||||
~WindowSystem();
|
||||
|
||||
public:
|
||||
void SetEventObserver(EventObserver* event_observer);
|
||||
void Update();
|
||||
|
||||
public:
|
||||
void TrackApplet(std::shared_ptr<Applet> applet, bool is_application);
|
||||
std::shared_ptr<Applet> GetByAppletResourceUserId(u64 aruid);
|
||||
std::shared_ptr<Applet> GetMainApplet();
|
||||
|
||||
public:
|
||||
void RequestHomeMenuToGetForeground();
|
||||
void RequestApplicationToGetForeground();
|
||||
void RequestLockHomeMenuIntoForeground();
|
||||
void RequestUnlockHomeMenuIntoForeground();
|
||||
void RequestAppletVisibilityState(Applet& applet, bool visible);
|
||||
|
||||
public:
|
||||
void OnOperationModeChanged();
|
||||
void OnExitRequested();
|
||||
void OnHomeButtonPressed(ButtonPressDuration type);
|
||||
void OnCaptureButtonPressed(ButtonPressDuration type) {}
|
||||
void OnPowerButtonPressed(ButtonPressDuration type) {}
|
||||
|
||||
private:
|
||||
void PruneTerminatedAppletsLocked();
|
||||
bool LockHomeMenuIntoForegroundLocked();
|
||||
void TerminateChildAppletsLocked(Applet* applet);
|
||||
void UpdateAppletStateLocked(Applet* applet, bool is_foreground);
|
||||
|
||||
private:
|
||||
// System reference.
|
||||
Core::System& m_system;
|
||||
|
||||
// Event observer.
|
||||
EventObserver* m_event_observer{};
|
||||
|
||||
// Lock.
|
||||
std::mutex m_lock{};
|
||||
|
||||
// Home menu state.
|
||||
bool m_home_menu_foreground_locked{};
|
||||
Applet* m_foreground_requested_applet{};
|
||||
|
||||
// Foreground roots.
|
||||
Applet* m_home_menu{};
|
||||
Applet* m_application{};
|
||||
|
||||
// Applet map by aruid.
|
||||
std::map<u64, std::shared_ptr<Applet>> m_applets{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue