diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ 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 --- a/greeter/CMakeLists.txt +++ b/greeter/CMakeLists.txt @@ -25,18 +25,17 @@ 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.h b/greeter/greeterapp.h --- a/greeter/greeterapp.h +++ b/greeter/greeterapp.h @@ -61,6 +61,8 @@ 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 @@ 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/greeter/greeterapp.cpp b/greeter/greeterapp.cpp --- a/greeter/greeterapp.cpp +++ b/greeter/greeterapp.cpp @@ -29,7 +29,6 @@ #include #include #include -#include //Plasma #include #include @@ -183,7 +182,6 @@ // 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 @@ 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 @@ m_ignoreRequests = true; m_resetRequestIgnoreTimer->start(); - Solid::PowerManagement::requestSleep(Solid::PowerManagement::SuspendState, 0, 0); + org_kde_ksld_suspendSystem(m_ksldInterface); } @@ -399,7 +395,7 @@ 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 @@ 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::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/powermanagement.h b/powermanagement.h new file mode 100644 --- /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/powermanagement.cpp b/powermanagement.cpp new file mode 100644 --- /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/protocols/ksld.xml b/protocols/ksld.xml --- 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 --- 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 --- /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 --- /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.h b/waylandserver.h --- a/waylandserver.h +++ b/waylandserver.h @@ -58,8 +58,12 @@ 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; diff --git a/waylandserver.cpp b/waylandserver.cpp --- a/waylandserver.cpp +++ b/waylandserver.cpp @@ -18,6 +18,7 @@ along with this program. If not, see . *********************************************************************/ #include "waylandserver.h" +#include "powermanagement.h" // ksld #include // Wayland @@ -54,6 +55,8 @@ 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 @@ 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 @@ 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 @@ 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::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(); +} + }