File indexing completed on 2024-04-28 16:55:15

0001 /***************************************************************************
0002  *   Copyright (C) 2010 by Dario Freddi <drf@kde.org>                      *
0003  *   Copyright (C) 2012 Lukáš Tinkl <ltinkl@redhat.com>                    *
0004  *   Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>            *
0005  *                                                                         *
0006  *   This program is free software; you can redistribute it and/or modify  *
0007  *   it under the terms of the GNU General Public License as published by  *
0008  *   the Free Software Foundation; either version 2 of the License, or     *
0009  *   (at your option) any later version.                                   *
0010  *                                                                         *
0011  *   This program is distributed in the hope that it will be useful,       *
0012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0014  *   GNU General Public License for more details.                          *
0015  *                                                                         *
0016  *   You should have received a copy of the GNU General Public License     *
0017  *   along with this program; if not, write to the                         *
0018  *   Free Software Foundation, Inc.,                                       *
0019  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
0020  ***************************************************************************/
0021 
0022 #include <QCoreApplication>
0023 #include <QDBusObjectPath>
0024 #include <QDBusArgument>
0025 #include <QMetaType>
0026 #include <QDBusMetaType>
0027 #include <QDBusConnection>
0028 #include <QDBusInterface>
0029 #include <QDBusPendingReply>
0030 #include <QDBusConnectionInterface>
0031 #include <QDBusServiceWatcher>
0032 #include <QTimer>
0033 
0034 #include <KIdleTime>
0035 
0036 #include <algorithm>
0037 #include <unistd.h>
0038 
0039 #include "powerdevilpolicyagent.h"
0040 #include "powerdevil_debug.h"
0041 
0042 #include "screenlocker_interface.h"
0043 
0044 struct NamedDBusObjectPath
0045 {
0046     QString name;
0047     QDBusObjectPath path;
0048 };
0049 
0050 // Marshall the NamedDBusObjectPath data into a D-Bus argument
0051 QDBusArgument &operator<<(QDBusArgument &argument, const NamedDBusObjectPath &namedPath)
0052 {
0053     argument.beginStructure();
0054     argument << namedPath.name << namedPath.path;
0055     argument.endStructure();
0056     return argument;
0057 }
0058 
0059 // Retrieve the NamedDBusObjectPath data from the D-Bus argument
0060 const QDBusArgument &operator>>(const QDBusArgument &argument, NamedDBusObjectPath &namedPath)
0061 {
0062     argument.beginStructure();
0063     argument >> namedPath.name >> namedPath.path;
0064     argument.endStructure();
0065     return argument;
0066 }
0067 
0068 QDBusArgument &operator<<(QDBusArgument &argument, const LogindInhibition &inhibition)
0069 {
0070     argument.beginStructure();
0071     argument << inhibition.what << inhibition.who << inhibition.why << inhibition.mode << inhibition.uid << inhibition.pid;
0072     argument.endStructure();
0073     return argument;
0074 }
0075 
0076 const QDBusArgument &operator>>(const QDBusArgument &argument, LogindInhibition &inhibition)
0077 {
0078     argument.beginStructure();
0079     argument >> inhibition.what >> inhibition.who >> inhibition.why >> inhibition.mode >> inhibition.uid >> inhibition.pid;
0080     argument.endStructure();
0081     return argument;
0082 }
0083 
0084 Q_DECLARE_METATYPE(NamedDBusObjectPath)
0085 Q_DECLARE_METATYPE(LogindInhibition)
0086 Q_DECLARE_METATYPE(QList<LogindInhibition>)
0087 Q_DECLARE_METATYPE(InhibitionInfo)
0088 Q_DECLARE_METATYPE(QList<InhibitionInfo>)
0089 
0090 namespace PowerDevil
0091 {
0092 
0093 static const QString SCREEN_LOCKER_SERVICE_NAME = QStringLiteral("org.freedesktop.ScreenSaver");
0094 
0095 class PolicyAgentHelper
0096 {
0097 public:
0098     PolicyAgentHelper() : q(nullptr) { }
0099     ~PolicyAgentHelper() {
0100         delete q;
0101     }
0102     PolicyAgent *q;
0103 };
0104 
0105 Q_GLOBAL_STATIC(PolicyAgentHelper, s_globalPolicyAgent)
0106 
0107 PolicyAgent *PolicyAgent::instance()
0108 {
0109     if (!s_globalPolicyAgent->q) {
0110         new PolicyAgent;
0111     }
0112 
0113     return s_globalPolicyAgent->q;
0114 }
0115 
0116 PolicyAgent::PolicyAgent(QObject* parent)
0117     : QObject(parent)
0118     , m_screenLockerWatcher(new QDBusServiceWatcher(this))
0119     , m_sdAvailable(false)
0120     , m_systemdInhibitFd(-1)
0121     , m_ckAvailable(false)
0122     , m_sessionIsBeingInterrupted(false)
0123     , m_lastCookie(0)
0124     , m_busWatcher(new QDBusServiceWatcher(this))
0125     , m_sdWatcher(new QDBusServiceWatcher(this))
0126     , m_ckWatcher(new QDBusServiceWatcher(this))
0127     , m_wasLastActiveSession(false)
0128 {
0129     Q_ASSERT(!s_globalPolicyAgent->q);
0130     s_globalPolicyAgent->q = this;
0131 }
0132 
0133 PolicyAgent::~PolicyAgent()
0134 {
0135 }
0136 
0137 void PolicyAgent::init()
0138 {
0139     qDBusRegisterMetaType<InhibitionInfo>();
0140     qDBusRegisterMetaType<QList<InhibitionInfo>>();
0141 
0142     // Watch over the systemd service
0143     m_sdWatcher.data()->setConnection(QDBusConnection::systemBus());
0144     m_sdWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration |
0145                                      QDBusServiceWatcher::WatchForRegistration);
0146     m_sdWatcher.data()->addWatchedService(SYSTEMD_LOGIN1_SERVICE);
0147 
0148     connect(m_sdWatcher.data(), &QDBusServiceWatcher::serviceRegistered,
0149             this, &PolicyAgent::onSessionHandlerRegistered);
0150     connect(m_sdWatcher.data(), &QDBusServiceWatcher::serviceUnregistered,
0151             this, &PolicyAgent::onSessionHandlerUnregistered);
0152     // If it's up and running already, let's cache it
0153     if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_LOGIN1_SERVICE)) {
0154         onSessionHandlerRegistered(SYSTEMD_LOGIN1_SERVICE);
0155     }
0156 
0157     // Watch over the ConsoleKit service
0158     m_ckWatcher.data()->setConnection(QDBusConnection::sessionBus());
0159     m_ckWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration |
0160                                      QDBusServiceWatcher::WatchForRegistration);
0161     m_ckWatcher.data()->addWatchedService(CONSOLEKIT_SERVICE);
0162 
0163     connect(m_ckWatcher.data(), &QDBusServiceWatcher::serviceRegistered,
0164             this, &PolicyAgent::onSessionHandlerRegistered);
0165     connect(m_ckWatcher.data(), &QDBusServiceWatcher::serviceUnregistered,
0166             this, &PolicyAgent::onSessionHandlerUnregistered);
0167     // If it's up and running already, let's cache it
0168     if (QDBusConnection::systemBus().interface()->isServiceRegistered(CONSOLEKIT_SERVICE)) {
0169         onSessionHandlerRegistered(CONSOLEKIT_SERVICE);
0170     }
0171 
0172     // Now set up our service watcher
0173     m_busWatcher.data()->setConnection(QDBusConnection::sessionBus());
0174     m_busWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
0175 
0176     connect(m_busWatcher.data(), &QDBusServiceWatcher::serviceUnregistered,
0177             this, &PolicyAgent::onServiceUnregistered);
0178 
0179     // Setup the screen locker watcher and check whether the screen is currently locked
0180     connect(m_screenLockerWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &PolicyAgent::onScreenLockerOwnerChanged);
0181     m_screenLockerWatcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
0182     m_screenLockerWatcher->addWatchedService(SCREEN_LOCKER_SERVICE_NAME);
0183 
0184     // async variant of QDBusConnectionInterface::serviceOwner ...
0185     auto msg = QDBusMessage::createMethodCall(
0186         QStringLiteral("org.freedesktop.DBus"),
0187         QStringLiteral("/"),
0188         QStringLiteral("org.freedesktop.DBus"),
0189         QStringLiteral("GetNameOwner")
0190     );
0191     msg.setArguments({SCREEN_LOCKER_SERVICE_NAME});
0192 
0193     auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), this);
0194     QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0195         QDBusPendingReply<QString> reply = *watcher;
0196         if (!reply.isError()) {
0197             onScreenLockerOwnerChanged(SCREEN_LOCKER_SERVICE_NAME, {}, reply.value());
0198         }
0199         watcher->deleteLater();
0200     });
0201 }
0202 
0203 QString PolicyAgent::getNamedPathProperty(const QString &path, const QString &iface, const QString &prop) const
0204 {
0205     QDBusMessage message = QDBusMessage::createMethodCall(SYSTEMD_LOGIN1_SERVICE, path,
0206                                                           QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get"));
0207     message << iface << prop;
0208     QDBusMessage reply = QDBusConnection::systemBus().call(message);
0209 
0210     QVariantList args = reply.arguments();
0211     if (!args.isEmpty()) {
0212         NamedDBusObjectPath namedPath;
0213         args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> namedPath;
0214         return namedPath.path.path();
0215     }
0216 
0217     return QString();
0218 }
0219 
0220 void PolicyAgent::onSessionHandlerRegistered(const QString & serviceName)
0221 {
0222     if (serviceName == SYSTEMD_LOGIN1_SERVICE) {
0223         m_sdAvailable = true;
0224 
0225         qRegisterMetaType<NamedDBusObjectPath>();
0226         qDBusRegisterMetaType<NamedDBusObjectPath>();
0227         qDBusRegisterMetaType<LogindInhibition>();
0228         qDBusRegisterMetaType<QList<LogindInhibition>>();
0229 
0230         // get the current session
0231         m_managerIface.reset(new QDBusInterface(SYSTEMD_LOGIN1_SERVICE, SYSTEMD_LOGIN1_PATH, SYSTEMD_LOGIN1_MANAGER_IFACE, QDBusConnection::systemBus()));
0232 
0233         if (!m_managerIface.data()->isValid()) {
0234             qCDebug(POWERDEVIL) << "Can't connect to systemd";
0235             m_sdAvailable = false;
0236             return;
0237         }
0238 
0239         QDBusPendingReply<QDBusObjectPath> session = m_managerIface.data()->asyncCall(QLatin1String("GetSession"), QLatin1String("auto"));
0240         session.waitForFinished();
0241 
0242         if (!session.isValid()) {
0243             qCDebug(POWERDEVIL) << "The session is not registered with systemd";
0244             m_sdAvailable = false;
0245             return;
0246         }
0247 
0248         QString sessionPath = session.value().path();
0249         qCDebug(POWERDEVIL) << "Session path:" << sessionPath;
0250 
0251         m_sdSessionInterface = new QDBusInterface(SYSTEMD_LOGIN1_SERVICE, sessionPath,
0252                                                   SYSTEMD_LOGIN1_SESSION_IFACE, QDBusConnection::systemBus(), this);
0253         if (!m_sdSessionInterface.data()->isValid()) {
0254             // As above
0255             qCDebug(POWERDEVIL) << "Can't contact session iface";
0256             m_sdAvailable = false;
0257             delete m_sdSessionInterface.data();
0258             return;
0259         }
0260 
0261         // now let's obtain the seat
0262         QString seatPath = getNamedPathProperty(sessionPath, SYSTEMD_LOGIN1_SESSION_IFACE, "Seat");
0263 
0264         if (seatPath.isEmpty() || seatPath == "/") {
0265             qCDebug(POWERDEVIL) << "Unable to associate systemd session with a seat" << seatPath;
0266             m_sdAvailable = false;
0267             return;
0268         }
0269 
0270         // get the current seat
0271         m_sdSeatInterface = new QDBusInterface(SYSTEMD_LOGIN1_SERVICE, seatPath,
0272                                                SYSTEMD_LOGIN1_SEAT_IFACE, QDBusConnection::systemBus(), this);
0273 
0274         if (!m_sdSeatInterface.data()->isValid()) {
0275             // As above
0276             qCDebug(POWERDEVIL) << "Can't contact seat iface";
0277             m_sdAvailable = false;
0278             delete m_sdSeatInterface.data();
0279             return;
0280         }
0281 
0282         // finally get the active session path and watch for its changes
0283         m_activeSessionPath = getNamedPathProperty(seatPath, SYSTEMD_LOGIN1_SEAT_IFACE, "ActiveSession");
0284 
0285         qCDebug(POWERDEVIL) << "ACTIVE SESSION PATH:" << m_activeSessionPath;
0286         QDBusConnection::systemBus().connect(SYSTEMD_LOGIN1_SERVICE, seatPath, "org.freedesktop.DBus.Properties", "PropertiesChanged", this,
0287                                              SLOT(onActiveSessionChanged(QString,QVariantMap,QStringList)));
0288 
0289         onActiveSessionChanged(m_activeSessionPath);
0290 
0291         // block logind from handling time-based inhibitions
0292         setupSystemdInhibition();
0293         // and then track logind's ihibitions, too
0294         QDBusConnection::systemBus().connect(SYSTEMD_LOGIN1_SERVICE, SYSTEMD_LOGIN1_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", this,
0295                                              SLOT(onManagerPropertyChanged(QString,QVariantMap,QStringList)));
0296         checkLogindInhibitions();
0297 
0298         qCDebug(POWERDEVIL) << "systemd support initialized";
0299     } else if (serviceName == CONSOLEKIT_SERVICE) {
0300         m_ckAvailable = true;
0301 
0302         // Otherwise, let's ask ConsoleKit
0303         m_managerIface.reset(new QDBusInterface(CONSOLEKIT_SERVICE, CONSOLEKIT_MANAGER_PATH, CONSOLEKIT_MANAGER_IFACE, QDBusConnection::systemBus()));
0304 
0305         if (!m_managerIface.data()->isValid()) {
0306             qCDebug(POWERDEVIL) << "Can't connect to ConsoleKit";
0307             m_ckAvailable = false;
0308             return;
0309         }
0310 
0311         QDBusPendingReply<QDBusObjectPath> sessionPath = m_managerIface.data()->asyncCall("GetCurrentSession");
0312 
0313         sessionPath.waitForFinished();
0314 
0315         if (!sessionPath.isValid() || sessionPath.value().path().isEmpty()) {
0316             qCDebug(POWERDEVIL) << "The session is not registered with ck";
0317             m_ckAvailable = false;
0318             return;
0319         }
0320 
0321         m_ckSessionInterface = new QDBusInterface(CONSOLEKIT_SERVICE, sessionPath.value().path(),
0322                                                   "org.freedesktop.ConsoleKit.Session", QDBusConnection::systemBus());
0323 
0324         if (!m_ckSessionInterface.data()->isValid()) {
0325             // As above
0326             qCDebug(POWERDEVIL) << "Can't contact iface";
0327             m_ckAvailable = false;
0328             return;
0329         }
0330 
0331         // Now let's obtain the seat
0332         QDBusPendingReply< QDBusObjectPath > seatPath = m_ckSessionInterface.data()->asyncCall(QStringLiteral("GetSeatId"));
0333         seatPath.waitForFinished();
0334 
0335         if (!seatPath.isValid() || seatPath.value().path().isEmpty()) {
0336             qCDebug(POWERDEVIL) << "Unable to associate ck session with a seat";
0337             m_ckAvailable = false;
0338             return;
0339         }
0340 
0341         if (!QDBusConnection::systemBus().connect(CONSOLEKIT_SERVICE, seatPath.value().path(),
0342                                                   "org.freedesktop.ConsoleKit.Seat", "ActiveSessionChanged",
0343                                                   this, SLOT(onActiveSessionChanged(QString)))) {
0344             qCDebug(POWERDEVIL) << "Unable to connect to ActiveSessionChanged";
0345             m_ckAvailable = false;
0346             return;
0347         }
0348 
0349         // Force triggering of active session changed
0350         QDBusMessage call = QDBusMessage::createMethodCall(CONSOLEKIT_SERVICE, seatPath.value().path(),
0351                                                            "org.freedesktop.ConsoleKit.Seat", "GetActiveSession");
0352         QDBusPendingReply< QDBusObjectPath > activeSession = QDBusConnection::systemBus().asyncCall(call);
0353         activeSession.waitForFinished();
0354 
0355         onActiveSessionChanged(activeSession.value().path());
0356 
0357         setupSystemdInhibition();
0358 
0359         qCDebug(POWERDEVIL) << "ConsoleKit support initialized";
0360     }
0361     else
0362         qCWarning(POWERDEVIL) << "Unhandled service registered:" << serviceName;
0363 }
0364 
0365 void PolicyAgent::onSessionHandlerUnregistered(const QString & serviceName)
0366 {
0367     if (serviceName == QLatin1String(SYSTEMD_LOGIN1_SERVICE)) {
0368         m_sdAvailable = false;
0369         delete m_sdSessionInterface.data();
0370     }
0371     else if (serviceName == QLatin1String(CONSOLEKIT_SERVICE)) {
0372         m_ckAvailable = false;
0373         delete m_ckSessionInterface.data();
0374     }
0375 }
0376 
0377 void PolicyAgent::onActiveSessionChanged(const QString & ifaceName, const QVariantMap & changedProps, const QStringList & invalidatedProps)
0378 {
0379     const QString key = QLatin1String("ActiveSession");
0380 
0381     if (ifaceName == SYSTEMD_LOGIN1_SEAT_IFACE && (changedProps.contains(key) || invalidatedProps.contains(key))) {
0382         m_activeSessionPath = getNamedPathProperty(m_sdSeatInterface.data()->path(), SYSTEMD_LOGIN1_SEAT_IFACE, key);
0383         qCDebug(POWERDEVIL) << "ACTIVE SESSION PATH CHANGED:" << m_activeSessionPath;
0384         onActiveSessionChanged(m_activeSessionPath);
0385     }
0386 }
0387 
0388 void PolicyAgent::onActiveSessionChanged(const QString& activeSession)
0389 {
0390     if (activeSession.isEmpty() || activeSession == QLatin1String("/")) {
0391         qCDebug(POWERDEVIL) << "Switched to inactive session - leaving unchanged";
0392         return;
0393     } else if ((!m_sdSessionInterface.isNull() && activeSession == m_sdSessionInterface.data()->path()) ||
0394                (!m_ckSessionInterface.isNull() && activeSession == m_ckSessionInterface.data()->path())) {
0395         qCDebug(POWERDEVIL) << "Current session is now active";
0396         if (!m_wasLastActiveSession) {
0397             m_wasLastActiveSession = true;
0398             Q_EMIT sessionActiveChanged(true);
0399         }
0400     } else {
0401         qCDebug(POWERDEVIL) << "Current session is now inactive";
0402         if (m_wasLastActiveSession) {
0403             m_wasLastActiveSession = false;
0404             Q_EMIT sessionActiveChanged(false);
0405         }
0406     }
0407 }
0408 
0409 void PolicyAgent::checkLogindInhibitions()
0410 {
0411     qCDebug(POWERDEVIL) << "Checking logind inhibitions";
0412 
0413     QDBusPendingReply<QList<LogindInhibition>> reply = m_managerIface->asyncCall(QStringLiteral("ListInhibitors"));
0414 
0415     auto *watcher = new QDBusPendingCallWatcher(reply, this);
0416     QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0417         QDBusPendingReply<QList<LogindInhibition>> reply = *watcher;
0418         watcher->deleteLater();
0419 
0420         if (reply.isError()) {
0421             qCWarning(POWERDEVIL) << "Failed to ask logind for inhibitions" << reply.error().message();
0422             return;
0423         }
0424 
0425         const auto activeInhibitions = reply.value();
0426 
0427         // Add all inhibitions that we don't know already
0428         for (const auto &activeInhibition : activeInhibitions) {
0429             if (activeInhibition.mode != QLatin1String("block")) {
0430                 continue;
0431             }
0432 
0433             if (static_cast<pid_t>(activeInhibition.pid) == getpid()) {
0434                 continue;
0435             }
0436 
0437             const auto types = activeInhibition.what.split(QLatin1Char(':'));
0438 
0439             RequiredPolicies policies{};
0440 
0441             if (types.contains(QLatin1String("sleep"))) {
0442                 policies |= InterruptSession;
0443             }
0444             if (types.contains(QLatin1String("idle"))) {
0445                 policies |= ChangeScreenSettings;
0446             }
0447 
0448             if (!policies) {
0449                 continue;
0450             }
0451 
0452             const bool known = std::find(m_logindInhibitions.constBegin(),
0453                                          m_logindInhibitions.constEnd(),
0454                                          activeInhibition) != m_logindInhibitions.constEnd();
0455             if (known) {
0456                 continue;
0457             }
0458 
0459             qCDebug(POWERDEVIL) << "Adding logind inhibition:" << activeInhibition.what << activeInhibition.who << activeInhibition.why
0460                                 << "from" << activeInhibition.pid << "of user" << activeInhibition.uid;
0461             const uint cookie = AddInhibition(policies, activeInhibition.who, activeInhibition.why);
0462             m_logindInhibitions.insert(cookie, activeInhibition);
0463         }
0464 
0465         // Remove all inhibitions that logind doesn't have anymore
0466         for (auto it = m_logindInhibitions.begin(); it != m_logindInhibitions.end();) {
0467             if (!activeInhibitions.contains(*it)) {
0468                 qCDebug(POWERDEVIL) << "Releasing logind inhibition:" << it->what << it->who << it->why << "from" << it->pid << "of user" << it->uid;
0469                 ReleaseInhibition(it.key());
0470                 it = m_logindInhibitions.erase(it);
0471             } else {
0472                 ++it;
0473             }
0474         }
0475     });
0476 }
0477 
0478 void PolicyAgent::onManagerPropertyChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps)
0479 {
0480     const QString key = QStringLiteral("BlockInhibited");
0481 
0482     if (ifaceName == SYSTEMD_LOGIN1_MANAGER_IFACE && (changedProps.contains(key) || invalidatedProps.contains(key))) {
0483         checkLogindInhibitions();
0484     }
0485 }
0486 
0487 void PolicyAgent::onServiceUnregistered(const QString& serviceName)
0488 {
0489     // Ouch - the application quit or crashed without releasing its inhibitions. Let's fix that.
0490 
0491     // ReleaseInhibition removes the cookies from the hash, so we need to operate on a copy
0492     const auto cookieToBusService = m_cookieToBusService;
0493     for (auto it = cookieToBusService.constBegin(); it != cookieToBusService.constEnd(); ++it) {
0494         if (it.value() == serviceName) {
0495             ReleaseInhibition(it.key());
0496         }
0497     }
0498 }
0499 
0500 PolicyAgent::RequiredPolicies PolicyAgent::unavailablePolicies()
0501 {
0502     RequiredPolicies retpolicies = None;
0503 
0504     if (!m_typesToCookie[ChangeProfile].isEmpty()) {
0505         retpolicies |= ChangeProfile;
0506     }
0507     // when screen locker is active it makes no sense to keep the screen on
0508     if (!m_screenLockerActive && !m_typesToCookie[ChangeScreenSettings].isEmpty()) {
0509         retpolicies |= ChangeScreenSettings;
0510     }
0511     if (!m_typesToCookie[InterruptSession].isEmpty()) {
0512         retpolicies |= InterruptSession;
0513     }
0514 
0515     return retpolicies;
0516 }
0517 
0518 bool PolicyAgent::screenLockerActive() const
0519 {
0520     return m_screenLockerActive;
0521 }
0522 
0523 PolicyAgent::RequiredPolicies PolicyAgent::requirePolicyCheck(PolicyAgent::RequiredPolicies policies)
0524 {
0525     if (!m_sdAvailable) {
0526         // No way to determine if we are on the current session, simply suppose we are
0527         qCDebug(POWERDEVIL) << "Can't contact systemd";
0528     } else if (!m_sdSessionInterface.isNull()) {
0529         bool isActive = m_sdSessionInterface.data()->property("Active").toBool();
0530 
0531         if (!isActive && !m_wasLastActiveSession) {
0532             return policies;
0533         }
0534     }
0535 
0536     if (!m_ckAvailable) {
0537         // No way to determine if we are on the current session, simply suppose we are
0538         qCDebug(POWERDEVIL) << "Can't contact ck";
0539     } else if (!m_ckSessionInterface.isNull()) {
0540         QDBusPendingReply< bool > rp = m_ckSessionInterface.data()->asyncCall(QStringLiteral("IsActive"));
0541         rp.waitForFinished();
0542 
0543         if (!(rp.isValid() && rp.value()) && !m_wasLastActiveSession) {
0544             return policies;
0545         }
0546     }
0547 
0548     // Ok, let's go then
0549     RequiredPolicies retpolicies = None;
0550 
0551     if (policies & ChangeProfile) {
0552         if (!m_typesToCookie[ChangeProfile].isEmpty()) {
0553             retpolicies |= ChangeProfile;
0554         }
0555     }
0556     if (policies & ChangeScreenSettings) {
0557         if (!m_typesToCookie[ChangeScreenSettings].isEmpty()) {
0558             retpolicies |= ChangeScreenSettings;
0559         }
0560     }
0561     if (policies & InterruptSession) {
0562         if (m_sessionIsBeingInterrupted || !m_typesToCookie[InterruptSession].isEmpty()) {
0563             retpolicies |= InterruptSession;
0564         }
0565     }
0566 
0567     return retpolicies;
0568 }
0569 
0570 void PolicyAgent::startSessionInterruption()
0571 {
0572     m_sessionIsBeingInterrupted = true;
0573 }
0574 
0575 void PolicyAgent::finishSessionInterruption()
0576 {
0577     m_sessionIsBeingInterrupted = false;
0578 }
0579 
0580 void PolicyAgent::onScreenLockerOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
0581 {
0582     Q_UNUSED(oldOwner);
0583     if (serviceName != SCREEN_LOCKER_SERVICE_NAME) {
0584         return;
0585     }
0586 
0587     delete m_screenLockerInterface;
0588     m_screenLockerInterface = nullptr;
0589     m_screenLockerActive = false;
0590 
0591     if (!newOwner.isEmpty()) {
0592         m_screenLockerInterface = new OrgFreedesktopScreenSaverInterface(newOwner, QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus(), this);
0593         connect(m_screenLockerInterface, &OrgFreedesktopScreenSaverInterface::ActiveChanged, this, &PolicyAgent::onScreenLockerActiveChanged);
0594 
0595         auto *activeReplyWatcher = new QDBusPendingCallWatcher(m_screenLockerInterface->GetActive());
0596         connect(activeReplyWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0597             QDBusPendingReply<bool> reply = *watcher;
0598             if (!reply.isError()) {
0599                 onScreenLockerActiveChanged(reply.value());
0600             }
0601             watcher->deleteLater();
0602         });
0603     }
0604 }
0605 
0606 void PolicyAgent::onScreenLockerActiveChanged(bool active)
0607 {
0608     const auto oldPolicies = unavailablePolicies();
0609 
0610     if (m_screenLockerActive != active) {
0611         m_screenLockerActive = active;
0612         Q_EMIT screenLockerActiveChanged(active);
0613     }
0614 
0615     const auto newPolicies = unavailablePolicies();
0616 
0617     if (oldPolicies != newPolicies) {
0618         qCDebug(POWERDEVIL) << "Screen saver active" << active << "- we have different inhibition policy now because of that";
0619         Q_EMIT unavailablePoliciesChanged(newPolicies);
0620     }
0621 }
0622 
0623 uint PolicyAgent::addInhibitionWithExplicitDBusService(uint types, const QString &appName,
0624                                                        const QString &reason, const QString &service)
0625 {
0626     ++m_lastCookie;
0627 
0628     const int cookie = m_lastCookie; // when the Timer below fires, m_lastCookie might be different already
0629 
0630     if (!m_busWatcher.isNull() && !service.isEmpty()) {
0631         m_cookieToBusService.insert(cookie, service);
0632         m_busWatcher.data()->addWatchedService(service);
0633     }
0634 
0635     m_pendingInhibitions.append(cookie);
0636 
0637     qCDebug(POWERDEVIL) << "Scheduling inhibition from" << service << appName << "with cookie"
0638                         << cookie << "and reason" << reason;
0639 
0640     // wait 5s before actually enforcing the inhibition
0641     // there might be short interruptions (such as receiving a message) where an app might automatically
0642     // post an inhibition but we don't want the system to constantly wakeup because of this
0643     QTimer::singleShot(5000, this, [=] {
0644         qCDebug(POWERDEVIL) << "Enforcing inhibition from" << service << appName << "with cookie"
0645                             << cookie << "and reason" << reason;
0646 
0647         if (!m_pendingInhibitions.contains(cookie)) {
0648             qCDebug(POWERDEVIL) << "By the time we wanted to enforce the inhibition it was already gone; discarding it";
0649             return;
0650         }
0651 
0652         m_cookieToAppName.insert(cookie, qMakePair(appName, reason));
0653 
0654         addInhibitionTypeHelper(cookie, static_cast< PolicyAgent::RequiredPolicies >(types));
0655 
0656         Q_EMIT InhibitionsChanged({ {qMakePair(appName, reason)} }, {});
0657 
0658         m_pendingInhibitions.removeOne(cookie);
0659     });
0660 
0661     return cookie;
0662 }
0663 
0664 uint PolicyAgent::AddInhibition(uint types, const QString &appName, const QString &reason)
0665 {
0666     if (calledFromDBus()) {
0667         return addInhibitionWithExplicitDBusService(types, appName, reason, message().service());
0668     } else {
0669         return addInhibitionWithExplicitDBusService(types, appName, reason, QString());
0670     }
0671 }
0672 
0673 void PolicyAgent::addInhibitionTypeHelper(uint cookie, PolicyAgent::RequiredPolicies types)
0674 {
0675     // Look through all of the inhibition types
0676     bool notify = false;
0677     if (types & ChangeProfile) {
0678         // Check if we have to notify
0679         if (m_typesToCookie[ChangeProfile].isEmpty()) {
0680             qCDebug(POWERDEVIL) << "Added change profile";
0681             notify = true;
0682         }
0683         m_typesToCookie[ChangeProfile].append(cookie);
0684     }
0685     if (types & ChangeScreenSettings) {
0686         // Check if we have to notify
0687         qCDebug(POWERDEVIL) << "Added change screen settings";
0688         if (m_typesToCookie[ChangeScreenSettings].isEmpty()) {
0689             notify = true;
0690         }
0691         m_typesToCookie[ChangeScreenSettings].append(cookie);
0692         types |= InterruptSession;  // implied by ChangeScreenSettings
0693     }
0694     if (types & InterruptSession) {
0695         // Check if we have to notify
0696         qCDebug(POWERDEVIL) << "Added interrupt session";
0697         if (m_typesToCookie[InterruptSession].isEmpty()) {
0698             notify = true;
0699         }
0700         m_typesToCookie[InterruptSession].append(cookie);
0701     }
0702 
0703     if (notify) {
0704         // emit the signal - inhibition has changed
0705         Q_EMIT unavailablePoliciesChanged(unavailablePolicies());
0706     }
0707 }
0708 
0709 void PolicyAgent::ReleaseInhibition(uint cookie)
0710 {
0711     qCDebug(POWERDEVIL) << "Releasing inhibition with cookie " << cookie;
0712 
0713     QString service = m_cookieToBusService.take(cookie);
0714     if (!m_busWatcher.isNull() && !service.isEmpty() && !m_cookieToBusService.key(service)) {
0715         // no cookies from service left
0716         m_busWatcher.data()->removeWatchedService(service);
0717     }
0718 
0719     if (m_pendingInhibitions.removeOne(cookie)) {
0720         qCDebug(POWERDEVIL) << "It was only scheduled for inhibition but not enforced yet, just discarding it";
0721         return;
0722     }
0723 
0724     Q_EMIT InhibitionsChanged(QList<InhibitionInfo>(), { {m_cookieToAppName.value(cookie).first} });
0725     m_cookieToAppName.remove(cookie);
0726 
0727     // Look through all of the inhibition types
0728     bool notify = false;
0729     if (m_typesToCookie[ChangeProfile].contains(cookie)) {
0730         m_typesToCookie[ChangeProfile].removeOne(cookie);
0731         // Check if we have to notify
0732         if (m_typesToCookie[ChangeProfile].isEmpty()) {
0733             notify = true;
0734         }
0735     }
0736     if (m_typesToCookie[ChangeScreenSettings].contains(cookie)) {
0737         m_typesToCookie[ChangeScreenSettings].removeOne(cookie);
0738         // Check if we have to notify
0739         if (m_typesToCookie[ChangeScreenSettings].isEmpty()) {
0740             notify = true;
0741         }
0742     }
0743     if (m_typesToCookie[InterruptSession].contains(cookie)) {
0744         m_typesToCookie[InterruptSession].removeOne(cookie);
0745         // Check if we have to notify
0746         if (m_typesToCookie[InterruptSession].isEmpty()) {
0747             notify = true;
0748         }
0749     }
0750 
0751     if (notify) {
0752         // Emit the signal - inhibition has changed
0753         Q_EMIT unavailablePoliciesChanged(unavailablePolicies());
0754     }
0755 }
0756 
0757 QList<InhibitionInfo> PolicyAgent::ListInhibitions() const
0758 {
0759     return m_cookieToAppName.values();
0760 }
0761 
0762 bool PolicyAgent::HasInhibition(/*PolicyAgent::RequiredPolicies*/ uint types)
0763 {
0764     return requirePolicyCheck(static_cast<PolicyAgent::RequiredPolicies>(types)) != PolicyAgent::None;
0765 }
0766 
0767 void PolicyAgent::releaseAllInhibitions()
0768 {
0769     const QList< uint > allCookies = m_cookieToAppName.keys();
0770     for (uint cookie : allCookies) {
0771         ReleaseInhibition(cookie);
0772     }
0773 }
0774 
0775 void PolicyAgent::setupSystemdInhibition()
0776 {
0777     if (m_systemdInhibitFd.fileDescriptor() != -1)
0778         return;
0779 
0780     if (!m_managerIface)
0781         return;
0782 
0783     // inhibit systemd/ConsoleKit2 handling of power/sleep/lid buttons
0784     // https://www.freedesktop.org/wiki/Software/systemd/inhibit
0785     // https://consolekit2.github.io/ConsoleKit2/#Manager.Inhibit
0786     qCDebug(POWERDEVIL) << "fd passing available:" << bool(m_managerIface.data()->connection().connectionCapabilities() & QDBusConnection::UnixFileDescriptorPassing);
0787 
0788     QVariantList args;
0789     args << QStringLiteral("handle-power-key:handle-suspend-key:handle-hibernate-key:handle-lid-switch"); // what
0790     args << QStringLiteral("PowerDevil"); // who
0791     args << QStringLiteral("KDE handles power events"); // why
0792     args << QStringLiteral("block"); // mode
0793     QDBusPendingReply<QDBusUnixFileDescriptor> desc = m_managerIface.data()->asyncCallWithArgumentList(QStringLiteral("Inhibit"), args);
0794     desc.waitForFinished();
0795     if (desc.isValid()) {
0796         m_systemdInhibitFd = desc.value();
0797         qCDebug(POWERDEVIL) << "systemd powersave events handling inhibited, descriptor:" << m_systemdInhibitFd.fileDescriptor();
0798     }
0799     else
0800         qCWarning(POWERDEVIL) << "failed to inhibit systemd powersave handling";
0801 }
0802 
0803 }