From fedb2b92d790d8cb86267e50c3ecfd523b439195 Mon Sep 17 00:00:00 2001 From: Maufeat Date: Wed, 5 Nov 2025 03:03:21 +0100 Subject: [PATCH] add overlay function to set emulator audio, and more. --- src/core/hle/service/am/applet.cpp | 10 +- src/core/hle/service/am/applet.h | 1 + src/core/hle/service/am/applet_manager.cpp | 11 +- src/core/hle/service/am/applet_manager.h | 4 + .../hle/service/am/display_layer_manager.cpp | 43 ++++- .../hle/service/am/display_layer_manager.h | 5 + .../am/service/common_state_getter.cpp | 7 +- .../service/am/service/overlay_functions.cpp | 45 ++++- src/core/hle/service/am/window_system.cpp | 103 +++++++---- src/core/hle/service/am/window_system.h | 6 +- .../hle/service/audio/audio_controller.cpp | 172 +++++++++++++++++- src/core/hle/service/audio/audio_controller.h | 11 ++ .../hle/service/audio/audio_out_manager.cpp | 14 ++ .../hle/service/audio/audio_out_manager.h | 5 + src/core/hle/service/audio/errors.h | 1 + .../ns/application_manager_interface.cpp | 17 +- .../ns/application_manager_interface.h | 3 + src/core/hle/service/vi/container.cpp | 7 +- 18 files changed, 406 insertions(+), 59 deletions(-) diff --git a/src/core/hle/service/am/applet.cpp b/src/core/hle/service/am/applet.cpp index af3fc82f94..2ae3102aff 100644 --- a/src/core/hle/service/am/applet.cpp +++ b/src/core/hle/service/am/applet.cpp @@ -63,7 +63,15 @@ void Applet::SetInteractibleLocked(bool interactible) { is_interactible = interactible; - hid_registration.EnableAppletToGetInput(interactible && !lifecycle_manager.GetExitRequested()); + const bool exit_requested = lifecycle_manager.GetExitRequested(); + const bool input_enabled = interactible && !exit_requested; + + if (applet_id == AppletId::OverlayDisplay || applet_id == AppletId::Application) { + LOG_DEBUG(Service_AM, "called, applet={} interactible={} exit_requested={} input_enabled={} overlay_in_foreground={}", + static_cast(applet_id), interactible, exit_requested, input_enabled, overlay_in_foreground); + } + + hid_registration.EnableAppletToGetInput(input_enabled); } void Applet::OnProcessTerminatedLocked() { diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h index 5082b03ccf..0763a5838e 100644 --- a/src/core/hle/service/am/applet.h +++ b/src/core/hle/service/am/applet.h @@ -122,6 +122,7 @@ struct Applet { bool is_activity_runnable{}; bool is_interactible{true}; bool window_visible{true}; + bool overlay_in_foreground{false}; // Events Event overlay_event; diff --git a/src/core/hle/service/am/applet_manager.cpp b/src/core/hle/service/am/applet_manager.cpp index ee86c47636..b110991d4f 100644 --- a/src/core/hle/service/am/applet_manager.cpp +++ b/src/core/hle/service/am/applet_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -271,10 +274,16 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) { overlay_applet->type = AppletType::OverlayApplet; // Use PartialForeground so blending is enabled and overlay can be composed on top overlay_applet->library_applet_mode = LibraryAppletMode::PartialForeground; + // Start with overlay visible but with low z-index (showing vignette behind app) + // Home button will toggle it to foreground (high z-index) to show full overlay + overlay_applet->window_visible = true; + // Enable home button watching so overlay can be toggled with home button + overlay_applet->home_button_short_pressed_blocked = false; + overlay_applet->home_button_long_pressed_blocked = false; // Track as a non-application so WindowSystem routes it to m_overlay_display m_window_system->TrackApplet(overlay_applet, false); overlay_applet->process->Run(); - LOG_INFO(Service_AM, "Overlay applet launched before application"); + LOG_INFO(Service_AM, "called, Overlay applet launched before application (initially hidden, watching home button)"); } const auto& params = m_pending_parameters; diff --git a/src/core/hle/service/am/applet_manager.h b/src/core/hle/service/am/applet_manager.h index fbdc771400..2b0714adf7 100644 --- a/src/core/hle/service/am/applet_manager.h +++ b/src/core/hle/service/am/applet_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -46,6 +49,7 @@ public: public: void SetWindowSystem(WindowSystem* window_system); + [[nodiscard]] WindowSystem* GetWindowSystem() const { return m_window_system; } private: Core::System& m_system; diff --git a/src/core/hle/service/am/display_layer_manager.cpp b/src/core/hle/service/am/display_layer_manager.cpp index b13b0f9e81..bac3cd6014 100644 --- a/src/core/hle/service/am/display_layer_manager.cpp +++ b/src/core/hle/service/am/display_layer_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -76,11 +79,18 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) { // Ensure visibility follows our state m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id); - // For non-application applets (e.g., overlay), make sure UI layers blend and render on top + // For non-application applets (e.g., overlay), make sure UI layers blend if (m_applet_id != AppletId::Application) { - static constexpr s32 kOverlayZ = 100000; (void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id); - (void)m_manager_display_service->SetLayerZIndex(kOverlayZ, *out_layer_id); + // Start with lower z-index for overlay (vignette/background mode) + // Will be raised when overlay is opened + if (m_applet_id == AppletId::OverlayDisplay) { + static constexpr s32 kOverlayBackgroundZ = -100000; + (void)m_manager_display_service->SetLayerZIndex(kOverlayBackgroundZ, *out_layer_id); + } else { + static constexpr s32 kOverlayZ = 100000; + (void)m_manager_display_service->SetLayerZIndex(kOverlayZ, *out_layer_id); + } } m_managed_display_layers.emplace(*out_layer_id); @@ -124,15 +134,18 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() { // We succeeded, so set up remaining state. m_buffer_sharing_enabled = true; - // Ensure the overlay layer is visible and above the application layer when applicable + // Ensure the overlay layer is visible m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id); if (m_applet_id != AppletId::Application) { - static constexpr s32 kOverlayZ = 100000; - m_manager_display_service->SetLayerZIndex(kOverlayZ, m_system_shared_layer_id); m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id); + s32 initial_z = 100000; + if (m_applet_id == AppletId::OverlayDisplay) { + initial_z = -100000; + } + m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id); LOG_INFO(Service_VI, "DLM: Overlay session ready buffer_id={} layer_id={} z={} visible={} blending={}", - m_system_shared_buffer_id, m_system_shared_layer_id, kOverlayZ, m_visible, + m_system_shared_buffer_id, m_system_shared_layer_id, initial_z, m_visible, m_blending_enabled); } @@ -175,6 +188,22 @@ bool DisplayLayerManager::GetWindowVisibility() const { return m_visible; } +void DisplayLayerManager::SetOverlayZIndex(s32 z_index) { + if (!m_manager_display_service) { + return; + } + + if (m_system_shared_layer_id) { + m_manager_display_service->SetLayerZIndex(z_index, m_system_shared_layer_id); + LOG_INFO(Service_VI, "called, shared_layer={} z={}", m_system_shared_layer_id, z_index); + } + + for (const auto layer_id : m_managed_display_layers) { + m_manager_display_service->SetLayerZIndex(z_index, layer_id); + LOG_INFO(Service_VI, "called, managed_layer={} z={}", layer_id, z_index); + } +} + Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index) { R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied); diff --git a/src/core/hle/service/am/display_layer_manager.h b/src/core/hle/service/am/display_layer_manager.h index a66509c04a..423eb432fa 100644 --- a/src/core/hle/service/am/display_layer_manager.h +++ b/src/core/hle/service/am/display_layer_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -43,6 +46,8 @@ public: void SetWindowVisibility(bool visible); bool GetWindowVisibility() const; + void SetOverlayZIndex(s32 z_index); + Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index); private: diff --git a/src/core/hle/service/am/service/common_state_getter.cpp b/src/core/hle/service/am/service/common_state_getter.cpp index 0aa22f0e21..93bded336c 100644 --- a/src/core/hle/service/am/service/common_state_getter.cpp +++ b/src/core/hle/service/am/service/common_state_getter.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -11,7 +14,6 @@ #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/pm/pm.h" #include "core/hle/service/sm/sm.h" -#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_types.h" namespace Service::AM { @@ -93,6 +95,9 @@ Result ICommonStateGetter::ReceiveMessage(Out out_applet_message) R_THROW(AM::ResultNoMessages); } + LOG_INFO(Service_AM, "called, returning message={} to applet_id={}", + static_cast(*out_applet_message), static_cast(m_applet->applet_id)); + R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/overlay_functions.cpp b/src/core/hle/service/am/service/overlay_functions.cpp index 4206383303..e841403700 100644 --- a/src/core/hle/service/am/service/overlay_functions.cpp +++ b/src/core/hle/service/am/service/overlay_functions.cpp @@ -1,7 +1,10 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include "core/core.h" #include "core/hle/service/am/applet.h" +#include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/am/window_system.h" #include "core/hle/service/am/service/overlay_functions.h" #include "core/hle/service/cmif_serialization.h" @@ -40,22 +43,50 @@ IOverlayFunctions::~IOverlayFunctions() = default; Result IOverlayFunctions::BeginToWatchShortHomeButtonMessage() { LOG_DEBUG(Service_AM, "called"); - std::scoped_lock lk{m_applet->lock}; - m_applet->home_button_short_pressed_blocked = false; + { + std::scoped_lock lk{m_applet->lock}; + + m_applet->overlay_in_foreground = true; + m_applet->home_button_short_pressed_blocked = false; + + static constexpr s32 kOverlayForegroundZ = 100; + m_applet->display_layer_manager.SetOverlayZIndex(kOverlayForegroundZ); + + LOG_INFO(Service_AM, "called, Overlay moved to FOREGROUND (z={}, overlay_in_foreground=true)", kOverlayForegroundZ); + } + + if (auto* window_system = system.GetAppletManager().GetWindowSystem()) { + window_system->RequestUpdate(); + } + R_SUCCEED(); } Result IOverlayFunctions::EndToWatchShortHomeButtonMessage() { LOG_DEBUG(Service_AM, "called"); - std::scoped_lock lk{m_applet->lock}; - m_applet->home_button_short_pressed_blocked = true; + + { + std::scoped_lock lk{m_applet->lock}; + + m_applet->overlay_in_foreground = false; + m_applet->home_button_short_pressed_blocked = false; + + static constexpr s32 kOverlayBackgroundZ = -100; + m_applet->display_layer_manager.SetOverlayZIndex(kOverlayBackgroundZ); + + LOG_INFO(Service_AM, "Overlay moved to BACKGROUND (z={}, overlay_in_foreground=false)", kOverlayBackgroundZ); + } + + if (auto* window_system = system.GetAppletManager().GetWindowSystem()) { + window_system->RequestUpdate(); + } + R_SUCCEED(); } Result IOverlayFunctions::GetApplicationIdForLogo(Out out_application_id) { LOG_DEBUG(Service_AM, "called"); - // Prefer explicit application_id if available, else fall back to program_id std::scoped_lock lk{m_applet->lock}; u64 id = m_applet->screen_shot_identity.application_id; if (id == 0) { @@ -66,7 +97,7 @@ Result IOverlayFunctions::GetApplicationIdForLogo(Out out_application_id) { } Result IOverlayFunctions::SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled) { - LOG_WARNING(Service_AM, "(STUBBED) called, enabled={}", enabled); + LOG_WARNING(Service_AM, "called, enabled={}", enabled); std::scoped_lock lk{m_applet->lock}; m_applet->auto_sleep_disabled = !enabled; R_SUCCEED(); @@ -75,7 +106,7 @@ Result IOverlayFunctions::SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled) { Result IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled(bool enabled) { LOG_DEBUG(Service_AM, "called, enabled={}", enabled); std::scoped_lock lk{m_applet->lock}; - m_applet->home_button_short_pressed_blocked = !enabled; + m_applet->home_button_short_pressed_blocked = false; R_SUCCEED(); } diff --git a/src/core/hle/service/am/window_system.cpp b/src/core/hle/service/am/window_system.cpp index 6f13e7e0e9..c76c154651 100644 --- a/src/core/hle/service/am/window_system.cpp +++ b/src/core/hle/service/am/window_system.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -21,9 +24,18 @@ void WindowSystem::SetEventObserver(EventObserver* 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, "WindowSystem::Update 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(); @@ -32,10 +44,16 @@ void WindowSystem::Update() { return; } + bool overlay_blocks_input = false; + if (m_overlay_display) { + std::scoped_lock lk_overlay{m_overlay_display->lock}; + overlay_blocks_input = m_overlay_display->overlay_in_foreground; + } + // 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); - this->UpdateAppletStateLocked(m_overlay_display, true); // overlay is always updated + this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_blocks_input); + this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_blocks_input); + this->UpdateAppletStateLocked(m_overlay_display, true, false); // overlay is always updated, never blocked } void WindowSystem::TrackApplet(std::shared_ptr applet, bool is_application) { @@ -143,35 +161,55 @@ void WindowSystem::OnExitRequested() { void WindowSystem::OnHomeButtonPressed(ButtonPressDuration type) { std::scoped_lock lk{m_lock}; - // Prefer sending to overlay display if present. + LOG_INFO(Service_AM, "WindowSystem::OnHomeButtonPressed - type={} overlay={} home_menu={} application={} foreground={}", + type == ButtonPressDuration::ShortPressing ? "Short" : "Long", + m_overlay_display != nullptr, m_home_menu != nullptr, m_application != nullptr, + m_foreground_requested_applet == m_home_menu ? "home_menu" : + m_foreground_requested_applet == m_application ? "application" : "none"); + + // Priority 1: Check overlay first (works everywhere, even in qlaunch) + // Long press always goes to overlay for toggling + // Short press only when overlay is already open (to close it) if (m_overlay_display) { std::scoped_lock lk3{m_overlay_display->lock}; + const bool overlay_should_handle = !m_overlay_display->home_button_short_pressed_blocked; + + if (overlay_should_handle) { + const bool overlay_in_fg = m_overlay_display->overlay_in_foreground; + + if (type == ButtonPressDuration::LongPressing || + (type == ButtonPressDuration::ShortPressing && overlay_in_fg)) { + + LOG_INFO(Service_AM, "Sending {} press to overlay (foreground={})", + type == ButtonPressDuration::ShortPressing ? "short" : "long", + overlay_in_fg); + + if (type == ButtonPressDuration::ShortPressing) { + m_overlay_display->lifecycle_manager.PushUnorderedMessage( + AppletMessage::DetectShortPressingHomeButton); + } else { + m_overlay_display->lifecycle_manager.PushUnorderedMessage( + AppletMessage::DetectLongPressingHomeButton); + } + return; + } + } + } + + if (m_home_menu) { + std::scoped_lock lk2{m_home_menu->lock}; + LOG_DEBUG(Service_AM, "called, Sending home button to home menu"); if (type == ButtonPressDuration::ShortPressing) { - m_overlay_display->lifecycle_manager.PushUnorderedMessage( + m_home_menu->lifecycle_manager.PushUnorderedMessage( AppletMessage::DetectShortPressingHomeButton); } else if (type == ButtonPressDuration::LongPressing) { - m_overlay_display->lifecycle_manager.PushUnorderedMessage( + m_home_menu->lifecycle_manager.PushUnorderedMessage( AppletMessage::DetectLongPressingHomeButton); } return; } - // 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); - } else if (type == ButtonPressDuration::LongPressing) { - m_home_menu->lifecycle_manager.PushUnorderedMessage( - AppletMessage::DetectLongPressingHomeButton); - } + LOG_DEBUG(Service_AM, "called, No target for home button press"); } void WindowSystem::PruneTerminatedAppletsLocked() { @@ -283,7 +321,7 @@ void WindowSystem::TerminateChildAppletsLocked(Applet* applet) { applet->lock.lock(); } -void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground) { +void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking) { // With no applet, we don't have anything to do. if (!applet) { return; @@ -316,17 +354,18 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground) { const bool should_be_visible = (applet->applet_id == AppletId::OverlayDisplay) ? applet->window_visible : (is_foreground && applet->window_visible); - if (applet->applet_id == AppletId::OverlayDisplay) { - LOG_INFO(Service_AM, "WindowSystem: Overlay visibility update - window_visible={} should_be_visible={} is_foreground={}", - applet->window_visible, should_be_visible, is_foreground); - } applet->display_layer_manager.SetWindowVisibility(should_be_visible); - // Update interactibility state. - // Overlay applets should be interactible when visible, as they need to handle input (e.g. home button) + const bool should_be_interactible = (applet->applet_id == AppletId::OverlayDisplay) - ? applet->window_visible - : (is_foreground && applet->window_visible); + ? applet->overlay_in_foreground + : (is_foreground && applet->window_visible && !overlay_blocking); + + if (applet->applet_id == AppletId::OverlayDisplay || applet->applet_id == AppletId::Application) { + LOG_DEBUG(Service_AM, "UpdateAppletStateLocked: applet={} overlay_in_foreground={} is_foreground={} window_visible={} overlay_blocking={} should_be_interactible={}", + static_cast(applet->applet_id), applet->overlay_in_foreground, is_foreground, applet->window_visible, overlay_blocking, should_be_interactible); + } + applet->SetInteractibleLocked(should_be_interactible); // Update focus state and suspension. @@ -345,7 +384,7 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground) { // Recurse into child applets. for (const auto& child_applet : applet->child_applets) { - this->UpdateAppletStateLocked(child_applet.get(), is_foreground); + this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_blocking); } } diff --git a/src/core/hle/service/am/window_system.h b/src/core/hle/service/am/window_system.h index eeb2eed2ad..7167d6af32 100644 --- a/src/core/hle/service/am/window_system.h +++ b/src/core/hle/service/am/window_system.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -32,6 +35,7 @@ public: public: void SetEventObserver(EventObserver* event_observer); void Update(); + void RequestUpdate(); public: void TrackApplet(std::shared_ptr applet, bool is_application); @@ -56,7 +60,7 @@ private: void PruneTerminatedAppletsLocked(); bool LockHomeMenuIntoForegroundLocked(); void TerminateChildAppletsLocked(Applet* applet); - void UpdateAppletStateLocked(Applet* applet, bool is_foreground); + void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking = false); private: // System reference. diff --git a/src/core/hle/service/audio/audio_controller.cpp b/src/core/hle/service/audio/audio_controller.cpp index 4b6f468c92..2c04e211ea 100644 --- a/src/core/hle/service/audio/audio_controller.cpp +++ b/src/core/hle/service/audio/audio_controller.cpp @@ -1,12 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/logging/log.h" +#include "audio_core/audio_core.h" #include "core/hle/service/audio/audio_controller.h" +#include "core/hle/service/audio/audio_out_manager.h" +#include "core/core.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/set/system_settings_server.h" #include "core/hle/service/sm/sm.h" +#include "common/settings.h" +#include +// for std::clamp +#include namespace Service::Audio { @@ -14,12 +26,12 @@ IAudioController::IAudioController(Core::System& system_) : ServiceFramework{system_, "audctl"}, service_context{system, "audctl"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetTargetVolume"}, - {1, nullptr, "SetTargetVolume"}, + {0, D<&IAudioController::GetTargetVolume>, "GetTargetVolume"}, + {1, D<&IAudioController::SetTargetVolume>, "SetTargetVolume"}, {2, D<&IAudioController::GetTargetVolumeMin>, "GetTargetVolumeMin"}, {3, D<&IAudioController::GetTargetVolumeMax>, "GetTargetVolumeMax"}, - {4, nullptr, "IsTargetMute"}, - {5, nullptr, "SetTargetMute"}, + {4, D<&IAudioController::IsTargetMute>, "IsTargetMute"}, + {5, D<&IAudioController::SetTargetMute>, "SetTargetMute"}, {6, nullptr, "IsTargetConnected"}, {7, nullptr, "SetDefaultTarget"}, {8, nullptr, "GetDefaultTarget"}, @@ -46,7 +58,7 @@ IAudioController::IAudioController(Core::System& system_) {29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"}, {30, D<&IAudioController::SetSpeakerAutoMuteEnabled>, "SetSpeakerAutoMuteEnabled"}, {31, D<&IAudioController::IsSpeakerAutoMuteEnabled>, "IsSpeakerAutoMuteEnabled"}, - {32, nullptr, "GetActiveOutputTarget"}, + {32, D<&IAudioController::GetActiveOutputTarget>, "GetActiveOutputTarget"}, {33, nullptr, "GetTargetDeviceInfo"}, {34, D<&IAudioController::AcquireTargetNotification>, "AcquireTargetNotification"}, {35, nullptr, "SetHearingProtectionSafeguardTimerRemainingTimeForDebug"}, @@ -80,6 +92,38 @@ IAudioController::IAudioController(Core::System& system_) m_set_sys = system.ServiceManager().GetService("set:sys", true); notification_event = service_context.CreateEvent("IAudioController:NotificationEvent"); + + // Probably shouldn't do this in constructor? + try { + const int ui_volume = Settings::values.volume.GetValue(); + const int mapped = static_cast(std::lround((static_cast(ui_volume) / 100.0) * 15.0)); + const auto active_idx = static_cast(m_active_target); + if (active_idx < m_target_volumes.size()) { + m_target_volumes[active_idx] = std::clamp(mapped, 0, 15); + } + + const bool muted = Settings::values.audio_muted.GetValue(); + for (auto& m : m_target_muted) { + m = muted; + } + + if (!muted && active_idx < m_target_volumes.size()) { + const float vol_f = static_cast(m_target_volumes[active_idx]) / 15.0f; + try { + auto& sink = system.AudioCore().GetOutputSink(); + sink.SetSystemVolume(vol_f); + sink.SetDeviceVolume(vol_f); + } catch (...) { + LOG_WARNING(Audio, "Failed to apply initial sink volume from settings"); + } + + if (auto audout_mgr = system.ServiceManager().GetService("audout:u")) { + audout_mgr->SetAllAudioOutVolume(static_cast(m_target_volumes[active_idx]) / 15.0f); + } + } + } catch (...) { + // Catch if something fails, since this is constructor + } } IAudioController::~IAudioController() { @@ -187,4 +231,120 @@ Result IAudioController::Unknown5000(Out> out_au R_SUCCEED(); } + +Result IAudioController::GetTargetVolume(Out out_target_volume, Set::AudioOutputModeTarget target) { + LOG_DEBUG(Audio, "GetTargetVolume called, target={}", target); + + const auto idx = static_cast(target); + if (idx >= m_target_volumes.size()) { + LOG_ERROR(Audio, "GetTargetVolume invalid target {}", idx); + R_THROW(ResultInvalidArgument); + } + + *out_target_volume = m_target_volumes[idx]; + R_SUCCEED(); +} + +Result IAudioController::SetTargetVolume(Set::AudioOutputModeTarget target, s32 target_volume) { + LOG_INFO(Audio, "SetTargetVolume called, target={}, volume={}", target, target_volume); + + const auto idx = static_cast(target); + if (idx >= m_target_volumes.size()) { + LOG_ERROR(Audio, "SetTargetVolume invalid target {}", idx); + R_THROW(ResultInvalidArgument); + } + + if (target_volume < 0) { + target_volume = 0; + } else if (target_volume > 15) { + target_volume = 15; + } + + m_target_volumes[idx] = target_volume; + + if (m_active_target == target && !m_target_muted[idx]) { + const float vol = static_cast(target_volume) / 15.0f; + // try catch this, as we don't know how it handles it when no output is set. + // we already have audio issues when no output is set, so catch. + try { + auto& sink = system.AudioCore().GetOutputSink(); + sink.SetSystemVolume(vol); + sink.SetDeviceVolume(vol); + } catch (...) { + LOG_WARNING(Audio, "Failed to set sink system volume"); + } + + if (auto audout_mgr = system.ServiceManager().GetService("audout:u")) { + audout_mgr->SetAllAudioOutVolume(vol); + } + } + + if (m_active_target == target) { + const int ui_volume = static_cast(std::lround((static_cast(target_volume) / 15.0) * 100.0)); + Settings::values.volume.SetValue(static_cast(std::clamp(ui_volume, 0, 100))); + } + + R_SUCCEED(); +} + +Result IAudioController::IsTargetMute(Out out_is_target_muted, Set::AudioOutputModeTarget target) { + LOG_DEBUG(Audio, "called, target={}", target); + + const auto idx = static_cast(target); + if (idx >= m_target_muted.size()) { + LOG_ERROR(Audio, "invalid target {}", idx); + R_THROW(ResultInvalidArgument); + } + + *out_is_target_muted = m_target_muted[idx]; + R_SUCCEED(); +} + +Result IAudioController::SetTargetMute(bool is_muted, Set::AudioOutputModeTarget target) { + LOG_INFO(Audio, "called, target={}, muted={}", target, is_muted); + + const auto idx = static_cast(target); + if (idx >= m_target_muted.size()) { + LOG_ERROR(Audio, "invalid target {}", idx); + R_THROW(ResultInvalidArgument); + } + + m_target_muted[idx] = is_muted; + + if (m_active_target == target) { + try { + auto& sink = system.AudioCore().GetOutputSink(); + if (is_muted) { + sink.SetSystemVolume(0.0f); + sink.SetDeviceVolume(0.0f); + } else { + const float vol = static_cast(m_target_volumes[idx]) / 15.0f; + sink.SetSystemVolume(vol); + sink.SetDeviceVolume(vol); + } + } catch (...) { + LOG_WARNING(Audio, "Failed to set sink system volume on mute change"); + } + + // Also update any open audout sessions via the audout:u service. + if (auto audout_mgr = system.ServiceManager().GetService("audout:u")) { + if (is_muted) { + audout_mgr->SetAllAudioOutVolume(0.0f); + } else { + const float vol = static_cast(m_target_volumes[idx]) / 15.0f; + audout_mgr->SetAllAudioOutVolume(vol); + } + } + } + + Settings::values.audio_muted.SetValue(is_muted); + + R_SUCCEED(); +} + +Result IAudioController::GetActiveOutputTarget(Out out_active_target) { + LOG_DEBUG(Audio, "GetActiveOutputTarget called"); + *out_active_target = m_active_target; + R_SUCCEED(); +} } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio_controller.h b/src/core/hle/service/audio/audio_controller.h index b7645332e6..ff51f4c244 100644 --- a/src/core/hle/service/audio/audio_controller.h +++ b/src/core/hle/service/audio/audio_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -35,6 +38,11 @@ private: Result GetTargetVolumeMin(Out out_target_min_volume); Result GetTargetVolumeMax(Out out_target_max_volume); + Result GetTargetVolume(Out out_target_volume, Set::AudioOutputModeTarget target); + Result SetTargetVolume(Set::AudioOutputModeTarget target, s32 target_volume); + Result IsTargetMute(Out out_is_target_muted, Set::AudioOutputModeTarget target); + Result SetTargetMute(bool is_muted, Set::AudioOutputModeTarget target); + Result GetActiveOutputTarget(Out out_active_target); Result GetAudioOutputMode(Out out_output_mode, Set::AudioOutputModeTarget target); Result SetAudioOutputMode(Set::AudioOutputModeTarget target, Set::AudioOutputMode output_mode); @@ -55,6 +63,9 @@ private: Kernel::KEvent* notification_event; std::shared_ptr m_set_sys; + std::array m_target_volumes{{15, 15, 15, 15, 15, 15}}; + std::array m_target_muted{{false, false, false, false, false, false}}; + Set::AudioOutputModeTarget m_active_target{Set::AudioOutputModeTarget::Speaker}; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio_out_manager.cpp b/src/core/hle/service/audio/audio_out_manager.cpp index 1534450972..0a8e1ec256 100644 --- a/src/core/hle/service/audio/audio_out_manager.cpp +++ b/src/core/hle/service/audio/audio_out_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -98,4 +101,15 @@ Result IAudioOutManager::OpenAudioOutAuto( R_SUCCEED(); } +Result IAudioOutManager::SetAllAudioOutVolume(f32 volume) { + std::scoped_lock l{impl->mutex}; + for (auto& session : impl->sessions) { + if (session) { + session->GetSystem().SetVolume(volume); + } + } + + R_SUCCEED(); +} + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio_out_manager.h b/src/core/hle/service/audio/audio_out_manager.h index eaa27bc798..791274d5e9 100644 --- a/src/core/hle/service/audio/audio_out_manager.h +++ b/src/core/hle/service/audio/audio_out_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,6 +21,8 @@ public: explicit IAudioOutManager(Core::System& system_); ~IAudioOutManager() override; + Result SetAllAudioOutVolume(f32 volume); + private: Result ListAudioOuts(OutArray out_audio_outs, Out out_count); diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index c41345f7e7..88d69de0aa 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -19,6 +19,7 @@ constexpr Result ResultInvalidAddressInfo{ErrorModule::Audio, 42}; constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; +constexpr Result ResultInvalidArgument{ErrorModule::Audio, 900}; constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index c36af6ca49..98d56667c0 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -19,7 +19,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ service_context{system, "IApplicationManagerInterface"}, record_update_system_event{service_context}, sd_card_mount_status_event{service_context}, gamecard_update_detection_event{service_context}, - gamecard_mount_status_event{service_context}, gamecard_mount_failure_event{service_context} { + gamecard_mount_status_event{service_context}, gamecard_mount_failure_event{service_context}, gamecard_waken_ready_event{service_context} { // clang-format off static const FunctionInfo functions[] = { {0, D<&IApplicationManagerInterface::ListApplicationRecord>, "ListApplicationRecord"}, @@ -138,6 +138,8 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {508, nullptr, "GetLastGameCardMountFailureResult"}, {509, nullptr, "ListApplicationIdOnGameCard"}, {510, nullptr, "GetGameCardPlatformRegion"}, + {511, D<&IApplicationManagerInterface::GetGameCardWakenReadyEvent>, "GetGameCardWakenReadyEvent"}, + {512, D<&IApplicationManagerInterface::IsGameCardApplicationRunning>, "IsGameCardApplicationRunning"}, {600, nullptr, "CountApplicationContentMeta"}, {601, nullptr, "ListApplicationContentMetaStatus"}, {602, nullptr, "ListAvailableAddOnContent"}, @@ -403,6 +405,19 @@ Result IApplicationManagerInterface::GetGameCardMountFailureEvent( R_SUCCEED(); } +Result IApplicationManagerInterface::GetGameCardWakenReadyEvent( + OutCopyHandle out_event) { + LOG_WARNING(Service_NS, "(STUBBED) called"); + *out_event = gamecard_waken_ready_event.GetHandle(); + R_SUCCEED(); +} + +Result IApplicationManagerInterface::IsGameCardApplicationRunning(Out out_is_running) { + LOG_WARNING(Service_NS, "(STUBBED) called"); + *out_is_running = false; + R_SUCCEED(); +} + Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled( Out out_is_any_application_entity_installed) { LOG_WARNING(Service_NS, "(STUBBED) called"); diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h index 8a9eda3d13..205a9ec7a7 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -32,6 +32,8 @@ public: Out out_count, s32 offset); Result GetApplicationRecordUpdateSystemEvent(OutCopyHandle out_event); Result GetGameCardMountFailureEvent(OutCopyHandle out_event); + Result GetGameCardWakenReadyEvent(OutCopyHandle out_event); + Result IsGameCardApplicationRunning(Out out_is_running); Result IsAnyApplicationEntityInstalled(Out out_is_any_application_entity_installed); Result GetApplicationViewDeprecated( OutArray out_application_views, @@ -70,6 +72,7 @@ private: Event gamecard_update_detection_event; Event gamecard_mount_status_event; Event gamecard_mount_failure_event; + Event gamecard_waken_ready_event; }; } // namespace Service::NS diff --git a/src/core/hle/service/vi/container.cpp b/src/core/hle/service/vi/container.cpp index 18b8bf410f..ddd61fcd33 100644 --- a/src/core/hle/service/vi/container.cpp +++ b/src/core/hle/service/vi/container.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -139,11 +142,11 @@ Result Container::SetLayerZIndex(u64 layer_id, s32 z_index) { // Forward to nvnflinger layer via surface flinger (store on the layer struct) if (auto layer_ref = m_surface_flinger->FindLayer(layer->GetConsumerBinderId())) { - LOG_INFO(Service_VI, "Container: SetLayerZIndex layer_id={} z={} (cid={})", layer_id, + LOG_DEBUG(Service_VI, "called, SetLayerZIndex layer_id={} z={} (cid={})", layer_id, z_index, layer->GetConsumerBinderId()); layer_ref->z_index = z_index; } else { - LOG_INFO(Service_VI, "Container: SetLayerZIndex failed to find layer for layer_id={} (cid={})", + LOG_DEBUG(Service_VI, "called, SetLayerZIndex failed to find layer for layer_id={} (cid={})", layer_id, layer->GetConsumerBinderId()); }