From d70eb6ec99bc5e8b766dd1a90bbb7b55c9f7553b Mon Sep 17 00:00:00 2001 From: Kai Uwe Broulik Date: Sun, 20 Apr 2025 12:48:26 +0200 Subject: [PATCH] [PATCH] core: Add infrastructure for inhibiting suspend in jobs This calls the freedesktop Inhibit interface on DBus which will inhibit suspend (but not display power management/screensaver). When inside a sandbox it instead calls the XDG Desktop Portal Inhibit interface. When a job is destroyed or gets suspended, the inhibition is lifted. When a job is resumed, `doInhibitSuspend` is called again to re-instate the inhibition. It is the job's responsibility to call `doInhibitSuspend` at the appropriate time (e.g. in doStart/slotStart). Gbp-Pq: Name upstream_3c3d5904_core-Add-infrastructure-for-inhibiting-suspend-in-jobs.patch --- src/core/CMakeLists.txt | 5 + src/core/config-kiocore.h.cmake | 2 + src/core/job.cpp | 139 +++++++++++++- src/core/job_p.h | 16 ++ ...rg.freedesktop.PowerManagement.Inhibit.xml | 20 ++ src/core/org.freedesktop.portal.Inhibit.xml | 173 ++++++++++++++++++ src/core/org.freedesktop.portal.Request.xml | 93 ++++++++++ 7 files changed, 446 insertions(+), 2 deletions(-) create mode 100644 src/core/org.freedesktop.PowerManagement.Inhibit.xml create mode 100644 src/core/org.freedesktop.portal.Inhibit.xml create mode 100644 src/core/org.freedesktop.portal.Request.xml diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c4dcd58..6ace75e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -164,6 +164,11 @@ if (HAVE_QTDBUS) PROPERTIES INCLUDE authinfo.h ) qt_add_dbus_interface(kiocore_dbus_SRCS org.kde.KPasswdServer.xml kpasswdserver_interface) + + qt_add_dbus_interface(kiocore_dbus_SRCS org.freedesktop.PowerManagement.Inhibit.xml inhibit_interface) + + qt_add_dbus_interface(kiocore_dbus_SRCS org.freedesktop.portal.Inhibit.xml portal_inhibit_interface) + qt_add_dbus_interface(kiocore_dbus_SRCS org.freedesktop.portal.Request.xml portal_request_interface) endif() target_sources(KF6KIOCore PRIVATE diff --git a/src/core/config-kiocore.h.cmake b/src/core/config-kiocore.h.cmake index 1f0bc42..bd9e758 100644 --- a/src/core/config-kiocore.h.cmake +++ b/src/core/config-kiocore.h.cmake @@ -7,6 +7,8 @@ /* Defined if sys/acl.h exists */ #cmakedefine01 HAVE_SYS_ACL_H +#cmakedefine01 HAVE_QTDBUS + #define KDE_INSTALL_FULL_LIBEXECDIR_KF "${KDE_INSTALL_FULL_LIBEXECDIR_KF}" #define KDE_INSTALL_FULL_KIO_PLUGINDIR "${KDE_INSTALL_FULL_PLUGINDIR}/kf6/kio/" diff --git a/src/core/job.cpp b/src/core/job.cpp index e8360d4..4eb0378 100644 --- a/src/core/job.cpp +++ b/src/core/job.cpp @@ -13,13 +13,30 @@ #include #include +#include #include +#include "kiocoredebug.h" #include "worker_p.h" #include +#if HAVE_QTDBUS +#include +#include + +#include "inhibit_interface.h" +#include "portal_inhibit_interface.h" +#include "portal_request_interface.h" +#endif + using namespace KIO; +static constexpr QLatin1String g_portalServiceName{"org.freedesktop.portal.Desktop"}; +static constexpr QLatin1String g_portalInhibitObjectPath{"/org/freedesktop/portal/desktop"}; + +static constexpr QLatin1String g_inhibitServiceName{"org.freedesktop.PowerManagement.Inhibit"}; +static constexpr QLatin1String g_inhibitObjectPath{"/org/freedesktop/PowerManagement/Inhibit"}; + Job::Job() : KCompositeJob(nullptr) , d_ptr(new JobPrivate) @@ -89,9 +106,127 @@ static QString url_description_string(const QUrl &url) } KIO::JobPrivate::~JobPrivate() +{ + uninhibitSuspend(); +} + +void JobPrivate::doInhibitSuspend() { } +void JobPrivate::inhibitSuspend(const QString &reason) +{ +#if HAVE_QTDBUS + if (KSandbox::isInside()) { + Q_ASSERT(m_portalInhibitionRequest.path().isEmpty()); + + org::freedesktop::portal::Inhibit inhibitInterface{g_portalServiceName, g_portalInhibitObjectPath, QDBusConnection::sessionBus()}; + QVariantMap args; + if (!reason.isEmpty()) { + args.insert(QStringLiteral("reason"), reason); + } + auto call = inhibitInterface.Inhibit(QString() /* TODO window. */, 4 /* Suspend */, args); + // This is not parented to the job, so we can properly clean up the inhibiton + // should the job finish before the inhibition has been processed. + auto *watcher = new QDBusPendingCallWatcher(call); + QPointer guard(q_ptr); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [this, guard, watcher, reason] { + QDBusPendingReply reply = *watcher; + + if (reply.isError()) { + qCWarning(KIO_CORE).nospace() << "Failed to inhibit suspend with reason " << reason << ": " << reply.error().message(); + } else { + const QDBusObjectPath requestPath = reply.value(); + + // By the time the inhibition returned, the job was already gone. Uninhibit again. + if (!guard) { + org::freedesktop::portal::Request requestInterface{g_portalServiceName, requestPath.path(), QDBusConnection::sessionBus()}; + requestInterface.Close(); + } else { + m_portalInhibitionRequest = requestPath; + } + } + + watcher->deleteLater(); + }); + } else { + Q_ASSERT(!m_inhibitionCookie); + + QString appName = q_ptr->property("desktopFileName").toString(); + if (appName.isEmpty()) { + // desktopFileName is in QGuiApplication but we're in KIO Core here. + appName = QCoreApplication::instance()->property("desktopFileName").toString(); + } + if (appName.isEmpty()) { + appName = QCoreApplication::applicationName(); + } + + org::freedesktop::PowerManagement::Inhibit inhibitInterface{g_inhibitServiceName, g_inhibitObjectPath, QDBusConnection::sessionBus()}; + auto call = inhibitInterface.Inhibit(appName, reason); + auto *watcher = new QDBusPendingCallWatcher(call); + QPointer guard(q_ptr); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [this, guard, watcher, appName, reason] { + QDBusPendingReply reply = *watcher; + + if (reply.isError()) { + qCWarning(KIO_CORE).nospace() << "Failed to inhibit suspend for " << appName << " with reason " << reason << ": " << reply.error().message(); + } else { + const uint cookie = reply.value(); + + if (!guard) { + org::freedesktop::PowerManagement::Inhibit inhibitInterface{g_inhibitServiceName, g_inhibitObjectPath, QDBusConnection::sessionBus()}; + inhibitInterface.UnInhibit(cookie); + } else { + m_inhibitionCookie = cookie; + } + } + + watcher->deleteLater(); + }); + } +#else + Q_UNUSED(reason) +#endif +} + +void JobPrivate::uninhibitSuspend() +{ +#if HAVE_QTDBUS + if (!m_portalInhibitionRequest.path().isEmpty()) { + org::freedesktop::portal::Request requestInterface{g_portalServiceName, m_portalInhibitionRequest.path(), QDBusConnection::sessionBus()}; + auto call = requestInterface.Close(); + auto *watcher = new QDBusPendingCallWatcher(call, q_ptr); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q_ptr, [this, watcher] { + QDBusPendingReply<> reply = *watcher; + + if (reply.isError()) { + qCWarning(KIO_CORE) << "Failed to uninhibit suspend:" << reply.error().message(); + } else { + m_portalInhibitionRequest = QDBusObjectPath(); + } + + watcher->deleteLater(); + }); + } else if (m_inhibitionCookie) { + org::freedesktop::PowerManagement::Inhibit inhibitInterface{g_inhibitServiceName, g_inhibitObjectPath, QDBusConnection::sessionBus()}; + const int cookie = *m_inhibitionCookie; + auto call = inhibitInterface.UnInhibit(cookie); + auto *watcher = new QDBusPendingCallWatcher(call, q_ptr); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q_ptr, [this, watcher, cookie] { + QDBusPendingReply<> reply = *watcher; + + if (reply.isError()) { + qCWarning(KIO_CORE).nospace() << "Failed to uninhibit suspend for cookie" << cookie << ": " << reply.error().message(); + } else { + m_inhibitionCookie.reset(); + } + + watcher->deleteLater(); + }); + } +#endif +} + void JobPrivate::emitMoving(KIO::Job *job, const QUrl &src, const QUrl &dest) { static const QString s_title = i18nc("@title job", "Moving"); @@ -172,7 +307,7 @@ bool Job::doSuspend() return false; } } - + d_ptr->uninhibitSuspend(); return true; } @@ -183,7 +318,7 @@ bool Job::doResume() return false; } } - + d_ptr->doInhibitSuspend(); return true; } diff --git a/src/core/job_p.h b/src/core/job_p.h index e9eab0c..e819119 100644 --- a/src/core/job_p.h +++ b/src/core/job_p.h @@ -12,6 +12,8 @@ #ifndef KIO_JOB_P_H #define KIO_JOB_P_H +#include "config-kiocore.h" + #include "commands_p.h" #include "global.h" #include "jobtracker.h" @@ -26,6 +28,12 @@ #include #include +#if HAVE_QTDBUS +#include + +#include +#endif + /* clang-format off */ #define KIO_ARGS \ QByteArray packedArgs; \ @@ -84,6 +92,10 @@ public: MetaData m_outgoingMetaData; JobUiDelegateExtension *m_uiDelegateExtension; Job *q_ptr; +#if HAVE_QTDBUS + std::optional m_inhibitionCookie; // fdo. + QDBusObjectPath m_portalInhibitionRequest; // portal. +#endif // For privilege operation bool m_privilegeExecutionEnabled; QString m_title, m_message; @@ -92,6 +104,10 @@ public: QByteArray privilegeOperationData(); void slotSpeed(KJob *job, unsigned long speed); + void inhibitSuspend(const QString &reason); + void uninhibitSuspend(); + virtual void doInhibitSuspend(); + static void emitMoving(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitRenaming(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitCopying(KIO::Job *, const QUrl &src, const QUrl &dest); diff --git a/src/core/org.freedesktop.PowerManagement.Inhibit.xml b/src/core/org.freedesktop.PowerManagement.Inhibit.xml new file mode 100644 index 0000000..21dfce8 --- /dev/null +++ b/src/core/org.freedesktop.PowerManagement.Inhibit.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/core/org.freedesktop.portal.Inhibit.xml b/src/core/org.freedesktop.portal.Inhibit.xml new file mode 100644 index 0000000..1ae413e --- /dev/null +++ b/src/core/org.freedesktop.portal.Inhibit.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/org.freedesktop.portal.Request.xml b/src/core/org.freedesktop.portal.Request.xml new file mode 100644 index 0000000..e8a2648 --- /dev/null +++ b/src/core/org.freedesktop.portal.Request.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + -- 2.30.2