From 99d906118d1a567bb18a503a08bfda8c4664320d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 13:50:38 +0200 Subject: [PATCH] [libcalamares] Suppress sleep / suspend during installation --- src/libcalamares/JobQueue.cpp | 165 ++++++++++++++++++++++++++++++++++ src/libcalamares/JobQueue.h | 9 ++ 2 files changed, 174 insertions(+) diff --git a/src/libcalamares/JobQueue.cpp b/src/libcalamares/JobQueue.cpp index e15df345e..ed06510e1 100644 --- a/src/libcalamares/JobQueue.cpp +++ b/src/libcalamares/JobQueue.cpp @@ -15,14 +15,175 @@ #include "Job.h" #include "utils/Logger.h" +#include +#include +#include +#include +#include #include #include #include #include +namespace +{ +// This power-management code is largely cribbed from KDE Discover, +// https://invent.kde.org/plasma/discover/-/blob/master/discover/PowerManagementInterface.cpp +// +// Upstream license text says: +// +// SPDX-FileCopyrightText: 2019 (c) Matthieu Gallien +// SPDX-License-Identifier: LGPL-3.0-or-later + + +/** @brief Class to manage sleep / suspend on inactivity + * + * Create an object of this class on the heap. Call inhibitSleep() + * to (try to) stop system sleep / suspend. Call uninhibitSleep() + * when the object is no longer needed. The object self-deletes + * after uninhibitSleep() completes. + */ +class PowerManagementInterface : public QObject +{ + Q_OBJECT +public: + PowerManagementInterface( QObject* parent = nullptr ); + ~PowerManagementInterface() override; + +public Q_SLOTS: + void inhibitSleep(); + void uninhibitSleep(); + +private Q_SLOTS: + void hostSleepInhibitChanged(); + void inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ); + void uninhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ); + +private: + uint m_inhibitSleepCookie = 0; + bool m_inhibitedSleep = false; +}; + +PowerManagementInterface::PowerManagementInterface( QObject* parent ) + : QObject( parent ) +{ + auto sessionBus = QDBusConnection::sessionBus(); + + sessionBus.connect( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ), + QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "HasInhibitChanged" ), + this, + SLOT( hostSleepInhibitChanged() ) ); +} + +PowerManagementInterface::~PowerManagementInterface() = default; + +void +PowerManagementInterface::hostSleepInhibitChanged() +{ + // We don't actually care +} + +void +PowerManagementInterface::inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ) +{ + QDBusPendingReply< uint > reply = *aWatcher; + if ( reply.isError() ) + { + cError() << "Could not inhibit sleep:" << reply.error(); + // m_inhibitedSleep = false; // unchanged + } + else + { + m_inhibitSleepCookie = reply.argumentAt< 0 >(); + m_inhibitedSleep = true; + cDebug() << "Sleep inhibited, cookie" << m_inhibitSleepCookie; + } + aWatcher->deleteLater(); +} + +void +PowerManagementInterface::uninhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ) +{ + QDBusPendingReply<> reply = *aWatcher; + if ( reply.isError() ) + { + cError() << "Could not uninhibit sleep:" << reply.error(); + } + else + { + m_inhibitedSleep = false; + m_inhibitSleepCookie = 0; + cDebug() << "Sleep uninhibited."; + } + aWatcher->deleteLater(); + this->deleteLater(); +} + +void +PowerManagementInterface::inhibitSleep() +{ + if ( m_inhibitedSleep ) + { + cDebug() << "Sleep is already inhibited."; + return; + } + + auto sessionBus = QDBusConnection::sessionBus(); + auto inhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ), + QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "Inhibit" ) ); + inhibitCall.setArguments( + { { tr( "Calamares" ) }, { tr( "Installation in progress", "@status" ) } } ); + + auto asyncReply = sessionBus.asyncCall( inhibitCall ); + auto* replyWatcher = new QDBusPendingCallWatcher( asyncReply, this ); + QObject::connect( + replyWatcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInterface::inhibitDBusCallFinished ); +} + +void +PowerManagementInterface::uninhibitSleep() +{ + if ( !m_inhibitedSleep ) + { + cDebug() << "Sleep was never inhibited."; + this->deleteLater(); + return; + } + + auto sessionBus = QDBusConnection::sessionBus(); + auto uninhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ), + QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "UnInhibit" ) ); + uninhibitCall.setArguments( { { m_inhibitSleepCookie } } ); + + auto asyncReply = sessionBus.asyncCall( uninhibitCall ); + auto replyWatcher = new QDBusPendingCallWatcher( asyncReply, this ); + QObject::connect( + replyWatcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInterface::uninhibitDBusCallFinished ); +} + +} // namespace + namespace Calamares { +SleepInhibitor::SleepInhibitor() +{ + // Create a PowerManagementInterface object with intentionally no parent + // so it is not destroyed along with this. Instead, when this + // is destroyed, **start** the uninhibit-sleep call which will (later) + // destroy the PowerManagementInterface object. + auto* p = new PowerManagementInterface( nullptr ); + p->inhibitSleep(); + connect( this, &QObject::destroyed, p, &PowerManagementInterface::uninhibitSleep ); +} + +SleepInhibitor::~SleepInhibitor() = default; struct WeightedJob { @@ -267,6 +428,10 @@ JobQueue::start() m_thread->finalize(); m_finished = false; m_thread->start(); + + auto* inhibitor = new PowerManagementInterface( this ); + inhibitor->inhibitSleep(); + connect( this, &JobQueue::finished, inhibitor, &PowerManagementInterface::uninhibitSleep ); } diff --git a/src/libcalamares/JobQueue.h b/src/libcalamares/JobQueue.h index 699f52e42..03096cbfe 100644 --- a/src/libcalamares/JobQueue.h +++ b/src/libcalamares/JobQueue.h @@ -20,6 +20,15 @@ namespace Calamares class GlobalStorage; class JobThread; +///@brief RAII class to suppress sleep / suspend during its lifetime +class DLLEXPORT SleepInhibitor : public QObject +{ + Q_OBJECT +public: + SleepInhibitor(); + ~SleepInhibitor() override; +}; + class DLLEXPORT JobQueue : public QObject { Q_OBJECT