File indexing completed on 2024-04-28 05:36:16

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