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 }