From a04768901ac13985dd673bbb45f2bf98d9a52903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Fri, 15 Jan 2016 11:18:17 +0100 Subject: [PATCH] Suspend/Hibernate through daemon instead of greeter Summary: This change addresses multiple problems with the implementation of PowerManagement's suspend/hibernate in the greeter. First of all it removes the usage of KDELibs4Support library by dropping the usage of the PowerManagement API. The previous variant performed blocking DBus calls which we don't want in the greeter. The new implementation is designed in an async way, so that it cannot block. The new implementation is also LGPL licensed to make it easy to use it as a base for a new implementation in frameworks. The new implementation also comes with a small test application to demonstrate the usage. Last but not least the power management query is moved from the greeter into the daemon. This brings multiple advantages. It brings us closer to no DBus in the greeter and means we don't need to query DBus every time the screen gets locked. Instead the value is cached in the deamon and pushed to the greeter on start (through the custom Wayland prtocol). Reviewers: bshah, broulik Differential Revision: https://phabricator.kde.org/D827 --- CMakeLists.txt | 1 + greeter/CMakeLists.txt | 3 +- greeter/greeterapp.cpp | 56 ++++++++-- greeter/greeterapp.h | 5 + powermanagement.cpp | 193 ++++++++++++++++++++++++++++++++++ powermanagement.h | 54 ++++++++++ protocols/ksld.xml | 10 +- tests/CMakeLists.txt | 5 + tests/powermanagement.qml | 39 +++++++ tests/powermanagementtest.cpp | 36 +++++++ waylandserver.cpp | 55 +++++++++- waylandserver.h | 4 + 12 files changed, 444 insertions(+), 17 deletions(-) create mode 100644 powermanagement.cpp create mode 100644 powermanagement.h create mode 100644 tests/powermanagement.qml create mode 100644 tests/powermanagementtest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ea39816..0bf9d3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ set(ksld_SRCS waylandlocker.cpp logind.cpp waylandserver.cpp + powermanagement.cpp ) qt5_add_dbus_adaptor(ksld_SRCS ${screensaver_dbusXML} interface.h ScreenLocker::Interface) qt5_add_dbus_adaptor(ksld_SRCS ${kscreensaver_dbusXML} interface.h ScreenLocker::Interface kscreensaveradaptor KScreenSaverAdaptor) diff --git a/greeter/CMakeLists.txt b/greeter/CMakeLists.txt index c12ec68..8d6a504 100644 --- a/greeter/CMakeLists.txt +++ b/greeter/CMakeLists.txt @@ -25,18 +25,17 @@ ecm_add_wayland_client_protocol(kscreenlocker_greet_SRCS add_executable(kscreenlocker_greet ${kscreenlocker_greet_SRCS}) target_link_libraries(kscreenlocker_greet - KF5::Solid KF5::Package KF5::Crash KF5::I18n KF5::ConfigGui KF5::Declarative KF5::QuickAddons + KF5::WindowSystem Qt5::Quick Qt5::Qml Qt5::X11Extras ${X11_LIBRARIES} - KF5::KDELibs4Support KF5::WaylandClient Wayland::Client ) diff --git a/greeter/greeterapp.cpp b/greeter/greeterapp.cpp index 970ae75..6cb49d6 100644 --- a/greeter/greeterapp.cpp +++ b/greeter/greeterapp.cpp @@ -29,7 +29,6 @@ along with this program. If not, see . #include #include #include -#include //Plasma #include #include @@ -183,7 +182,6 @@ void UnlockApp::desktopResized() // extend views and savers to current demand const bool canLogout = KAuthorized::authorizeKAction(QStringLiteral("logout")) && KAuthorized::authorize(QStringLiteral("logout")); - const QSet spdMethods = Solid::PowerManagement::supportedSleepStates(); for (int i = m_views.count(); i < nScreens; ++i) { connect(QGuiApplication::screens()[i], &QObject::destroyed, this, &UnlockApp::desktopResized); // create the view @@ -250,16 +248,14 @@ void UnlockApp::desktopResized() lockProperty.write(m_immediateLock || (!m_noLock && !m_delayedLockTimer)); QQmlProperty sleepProperty(view->rootObject(), QStringLiteral("suspendToRamSupported")); - sleepProperty.write(spdMethods.contains(Solid::PowerManagement::SuspendState)); - if (spdMethods.contains(Solid::PowerManagement::SuspendState) && - view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToRam()").constData()) != -1) { + sleepProperty.write(m_canSuspend); + if (view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToRam()").constData()) != -1) { connect(view->rootObject(), SIGNAL(suspendToRam()), SLOT(suspendToRam())); } QQmlProperty hibernateProperty(view->rootObject(), QStringLiteral("suspendToDiskSupported")); - hibernateProperty.write(spdMethods.contains(Solid::PowerManagement::HibernateState)); - if (spdMethods.contains(Solid::PowerManagement::HibernateState) && - view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToDisk()").constData()) != -1) { + hibernateProperty.write(m_canHibernate); + if (view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToDisk()").constData()) != -1) { connect(view->rootObject(), SIGNAL(suspendToDisk()), SLOT(suspendToDisk())); } @@ -386,7 +382,7 @@ void UnlockApp::suspendToRam() m_ignoreRequests = true; m_resetRequestIgnoreTimer->start(); - Solid::PowerManagement::requestSleep(Solid::PowerManagement::SuspendState, 0, 0); + org_kde_ksld_suspendSystem(m_ksldInterface); } @@ -399,7 +395,7 @@ void UnlockApp::suspendToDisk() m_ignoreRequests = true; m_resetRequestIgnoreTimer->start(); - Solid::PowerManagement::requestSleep(Solid::PowerManagement::HibernateState, 0, 0); + org_kde_ksld_hibernateSystem(m_ksldInterface); } void UnlockApp::setTesting(bool enable) @@ -523,9 +519,23 @@ static void osdText(void *data, org_kde_ksld *org_kde_ksld, const char *icon, co reinterpret_cast(data)->osdText(QString::fromUtf8(icon), QString::fromUtf8(text)); } +static void canSuspend(void *data, org_kde_ksld *org_kde_ksld, uint suspend) +{ + Q_UNUSED(org_kde_ksld) + reinterpret_cast(data)->updateCanSuspend(suspend); +} + +static void canHibernate(void *data, org_kde_ksld *org_kde_ksld, uint hibernate) +{ + Q_UNUSED(org_kde_ksld) + reinterpret_cast(data)->updateCanHibernate(hibernate); +} + static const struct org_kde_ksld_listener s_listener { osdProgress, - osdText + osdText, + canSuspend, + canHibernate }; void UnlockApp::setKsldSocket(int socket) @@ -598,5 +608,29 @@ void UnlockApp::osdText(const QString &icon, const QString &additionalText) } } +void UnlockApp::updateCanSuspend(bool set) +{ + if (m_canSuspend == set) { + return; + } + m_canSuspend = set; + for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) { + QQmlProperty sleepProperty((*it)->rootObject(), QStringLiteral("suspendToRamSupported")); + sleepProperty.write(m_canSuspend); + } +} + +void UnlockApp::updateCanHibernate(bool set) +{ + if (m_canHibernate == set) { + return; + } + m_canHibernate = set; + for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) { + QQmlProperty hibernateProperty((*it)->rootObject(), QStringLiteral("suspendToDiskSupported")); + hibernateProperty.write(m_canHibernate); + } +} + } // namespace diff --git a/greeter/greeterapp.h b/greeter/greeterapp.h index 8a9ebe0..380bea8 100644 --- a/greeter/greeterapp.h +++ b/greeter/greeterapp.h @@ -61,6 +61,8 @@ public: void osdProgress(const QString &icon, int percent, const QString &additionalText); void osdText(const QString &icon, const QString &additionalText); + void updateCanSuspend(bool set); + void updateCanHibernate(bool set); public Q_SLOTS: void desktopResized(); @@ -94,6 +96,9 @@ private: int m_graceTime; bool m_noLock; + bool m_canSuspend = false; + bool m_canHibernate = false; + KWayland::Client::ConnectionThread *m_ksldConnection = nullptr; KWayland::Client::Registry *m_ksldRegistry = nullptr; QThread *m_ksldConnectionThread = nullptr; diff --git a/powermanagement.cpp b/powermanagement.cpp new file mode 100644 index 0000000..b0ef92f --- /dev/null +++ b/powermanagement.cpp @@ -0,0 +1,193 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#include "powermanagement.h" + +#include +#include +#include + +static const QString s_fdoPowerService = QStringLiteral("org.freedesktop.PowerManagement"); +static const QString s_fdoPowerPath = QStringLiteral("/org/freedesktop/PowerManagement"); + +class PowerManagementInstance : public PowerManagement +{ + Q_OBJECT +public: + explicit PowerManagementInstance() : PowerManagement() {} +}; +Q_GLOBAL_STATIC(PowerManagementInstance, s_instance) + +class PowerManagement::Private +{ +public: + Private(PowerManagement *q); + void update(); + void setCanSuspend(bool set); + void setCanHibernate(bool set); + + bool serviceRegistered; + bool canSuspend; + bool canHibernate; + QScopedPointer fdoPowerServiceWatcher; + +private: + void updateProperty(const QString &dbusName, void (Private::*setter)(bool)); + PowerManagement *q; +}; + +PowerManagement::Private::Private(PowerManagement *q) + : serviceRegistered(false) + , canSuspend(false) + , canHibernate(false) + , fdoPowerServiceWatcher(new QDBusServiceWatcher(s_fdoPowerService, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration)) + , q(q) +{ +} + +void PowerManagement::Private::update() +{ + serviceRegistered = true; + updateProperty(QStringLiteral("CanSuspend"), &Private::setCanSuspend); + updateProperty(QStringLiteral("CanHibernate"), &Private::setCanHibernate); +} + +void PowerManagement::Private::updateProperty(const QString &dbusName, void (Private::*setter)(bool)) +{ + QDBusMessage message = QDBusMessage::createMethodCall(s_fdoPowerService, + s_fdoPowerPath, + s_fdoPowerService, + dbusName); + QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(message); + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(reply, q); + QObject::connect(callWatcher, &QDBusPendingCallWatcher::finished, q, + [this, setter](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + self->deleteLater(); + if (!reply.isValid()) { + return; + } + ((this)->*setter)(reply.value()); + } + ); + +} + +void PowerManagement::Private::setCanHibernate(bool set) +{ + if (canHibernate == set) { + return; + } + canHibernate = set; + emit q->canHibernateChanged(); +} + +void PowerManagement::Private::setCanSuspend(bool set) +{ + if (canSuspend == set) { + return; + } + canSuspend = set; + emit q->canSuspendChanged(); +} + +PowerManagement *PowerManagement::instance() +{ + return s_instance; +} + +PowerManagement::PowerManagement() + : QObject() + , d(new Private(this)) +{ + connect(d->fdoPowerServiceWatcher.data(), &QDBusServiceWatcher::serviceRegistered, this, [this] { d->update(); }); + connect(d->fdoPowerServiceWatcher.data(), &QDBusServiceWatcher::serviceUnregistered, this, + [this] { + d->serviceRegistered = false; + d->setCanSuspend(false); + d->setCanHibernate(false); + } + ); + + // check whether the service is registered + QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), + QStringLiteral("/"), + QStringLiteral("org.freedesktop.DBus"), + QStringLiteral("ListNames")); + QDBusPendingReply async = QDBusConnection::sessionBus().asyncCall(message); + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); + connect(callWatcher, &QDBusPendingCallWatcher::finished, this, + [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + self->deleteLater(); + if (!reply.isValid()) { + return; + } + if (reply.value().contains(s_fdoPowerService)) { + d->update(); + } + } + ); +} + +PowerManagement::~PowerManagement() +{ +} + +void PowerManagement::suspend() +{ + if (!d->serviceRegistered) { + return; + } + if (!d->canSuspend) { + return; + } + QDBusMessage message = QDBusMessage::createMethodCall(s_fdoPowerService, + s_fdoPowerPath, + s_fdoPowerService, + QStringLiteral("Suspend")); + QDBusConnection::sessionBus().asyncCall(message); +} + +void PowerManagement::hibernate() +{ + if (!d->serviceRegistered) { + return; + } + if (!d->canHibernate) { + return; + } + QDBusMessage message = QDBusMessage::createMethodCall(s_fdoPowerService, + s_fdoPowerPath, + s_fdoPowerService, + QStringLiteral("Hibernate")); + QDBusConnection::sessionBus().asyncCall(message); +} + +bool PowerManagement::canSuspend() const +{ + return d->canSuspend; +} + +bool PowerManagement::canHibernate() const +{ + return d->canHibernate; +} + +#include "powermanagement.moc" diff --git a/powermanagement.h b/powermanagement.h new file mode 100644 index 0000000..6fc030c --- /dev/null +++ b/powermanagement.h @@ -0,0 +1,54 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#ifndef POWERMANAGEMENT_H +#define POWERMANAGEMENT_H + +#include + +class PowerManagement : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool canSuspend READ canSuspend NOTIFY canSuspendChanged) + Q_PROPERTY(bool canHibernate READ canHibernate NOTIFY canHibernateChanged) +public: + virtual ~PowerManagement(); + + bool canSuspend() const; + bool canHibernate() const; + + static PowerManagement *instance(); + +public Q_SLOTS: + void suspend(); + void hibernate(); + +Q_SIGNALS: + void canSuspendChanged(); + void canHibernateChanged(); + +protected: + explicit PowerManagement(); + +private: + class Private; + QScopedPointer d; +}; + +#endif diff --git a/protocols/ksld.xml b/protocols/ksld.xml index bd4e783..289f28e 100644 --- a/protocols/ksld.xml +++ b/protocols/ksld.xml @@ -1,9 +1,11 @@ - + + + @@ -13,6 +15,12 @@ + + + + + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fb64b2c..3e32156 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,8 @@ add_executable(kscreenlocker_test kscreenlocker_main.cpp) target_link_libraries(kscreenlocker_test KScreenLocker Qt5::Widgets KF5::I18n) ecm_mark_as_test(kscreenlocker_test) + +add_definitions(-DQML_PATH="${CMAKE_CURRENT_SOURCE_DIR}/powermanagement.qml") +add_executable(powermanagment_test powermanagementtest.cpp ../powermanagement.cpp) +target_link_libraries(powermanagment_test Qt5::Gui Qt5::DBus Qt5::Quick) +ecm_mark_as_test(powermanagment_test) diff --git a/tests/powermanagement.qml b/tests/powermanagement.qml new file mode 100644 index 0000000..0659d2b --- /dev/null +++ b/tests/powermanagement.qml @@ -0,0 +1,39 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +import QtQuick 2.0 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 1.4 + +Rectangle { + color: "white" + + RowLayout { + Button { + enabled: powerManagement.canSuspend + text: "Suspend" + onClicked: powerManagement.suspend() + } + Button { + enabled: powerManagement.canHibernate + text: "Hibernate" + onClicked: powerManagement.hibernate() + } + } +} diff --git a/tests/powermanagementtest.cpp b/tests/powermanagementtest.cpp new file mode 100644 index 0000000..5f8cc60 --- /dev/null +++ b/tests/powermanagementtest.cpp @@ -0,0 +1,36 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#include "../powermanagement.h" +#include +#include +#include + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + QQuickView view; + view.rootContext()->setContextProperty("powerManagement", PowerManagement::instance()); + view.setSource(QUrl::fromLocalFile(QStringLiteral(QML_PATH))); + + view.show(); + + return app.exec(); +} diff --git a/waylandserver.cpp b/waylandserver.cpp index 98b8bd7..f827ee6 100644 --- a/waylandserver.cpp +++ b/waylandserver.cpp @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "waylandserver.h" +#include "powermanagement.h" // ksld #include // Wayland @@ -54,6 +55,8 @@ WaylandServer::WaylandServer(QObject *parent) s_osdServiceInterface, QStringLiteral("osdText"), this, SLOT(osdText(QString,QString))); + connect(PowerManagement::instance(), &PowerManagement::canSuspendChanged, this, &WaylandServer::sendCanSuspend); + connect(PowerManagement::instance(), &PowerManagement::canHibernateChanged, this, &WaylandServer::sendCanHibernate); } WaylandServer::~WaylandServer() @@ -85,7 +88,7 @@ int WaylandServer::start() return -1; } connect(m_allowedClient, &KWayland::Server::ClientConnection::disconnected, this, [this] { m_allowedClient = nullptr; }); - m_interface = wl_global_create(*m_display.data(), &org_kde_ksld_interface, 2, this, bind); + m_interface = wl_global_create(*m_display.data(), &org_kde_ksld_interface, 3, this, bind); return socketPair[1]; } @@ -110,17 +113,21 @@ void WaylandServer::bind(wl_client *client, void *data, uint32_t version, uint32 wl_client_post_no_memory(client); return; } - wl_resource *r = s->m_allowedClient->createResource(&org_kde_ksld_interface, qMin(version, 2u), id); + wl_resource *r = s->m_allowedClient->createResource(&org_kde_ksld_interface, qMin(version, 3u), id); if (!r) { wl_client_post_no_memory(client); return; } static const struct org_kde_ksld_interface s_interface = { - x11WindowCallback + x11WindowCallback, + suspendSystemCallback, + hibernateSystemCallback }; wl_resource_set_implementation(r, &s_interface, s, unbind); s->addResource(r); + s->sendCanSuspend(); + s->sendCanHibernate(); s->m_allowedClient->flush(); } @@ -138,6 +145,20 @@ void WaylandServer::x11WindowCallback(wl_client *client, wl_resource *resource, emit s->x11WindowAdded(id); } +void WaylandServer::suspendSystemCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + Q_UNUSED(resource) + PowerManagement::instance()->suspend(); +} + +void WaylandServer::hibernateSystemCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + Q_UNUSED(resource) + PowerManagement::instance()->hibernate(); +} + void WaylandServer::addResource(wl_resource *r) { m_resources.append(r); @@ -176,4 +197,32 @@ void WaylandServer::osdText(const QString &icon, const QString &additionalText) } } +void WaylandServer::sendCanSuspend() +{ + if (!m_allowedClient) { + return; + } + Q_FOREACH (auto r, m_resources) { + if (wl_resource_get_version(r) < ORG_KDE_KSLD_CANSUSPENDSYSTEM_SINCE_VERSION) { + continue; + } + org_kde_ksld_send_canSuspendSystem(r, PowerManagement::instance()->canSuspend() ? 1 : 0); + } + m_allowedClient->flush(); +} + +void WaylandServer::sendCanHibernate() +{ + if (!m_allowedClient) { + return; + } + Q_FOREACH (auto r, m_resources) { + if (wl_resource_get_version(r) < ORG_KDE_KSLD_CANHIBERNATESYSTEM_SINCE_VERSION) { + continue; + } + org_kde_ksld_send_canHibernateSystem(r, PowerManagement::instance()->canHibernate() ? 1 : 0); + } + m_allowedClient->flush(); +} + } diff --git a/waylandserver.h b/waylandserver.h index 65d97e3..24bc3e4 100644 --- a/waylandserver.h +++ b/waylandserver.h @@ -58,8 +58,12 @@ private: static void bind(wl_client *client, void *data, uint32_t version, uint32_t id); static void unbind(wl_resource *resource); static void x11WindowCallback(wl_client *client, wl_resource *resource, uint32_t id); + static void suspendSystemCallback(wl_client *client, wl_resource *resource); + static void hibernateSystemCallback(wl_client *client, wl_resource *resource); void addResource(wl_resource *r); void removeResource(wl_resource *r); + void sendCanSuspend(); + void sendCanHibernate(); QScopedPointer m_display; KWayland::Server::ClientConnection *m_allowedClient = nullptr; wl_global *m_interface = nullptr; -- GitLab