diff --git a/framework/audio/driver/CMakeLists.txt b/framework/audio/driver/CMakeLists.txt index 442cb70cb7..6c02abbf82 100644 --- a/framework/audio/driver/CMakeLists.txt +++ b/framework/audio/driver/CMakeLists.txt @@ -79,11 +79,14 @@ elseif(OS_IS_MAC) platform/osx/osxaudiodriver.h platform/osx/osxdirectaudiodriver.mm platform/osx/osxdirectaudiodriver.h + platform/osx/osxidlesleeppolicy.mm + platform/osx/osxidlesleeppolicy.h ) - + set_source_files_properties( platform/osx/osxaudiodriver.mm platform/osx/osxdirectaudiodriver.mm + platform/osx/osxidlesleeppolicy.mm PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON SKIP_PRECOMPILE_HEADERS ON diff --git a/framework/audio/driver/platform/osx/osxaudiodriver.h b/framework/audio/driver/platform/osx/osxaudiodriver.h index 74f27aedfe..f9a957f6f8 100644 --- a/framework/audio/driver/platform/osx/osxaudiodriver.h +++ b/framework/audio/driver/platform/osx/osxaudiodriver.h @@ -51,6 +51,8 @@ class OSXAudioDriver : public IAudioDriver void close() override; bool isOpened() const override; + void setLivePlaybackOngoing(bool ongoing) override; + const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; @@ -79,6 +81,7 @@ class OSXAudioDriver : public IAudioDriver std::map m_outputDevices = {}, m_inputDevices = {}; mutable std::mutex m_devicesMutex; async::Notification m_availableOutputDevicesChanged; + bool m_livePlaybackOngoing = false; }; } #endif // MUSE_AUDIO_OSXAUDIODRIVER_H diff --git a/framework/audio/driver/platform/osx/osxaudiodriver.mm b/framework/audio/driver/platform/osx/osxaudiodriver.mm index 79eae85c12..655e82cb98 100644 --- a/framework/audio/driver/platform/osx/osxaudiodriver.mm +++ b/framework/audio/driver/platform/osx/osxaudiodriver.mm @@ -26,6 +26,8 @@ #include +#include "osxidlesleeppolicy.h" + #include "translation.h" #include "log.h" @@ -165,6 +167,10 @@ void clear() return false; } + //! NOTE From now on the device is running, so make sure the system knows whether + //! it is allowed to go to sleep while that is the case + OSXIdleSleepPolicy::setPreventIdleSleep(m_livePlaybackOngoing); + m_activeSpecChanged.send(m_data->format); LOGI() << "Connected to " << m_data->format.deviceId @@ -193,6 +199,12 @@ void clear() return m_data->audioQueue != nullptr; } +void OSXAudioDriver::setLivePlaybackOngoing(bool ongoing) +{ + m_livePlaybackOngoing = ongoing; + OSXIdleSleepPolicy::setPreventIdleSleep(ongoing); +} + const OSXAudioDriver::Spec& OSXAudioDriver::activeSpec() const { return m_data->format; diff --git a/framework/audio/driver/platform/osx/osxdirectaudiodriver.h b/framework/audio/driver/platform/osx/osxdirectaudiodriver.h index 215d3bedd8..edec6885d7 100644 --- a/framework/audio/driver/platform/osx/osxdirectaudiodriver.h +++ b/framework/audio/driver/platform/osx/osxdirectaudiodriver.h @@ -53,6 +53,8 @@ class OSXDirectAudioDriver : public IAudioDriver void close() override; bool isOpened() const override; + void setLivePlaybackOngoing(bool ongoing) override; + const Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; @@ -88,6 +90,7 @@ class OSXDirectAudioDriver : public IAudioDriver AudioWorkGroup m_audioWorkGroup; bool m_deviceMapListenerRegistered = false; + bool m_livePlaybackOngoing = false; }; } #endif // MUSE_AUDIO_OSXDIRECTAUDIODRIVER_H diff --git a/framework/audio/driver/platform/osx/osxdirectaudiodriver.mm b/framework/audio/driver/platform/osx/osxdirectaudiodriver.mm index fa2423569d..5008b05836 100644 --- a/framework/audio/driver/platform/osx/osxdirectaudiodriver.mm +++ b/framework/audio/driver/platform/osx/osxdirectaudiodriver.mm @@ -39,6 +39,7 @@ #include "common/audiotypes.h" #include "common/audioworkgroup.h" +#include "osxidlesleeppolicy.h" #include "translation.h" #include "log.h" @@ -668,6 +669,10 @@ AudioWorkGroup createAudioWorkgroup(OSXAudioDeviceID deviceId) return false; } + //! NOTE From now on the device is running, so make sure the system knows whether + //! it is allowed to go to sleep while that is the case + OSXIdleSleepPolicy::setPreventIdleSleep(m_livePlaybackOngoing); + m_audioWorkGroup = createAudioWorkgroup(*deviceId); m_currentWorkgroupChanged.notify(); @@ -717,6 +722,12 @@ AudioWorkGroup createAudioWorkgroup(OSXAudioDeviceID deviceId) return m_data->procId != nullptr; } +void OSXDirectAudioDriver::setLivePlaybackOngoing(bool ongoing) +{ + m_livePlaybackOngoing = ongoing; + OSXIdleSleepPolicy::setPreventIdleSleep(ongoing); +} + const OSXDirectAudioDriver::Spec& OSXDirectAudioDriver::activeSpec() const { return m_data->format; diff --git a/framework/audio/driver/platform/osx/osxidlesleeppolicy.h b/framework/audio/driver/platform/osx/osxidlesleeppolicy.h new file mode 100644 index 0000000000..ebe9ab62da --- /dev/null +++ b/framework/audio/driver/platform/osx/osxidlesleeppolicy.h @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +namespace muse::audio { +//! NOTE macOS prevents the system from going to idle sleep for as long as a process has +//! audio IO running, no matter whether that IO is audible. Since we keep the audio device +//! open for the whole session, that would keep the Mac awake permanently, also when idle. +//! So we tell CoreAudio that idle sleep is fine, and only opt out of it while something +//! is actually being played back. +//! +//! The policy applies to the process as a whole, so it is shared by all drivers. +class OSXIdleSleepPolicy +{ +public: + static void setPreventIdleSleep(bool prevent); +}; +} diff --git a/framework/audio/driver/platform/osx/osxidlesleeppolicy.mm b/framework/audio/driver/platform/osx/osxidlesleeppolicy.mm new file mode 100644 index 0000000000..f0fb06d08a --- /dev/null +++ b/framework/audio/driver/platform/osx/osxidlesleeppolicy.mm @@ -0,0 +1,92 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "osxidlesleeppolicy.h" + +#include + +#include + +#include "log.h" + +using namespace muse::audio; + +//! NOTE The policy is set from the thread that starts and stops playback, but reapplied +//! from a CoreAudio owned thread, so reading it and applying it has to be serialized +static std::mutex s_mutex; +static bool s_preventIdleSleep = false; + +static void applyPolicy() +{ + std::lock_guard lock(s_mutex); + + //! NOTE 1 means: allow the CPU to idle sleep even if there is audio IO in progress + UInt32 sleepingIsAllowed = s_preventIdleSleep ? 0 : 1; + + AudioObjectPropertyAddress address = { + .mSelector = kAudioHardwarePropertySleepingIsAllowed, + .mScope = kAudioObjectPropertyScopeGlobal, + .mElement = kAudioObjectPropertyElementMain + }; + + OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject, &address, 0, nullptr, + sizeof(sleepingIsAllowed), &sleepingIsAllowed); + if (result != noErr) { + LOGE() << "Failed to set idle sleep policy, err: " << result; + } +} + +static OSStatus onServiceRestarted(AudioObjectID, UInt32, const AudioObjectPropertyAddress*, void*) +{ + //! NOTE The policy belongs to our HAL client, which is recreated when coreaudiod restarts + applyPolicy(); + return noErr; +} + +static void initServiceRestartListener() +{ + static std::once_flag onceFlag; + std::call_once(onceFlag, []() { + AudioObjectPropertyAddress address = { + .mSelector = kAudioHardwarePropertyServiceRestarted, + .mScope = kAudioObjectPropertyScopeGlobal, + .mElement = kAudioObjectPropertyElementMain + }; + + OSStatus result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &address, &onServiceRestarted, nullptr); + if (result != noErr) { + LOGE() << "Failed to add service restart listener, err: " << result; + } + }); +} + +void OSXIdleSleepPolicy::setPreventIdleSleep(bool prevent) +{ + initServiceRestartListener(); + + { + std::lock_guard lock(s_mutex); + s_preventIdleSleep = prevent; + } + + applyPolicy(); +} diff --git a/framework/audio/iaudiodriver.h b/framework/audio/iaudiodriver.h index b10d139c65..d0160511e0 100644 --- a/framework/audio/iaudiodriver.h +++ b/framework/audio/iaudiodriver.h @@ -61,6 +61,12 @@ class IAudioDriver virtual void close() = 0; virtual bool isOpened() const = 0; + //! NOTE Tells the driver whether audio is currently being played back in real time. + //! The driver is kept open for the whole session and simply receives silence when + //! nothing is playing, so it cannot tell the difference on its own. + //! Drivers may use this to adjust platform specific behaviour; ignored by default. + virtual void setLivePlaybackOngoing(bool ongoing) { (void)ongoing; } + virtual const Spec& activeSpec() const = 0; virtual async::Channel activeSpecChanged() const = 0; diff --git a/framework/audio/iaudiodrivercontroller.h b/framework/audio/iaudiodrivercontroller.h index d3d2f630e1..a9fbb14433 100644 --- a/framework/audio/iaudiodrivercontroller.h +++ b/framework/audio/iaudiodrivercontroller.h @@ -48,6 +48,9 @@ class IAudioDriverController : MODULE_GLOBAL_INTERFACE virtual void close() = 0; virtual bool isOpened() const = 0; + //! NOTE See IAudioDriver::setLivePlaybackOngoing + virtual void setLivePlaybackOngoing(bool ongoing) = 0; + virtual const IAudioDriver::Spec& activeSpec() const = 0; virtual async::Channel activeSpecChanged() const = 0; diff --git a/framework/audio/main/internal/audiodrivercontroller.cpp b/framework/audio/main/internal/audiodrivercontroller.cpp index 64ed274120..2c2ed5525b 100644 --- a/framework/audio/main/internal/audiodrivercontroller.cpp +++ b/framework/audio/main/internal/audiodrivercontroller.cpp @@ -178,6 +178,9 @@ void AudioDriverController::setNewDriver(IAudioDriverPtr newDriver) m_audioDriver = newDriver; if (m_audioDriver) { + //! NOTE The new driver doesn't know yet what is going on + m_audioDriver->setLivePlaybackOngoing(m_livePlaybackOngoing); + // subscribe m_audioDriver->availableOutputDevicesChanged().onNotify(this, [this]() { async::Async::call(this, [this]() { @@ -317,6 +320,19 @@ bool AudioDriverController::isOpened() const return m_audioDriver->isOpened(); } +void AudioDriverController::setLivePlaybackOngoing(bool ongoing) +{ + if (m_livePlaybackOngoing == ongoing) { + return; + } + + m_livePlaybackOngoing = ongoing; + + if (m_audioDriver) { + m_audioDriver->setLivePlaybackOngoing(ongoing); + } +} + const IAudioDriver::Spec& AudioDriverController::activeSpec() const { IF_ASSERT_FAILED(m_audioDriver) { diff --git a/framework/audio/main/internal/audiodrivercontroller.h b/framework/audio/main/internal/audiodrivercontroller.h index 7a6260858b..965177b661 100644 --- a/framework/audio/main/internal/audiodrivercontroller.h +++ b/framework/audio/main/internal/audiodrivercontroller.h @@ -54,6 +54,8 @@ class AudioDriverController : public IAudioDriverController, public async::Async void close() override; bool isOpened() const override; + void setLivePlaybackOngoing(bool ongoing) override; + const IAudioDriver::Spec& activeSpec() const override; async::Channel activeSpecChanged() const override; @@ -91,5 +93,6 @@ class AudioDriverController : public IAudioDriverController, public async::Async async::Notification m_outputDeviceSampleRateChanged; bool m_retryOpenDevice = false; + bool m_livePlaybackOngoing = false; }; } diff --git a/framework/audio/main/internal/player.cpp b/framework/audio/main/internal/player.cpp index afe9ebbe6a..a2905d201c 100644 --- a/framework/audio/main/internal/player.cpp +++ b/framework/audio/main/internal/player.cpp @@ -21,6 +21,8 @@ */ #include "player.h" +#include + #include "audio/common/rpc/rpcpacker.h" #include "audio/common/audiosanitizer.h" @@ -31,11 +33,37 @@ using namespace muse::async; using namespace muse::audio; using namespace muse::audio::rpc; +//! NOTE Playback is contextual (each window has its own), and every context can hand out +//! more than one player, so live playback is ongoing as long as any of them is running. +static std::set& runningPlayers() +{ + static std::set s_runningPlayers; + return s_runningPlayers; +} + +Player::~Player() +{ + if (runningPlayers().erase(this) != 0) { + updateLivePlaybackOngoing(); + } +} + rpc::CtxId Player::ctxId() const { return rpc::ctxId(iocContext()); } +void Player::updateLivePlaybackOngoing() +{ + ONLY_AUDIO_MAIN_THREAD; + + //! NOTE Can already be gone on shutdown + const std::shared_ptr& controller = audioDriverController.get(); + if (controller) { + controller->setLivePlaybackOngoing(!runningPlayers().empty()); + } +} + void Player::init() { ONLY_AUDIO_MAIN_THREAD; @@ -45,6 +73,16 @@ void Player::init() { m_playbackStatusChanged.onReceive(this, [this](PlaybackStatus st) { m_playbackStatus = st; + + //! NOTE Only while running is audio actually being produced; + //! when paused or stopped there is nothing to keep the system awake for + if (m_playbackStatus == PlaybackStatus::Running) { + runningPlayers().insert(this); + } else { + runningPlayers().erase(this); + } + + updateLivePlaybackOngoing(); }); Msg msg = rpc::make_request(ctxId(), MsgCode::GetPlaybackStatus); diff --git a/framework/audio/main/internal/player.h b/framework/audio/main/internal/player.h index 0ae06a14d3..0f50a01b25 100644 --- a/framework/audio/main/internal/player.h +++ b/framework/audio/main/internal/player.h @@ -29,16 +29,20 @@ #include "modularity/ioc.h" #include "audio/common/rpc/irpcchannel.h" +#include "audio/iaudiodrivercontroller.h" namespace muse::audio { class Player : public IPlayer, public Contextable, public async::Asyncable { GlobalInject channel; + GlobalInject audioDriverController; public: Player(const muse::modularity::ContextPtr& ctx) : Contextable(ctx) {} + ~Player() override; + void init(); async::Promise prepareToPlay() override; @@ -63,6 +67,8 @@ class Player : public IPlayer, public Contextable, public async::Asyncable rpc::CtxId ctxId() const; + void updateLivePlaybackOngoing(); + PlaybackStatus m_playbackStatus = PlaybackStatus::Stopped; async::Channel m_playbackStatusChanged; diff --git a/framework/global/CMakeLists.txt b/framework/global/CMakeLists.txt index ebd5e9a810..c3b1ffebf5 100644 --- a/framework/global/CMakeLists.txt +++ b/framework/global/CMakeLists.txt @@ -58,6 +58,7 @@ target_sources(muse_global PRIVATE timer.cpp timer.h progress.h + idlesleepblocker.h utils.cpp utils.h interpolation.h @@ -243,6 +244,34 @@ else() endif() endif() +#! NOTE IdleSleepBlocker is always declared, so it always needs an implementation +include(GetPlatformInfo) + +if (GLOBAL_NO_INTERNAL) + target_sources(muse_global PRIVATE + internal/platform/stub/idlesleepblocker.cpp + ) +elseif (OS_IS_MAC) + target_sources(muse_global PRIVATE + internal/platform/macos/idlesleepblocker.cpp + ) + find_library(IOKit NAMES IOKit) + target_link_libraries(muse_global PRIVATE ${IOKit}) +elseif (OS_IS_WIN) + target_sources(muse_global PRIVATE + internal/platform/windows/idlesleepblocker.cpp + ) +elseif ((OS_IS_LIN OR OS_IS_FBSD) AND MUSE_QT_SUPPORT) + target_sources(muse_global PRIVATE + internal/platform/linux/idlesleepblocker.cpp + ) + target_link_libraries(muse_global PRIVATE Qt::DBus) +else() + target_sources(muse_global PRIVATE + internal/platform/stub/idlesleepblocker.cpp + ) +endif() + include(GetCompilerInfo) if (NOT CC_IS_EMCC) #zlib included in main linker target_link_libraries(muse_global PRIVATE zlib::zlib) diff --git a/framework/global/idlesleepblocker.h b/framework/global/idlesleepblocker.h new file mode 100644 index 0000000000..cd9c35321c --- /dev/null +++ b/framework/global/idlesleepblocker.h @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +namespace muse { +//! NOTE Prevents the system from going to idle sleep for as long as this object exists. +//! Meant for tasks that can take a long time while the user is not interacting with the +//! application and nothing is audible, like exporting an audio file: without this, the +//! system can suspend halfway through such a task. +//! +//! It does not prevent the display from sleeping, and it does not prevent sleep that the +//! user or the system asks for explicitly (closing the lid, low battery, etc.). +//! +//! Blocking is best effort: if the platform refuses, or has no way to express this, the +//! failure is logged and the object simply does nothing. +class IdleSleepBlocker +{ +public: + //! NOTE `reason` is shown to the user by some systems, so it should describe the task + //! being performed, and be translated + explicit IdleSleepBlocker(const std::string& reason); + ~IdleSleepBlocker(); + + IdleSleepBlocker(const IdleSleepBlocker&) = delete; + IdleSleepBlocker& operator=(const IdleSleepBlocker&) = delete; + +private: + struct Data; + std::unique_ptr m_data; +}; + +using IdleSleepBlockerPtr = std::shared_ptr; +} diff --git a/framework/global/internal/platform/linux/idlesleepblocker.cpp b/framework/global/internal/platform/linux/idlesleepblocker.cpp new file mode 100644 index 0000000000..c99f42a14b --- /dev/null +++ b/framework/global/internal/platform/linux/idlesleepblocker.cpp @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../../../idlesleepblocker.h" + +#include +#include +#include +#include +#include + +#include "log.h" + +using namespace muse; + +struct IdleSleepBlocker::Data { + //! NOTE Holding the descriptor is what holds the inhibitor; closing it releases it + QDBusUnixFileDescriptor descriptor; +}; + +IdleSleepBlocker::IdleSleepBlocker(const std::string& reason) + : m_data(std::make_unique()) +{ + QDBusInterface manager("org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + QDBusConnection::systemBus()); + + if (!manager.isValid()) { + LOGW() << "Failed to connect to login1, the system may go to sleep"; + return; + } + + QDBusReply reply = manager.call("Inhibit", + QStringLiteral("idle"), + QCoreApplication::applicationName(), + QString::fromStdString(reason), + QStringLiteral("block")); + + if (!reply.isValid()) { + LOGW() << "Failed to inhibit idle sleep: " << reply.error().message().toStdString() + << ", the system may go to sleep"; + return; + } + + m_data->descriptor = reply.value(); +} + +IdleSleepBlocker::~IdleSleepBlocker() = default; diff --git a/framework/global/internal/platform/macos/idlesleepblocker.cpp b/framework/global/internal/platform/macos/idlesleepblocker.cpp new file mode 100644 index 0000000000..55a11affbd --- /dev/null +++ b/framework/global/internal/platform/macos/idlesleepblocker.cpp @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../../../idlesleepblocker.h" + +#include +#include + +#include "log.h" + +using namespace muse; + +struct IdleSleepBlocker::Data { + IOPMAssertionID assertionId = kIOPMNullAssertionID; +}; + +IdleSleepBlocker::IdleSleepBlocker(const std::string& reason) + : m_data(std::make_unique()) +{ + CFStringRef reasonRef = CFStringCreateWithCString(kCFAllocatorDefault, reason.c_str(), kCFStringEncodingUTF8); + if (!reasonRef) { + LOGW() << "Failed to create reason string, the system may go to sleep"; + return; + } + + IOPMAssertionID assertionId = kIOPMNullAssertionID; + IOReturn result = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep, + kIOPMAssertionLevelOn, reasonRef, &assertionId); + CFRelease(reasonRef); + + if (result != kIOReturnSuccess) { + LOGW() << "Failed to create power assertion, err: " << result << ", the system may go to sleep"; + return; + } + + m_data->assertionId = assertionId; +} + +IdleSleepBlocker::~IdleSleepBlocker() +{ + if (m_data->assertionId != kIOPMNullAssertionID) { + IOPMAssertionRelease(m_data->assertionId); + } +} diff --git a/framework/global/internal/platform/stub/idlesleepblocker.cpp b/framework/global/internal/platform/stub/idlesleepblocker.cpp new file mode 100644 index 0000000000..c83720c8d0 --- /dev/null +++ b/framework/global/internal/platform/stub/idlesleepblocker.cpp @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../../../idlesleepblocker.h" + +using namespace muse; + +struct IdleSleepBlocker::Data { +}; + +IdleSleepBlocker::IdleSleepBlocker(const std::string&) + : m_data(std::make_unique()) +{ +} + +IdleSleepBlocker::~IdleSleepBlocker() = default; diff --git a/framework/global/internal/platform/windows/idlesleepblocker.cpp b/framework/global/internal/platform/windows/idlesleepblocker.cpp new file mode 100644 index 0000000000..b18c5a2a18 --- /dev/null +++ b/framework/global/internal/platform/windows/idlesleepblocker.cpp @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../../../idlesleepblocker.h" + +#include + +#include "log.h" + +using namespace muse; + +struct IdleSleepBlocker::Data { + //! NOTE Kept alive because the reason string is referenced by the request, not copied + std::wstring reason; + HANDLE request = INVALID_HANDLE_VALUE; +}; + +IdleSleepBlocker::IdleSleepBlocker(const std::string& reason) + : m_data(std::make_unique()) +{ + int size = MultiByteToWideChar(CP_UTF8, 0, reason.c_str(), static_cast(reason.size()), nullptr, 0); + m_data->reason.resize(static_cast(size)); + MultiByteToWideChar(CP_UTF8, 0, reason.c_str(), static_cast(reason.size()), m_data->reason.data(), size); + + REASON_CONTEXT context = {}; + context.Version = POWER_REQUEST_CONTEXT_VERSION; + context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING; + context.Reason.SimpleReasonString = m_data->reason.data(); + + HANDLE request = PowerCreateRequest(&context); + if (request == INVALID_HANDLE_VALUE) { + LOGW() << "Failed to create power request, err: " << GetLastError() << ", the system may go to sleep"; + return; + } + + if (!PowerSetRequest(request, PowerRequestSystemRequired)) { + LOGW() << "Failed to set power request, err: " << GetLastError() << ", the system may go to sleep"; + CloseHandle(request); + return; + } + + m_data->request = request; +} + +IdleSleepBlocker::~IdleSleepBlocker() +{ + if (m_data->request == INVALID_HANDLE_VALUE) { + return; + } + + PowerClearRequest(m_data->request, PowerRequestSystemRequired); + CloseHandle(m_data->request); +} diff --git a/framework/stubs/audio/audiodrivercontrollerstub.cpp b/framework/stubs/audio/audiodrivercontrollerstub.cpp index 8c707018e9..2733138c4e 100644 --- a/framework/stubs/audio/audiodrivercontrollerstub.cpp +++ b/framework/stubs/audio/audiodrivercontrollerstub.cpp @@ -69,6 +69,10 @@ bool AudioDriverControllerStub::isOpened() const return false; } +void AudioDriverControllerStub::setLivePlaybackOngoing(bool) +{ +} + const IAudioDriver::Spec& AudioDriverControllerStub::activeSpec() const { static IAudioDriver::Spec spec; diff --git a/framework/stubs/audio/audiodrivercontrollerstub.h b/framework/stubs/audio/audiodrivercontrollerstub.h index a6516f23fc..c38d293184 100644 --- a/framework/stubs/audio/audiodrivercontrollerstub.h +++ b/framework/stubs/audio/audiodrivercontrollerstub.h @@ -43,6 +43,8 @@ class AudioDriverControllerStub : public IAudioDriverController void close() override; bool isOpened() const override; + void setLivePlaybackOngoing(bool ongoing) override; + const IAudioDriver::Spec& activeSpec() const override; async::Channel activeSpecChanged() const override;