Browse Source

add overlay function to set emulator audio, and more.

pull/2953/head
Maufeat 3 months ago
parent
commit
fedb2b92d7
  1. 10
      src/core/hle/service/am/applet.cpp
  2. 1
      src/core/hle/service/am/applet.h
  3. 11
      src/core/hle/service/am/applet_manager.cpp
  4. 4
      src/core/hle/service/am/applet_manager.h
  5. 43
      src/core/hle/service/am/display_layer_manager.cpp
  6. 5
      src/core/hle/service/am/display_layer_manager.h
  7. 7
      src/core/hle/service/am/service/common_state_getter.cpp
  8. 45
      src/core/hle/service/am/service/overlay_functions.cpp
  9. 103
      src/core/hle/service/am/window_system.cpp
  10. 6
      src/core/hle/service/am/window_system.h
  11. 172
      src/core/hle/service/audio/audio_controller.cpp
  12. 11
      src/core/hle/service/audio/audio_controller.h
  13. 14
      src/core/hle/service/audio/audio_out_manager.cpp
  14. 5
      src/core/hle/service/audio/audio_out_manager.h
  15. 1
      src/core/hle/service/audio/errors.h
  16. 17
      src/core/hle/service/ns/application_manager_interface.cpp
  17. 3
      src/core/hle/service/ns/application_manager_interface.h
  18. 7
      src/core/hle/service/vi/container.cpp

10
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<u32>(applet_id), interactible, exit_requested, input_enabled, overlay_in_foreground);
}
hid_registration.EnableAppletToGetInput(input_enabled);
}
void Applet::OnProcessTerminatedLocked() {

1
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;

11
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;

4
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;

43
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);

5
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:

7
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<AppletMessage> out_applet_message)
R_THROW(AM::ResultNoMessages);
}
LOG_INFO(Service_AM, "called, returning message={} to applet_id={}",
static_cast<u32>(*out_applet_message), static_cast<u32>(m_applet->applet_id));
R_SUCCEED();
}

45
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<u64> 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<u64> 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();
}

103
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> 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<u32>(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);
}
}

6
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> 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.

172
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 <cmath>
// for std::clamp
#include <algorithm>
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<Service::Set::ISystemSettingsServer>("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<int>(std::lround((static_cast<double>(ui_volume) / 100.0) * 15.0));
const auto active_idx = static_cast<size_t>(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<float>(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<Service::Audio::IAudioOutManager>("audout:u")) {
audout_mgr->SetAllAudioOutVolume(static_cast<float>(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<SharedPointer<IAudioController>> out_au
R_SUCCEED();
}
Result IAudioController::GetTargetVolume(Out<s32> out_target_volume, Set::AudioOutputModeTarget target) {
LOG_DEBUG(Audio, "GetTargetVolume called, target={}", target);
const auto idx = static_cast<size_t>(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<size_t>(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<float>(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<IAudioOutManager>("audout:u")) {
audout_mgr->SetAllAudioOutVolume(vol);
}
}
if (m_active_target == target) {
const int ui_volume = static_cast<int>(std::lround((static_cast<double>(target_volume) / 15.0) * 100.0));
Settings::values.volume.SetValue(static_cast<u8>(std::clamp(ui_volume, 0, 100)));
}
R_SUCCEED();
}
Result IAudioController::IsTargetMute(Out<bool> out_is_target_muted, Set::AudioOutputModeTarget target) {
LOG_DEBUG(Audio, "called, target={}", target);
const auto idx = static_cast<size_t>(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<size_t>(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<float>(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<IAudioOutManager>("audout:u")) {
if (is_muted) {
audout_mgr->SetAllAudioOutVolume(0.0f);
} else {
const float vol = static_cast<float>(m_target_volumes[idx]) / 15.0f;
audout_mgr->SetAllAudioOutVolume(vol);
}
}
}
Settings::values.audio_muted.SetValue(is_muted);
R_SUCCEED();
}
Result IAudioController::GetActiveOutputTarget(Out<Set::AudioOutputModeTarget> out_active_target) {
LOG_DEBUG(Audio, "GetActiveOutputTarget called");
*out_active_target = m_active_target;
R_SUCCEED();
}
} // namespace Service::Audio

11
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<s32> out_target_min_volume);
Result GetTargetVolumeMax(Out<s32> out_target_max_volume);
Result GetTargetVolume(Out<s32> out_target_volume, Set::AudioOutputModeTarget target);
Result SetTargetVolume(Set::AudioOutputModeTarget target, s32 target_volume);
Result IsTargetMute(Out<bool> out_is_target_muted, Set::AudioOutputModeTarget target);
Result SetTargetMute(bool is_muted, Set::AudioOutputModeTarget target);
Result GetActiveOutputTarget(Out<Set::AudioOutputModeTarget> out_active_target);
Result GetAudioOutputMode(Out<Set::AudioOutputMode> 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<Service::Set::ISystemSettingsServer> m_set_sys;
std::array<s32, 6> m_target_volumes{{15, 15, 15, 15, 15, 15}};
std::array<bool, 6> m_target_muted{{false, false, false, false, false, false}};
Set::AudioOutputModeTarget m_active_target{Set::AudioOutputModeTarget::Speaker};
};
} // namespace Service::Audio

14
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

5
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<AudioDeviceName, BufferAttr_HipcMapAlias> out_audio_outs,
Out<u32> out_count);

1
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};

17
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<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called");
*out_event = gamecard_waken_ready_event.GetHandle();
R_SUCCEED();
}
Result IApplicationManagerInterface::IsGameCardApplicationRunning(Out<bool> out_is_running) {
LOG_WARNING(Service_NS, "(STUBBED) called");
*out_is_running = false;
R_SUCCEED();
}
Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
Out<bool> out_is_any_application_entity_installed) {
LOG_WARNING(Service_NS, "(STUBBED) called");

3
src/core/hle/service/ns/application_manager_interface.h

@ -32,6 +32,8 @@ public:
Out<s32> out_count, s32 offset);
Result GetApplicationRecordUpdateSystemEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetGameCardMountFailureEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetGameCardWakenReadyEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result IsGameCardApplicationRunning(Out<bool> out_is_running);
Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed);
Result GetApplicationViewDeprecated(
OutArray<ApplicationView, BufferAttr_HipcMapAlias> 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

7
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());
}

Loading…
Cancel
Save