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"