/*************************************************************************** * Copyright (C) 2010 by Dario Freddi * * Copyright (C) 2012 Lukáš Tinkl * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "powerdevilpolicyagent.h" #include "screensaver_interface.h" struct NamedDBusObjectPath { QString name; QDBusObjectPath path; }; // Marshall the NamedDBusObjectPath data into a D-Bus argument QDBusArgument &operator<<(QDBusArgument &argument, const NamedDBusObjectPath &namedPath) { argument.beginStructure(); argument << namedPath.name << namedPath.path; argument.endStructure(); return argument; } // Retrieve the NamedDBusObjectPath data from the D-Bus argument const QDBusArgument &operator>>(const QDBusArgument &argument, NamedDBusObjectPath &namedPath) { argument.beginStructure(); argument >> namedPath.name >> namedPath.path; argument.endStructure(); return argument; } Q_DECLARE_METATYPE(NamedDBusObjectPath) namespace PowerDevil { class PolicyAgentHelper { public: PolicyAgentHelper() : q(0) { } ~PolicyAgentHelper() { delete q; } PolicyAgent *q; }; K_GLOBAL_STATIC(PolicyAgentHelper, s_globalPolicyAgent) PolicyAgent *PolicyAgent::instance() { if (!s_globalPolicyAgent->q) { new PolicyAgent; } return s_globalPolicyAgent->q; } PolicyAgent::PolicyAgent(QObject* parent) : QObject(parent) , m_sdAvailable(false) , m_systemdInhibitFd(-1) , m_ckAvailable(false) , m_sessionIsBeingInterrupted(false) , m_lastCookie(0) , m_busWatcher(new QDBusServiceWatcher(this)) , m_sdWatcher(new QDBusServiceWatcher(this)) , m_ckWatcher(new QDBusServiceWatcher(this)) { Q_ASSERT(!s_globalPolicyAgent->q); s_globalPolicyAgent->q = this; } PolicyAgent::~PolicyAgent() { } void PolicyAgent::init() { // Watch over the systemd service m_sdWatcher.data()->setConnection(QDBusConnection::systemBus()); m_sdWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration); m_sdWatcher.data()->addWatchedService(SYSTEMD_LOGIN1_SERVICE); connect(m_sdWatcher.data(), SIGNAL(serviceRegistered(QString)), this, SLOT(onSessionHandlerRegistered(QString))); connect(m_sdWatcher.data(), SIGNAL(serviceUnregistered(QString)), this, SLOT(onSessionHandlerUnregistered(QString))); // If it's up and running already, let's cache it if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_LOGIN1_SERVICE)) { onSessionHandlerRegistered(SYSTEMD_LOGIN1_SERVICE); } // Watch over the ConsoleKit service m_ckWatcher.data()->setConnection(QDBusConnection::sessionBus()); m_ckWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration); m_ckWatcher.data()->addWatchedService(CONSOLEKIT_SERVICE); connect(m_ckWatcher.data(), SIGNAL(serviceRegistered(QString)), this, SLOT(onSessionHandlerRegistered(QString))); connect(m_ckWatcher.data(), SIGNAL(serviceUnregistered(QString)), this, SLOT(onSessionHandlerUnregistered(QString))); // If it's up and running already, let's cache it if (QDBusConnection::systemBus().interface()->isServiceRegistered(CONSOLEKIT_SERVICE)) { onSessionHandlerRegistered(CONSOLEKIT_SERVICE); } // Now set up our service watcher m_busWatcher.data()->setConnection(QDBusConnection::sessionBus()); m_busWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); connect(m_busWatcher.data(), SIGNAL(serviceUnregistered(QString)), this, SLOT(onServiceUnregistered(QString))); } QString PolicyAgent::getNamedPathProperty(const QString &path, const QString &iface, const QString &prop) const { QDBusMessage message = QDBusMessage::createMethodCall(SYSTEMD_LOGIN1_SERVICE, path, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get")); message << iface << prop; QDBusMessage reply = QDBusConnection::systemBus().call(message); QVariantList args = reply.arguments(); if (!args.isEmpty()) { NamedDBusObjectPath namedPath; args.at(0).value().variant().value() >> namedPath; return namedPath.path.path(); } return QString(); } void PolicyAgent::forceLockAndWait() { OrgFreedesktopScreenSaverInterface screenSaveriface("org.freedesktop.ScreenSaver", "/ScreenSaver", QDBusConnection::sessionBus()); QDBusPendingReply< void > reply = screenSaveriface.Lock(); reply.waitForFinished(); } void PolicyAgent::onSessionHandlerRegistered(const QString & serviceName) { if (serviceName == SYSTEMD_LOGIN1_SERVICE) { m_sdAvailable = true; qRegisterMetaType(); qDBusRegisterMetaType(); // get the current session QDBusInterface managerIface(SYSTEMD_LOGIN1_SERVICE, SYSTEMD_LOGIN1_PATH, SYSTEMD_LOGIN1_MANAGER_IFACE, QDBusConnection::systemBus()); QDBusPendingReply session = managerIface.asyncCall(QLatin1String("GetSessionByPID"), (quint32) QCoreApplication::applicationPid()); session.waitForFinished(); if (!session.isValid()) { kDebug() << "The session is not registered with systemd"; m_sdAvailable = false; return; } QString sessionPath = session.value().path(); kDebug() << "Session path:" << sessionPath; m_sdSessionInterface = new QDBusInterface(SYSTEMD_LOGIN1_SERVICE, sessionPath, SYSTEMD_LOGIN1_SESSION_IFACE, QDBusConnection::systemBus(), this); if (!m_sdSessionInterface.data()->isValid()) { // As above kDebug() << "Can't contact session iface"; m_sdAvailable = false; delete m_sdSessionInterface.data(); return; } // listen to the systemd-login1 session's Lock signal connect(m_sdSessionInterface.data(), SIGNAL(Lock()), this, SLOT(forceLockAndWait())); // now let's obtain the seat QString seatPath = getNamedPathProperty(sessionPath, SYSTEMD_LOGIN1_SESSION_IFACE, "Seat"); if (seatPath.isEmpty() || seatPath == "/") { kDebug() << "Unable to associate systemd session with a seat" << seatPath; m_sdAvailable = false; return; } // get the current seat m_sdSeatInterface = new QDBusInterface(SYSTEMD_LOGIN1_SERVICE, seatPath, SYSTEMD_LOGIN1_SEAT_IFACE, QDBusConnection::systemBus(), this); if (!m_sdSeatInterface.data()->isValid()) { // As above kDebug() << "Can't contact seat iface"; m_sdAvailable = false; delete m_sdSeatInterface.data(); return; } // finally get the active session path and watch for its changes m_activeSessionPath = getNamedPathProperty(seatPath, SYSTEMD_LOGIN1_SEAT_IFACE, "ActiveSession"); kDebug() << "ACTIVE SESSION PATH:" << m_activeSessionPath; QDBusConnection::systemBus().connect(SYSTEMD_LOGIN1_SERVICE, seatPath, "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onActiveSessionChanged(QString,QVariantMap,QStringList))); onActiveSessionChanged(m_activeSessionPath); setupSystemdInhibition(); kDebug() << "systemd support initialized"; } else if (serviceName == CONSOLEKIT_SERVICE) { m_ckAvailable = true; // Otherwise, let's ask ConsoleKit QDBusInterface ckiface(CONSOLEKIT_SERVICE, "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus()); QDBusPendingReply sessionPath = ckiface.asyncCall("GetCurrentSession"); sessionPath.waitForFinished(); if (!sessionPath.isValid() || sessionPath.value().path().isEmpty()) { kDebug() << "The session is not registered with ck"; m_ckAvailable = false; return; } m_ckSessionInterface = new QDBusInterface(CONSOLEKIT_SERVICE, sessionPath.value().path(), "org.freedesktop.ConsoleKit.Session", QDBusConnection::systemBus()); if (!m_ckSessionInterface.data()->isValid()) { // As above kDebug() << "Can't contact iface"; m_ckAvailable = false; return; } // Now let's obtain the seat QDBusPendingReply< QDBusObjectPath > seatPath = m_ckSessionInterface.data()->asyncCall("GetSeatId"); seatPath.waitForFinished(); if (!seatPath.isValid() || seatPath.value().path().isEmpty()) { kDebug() << "Unable to associate ck session with a seat"; m_ckAvailable = false; return; } if (!QDBusConnection::systemBus().connect(CONSOLEKIT_SERVICE, seatPath.value().path(), "org.freedesktop.ConsoleKit.Seat", "ActiveSessionChanged", this, SLOT(onActiveSessionChanged(QString)))) { kDebug() << "Unable to connect to ActiveSessionChanged"; m_ckAvailable = false; return; } // Force triggering of active session changed QDBusMessage call = QDBusMessage::createMethodCall(CONSOLEKIT_SERVICE, seatPath.value().path(), "org.freedesktop.ConsoleKit.Seat", "GetActiveSession"); QDBusPendingReply< QDBusObjectPath > activeSession = QDBusConnection::systemBus().asyncCall(call); activeSession.waitForFinished(); onActiveSessionChanged(activeSession.value().path()); kDebug() << "ConsoleKit support initialized"; } else kWarning() << "Unhandled service registered:" << serviceName; } void PolicyAgent::onSessionHandlerUnregistered(const QString & serviceName) { if (serviceName == SYSTEMD_LOGIN1_SERVICE) { m_sdAvailable = false; delete m_sdSessionInterface.data(); } else if (serviceName == CONSOLEKIT_SERVICE) { m_ckAvailable = false; delete m_ckSessionInterface.data(); } } void PolicyAgent::onActiveSessionChanged(const QString & ifaceName, const QVariantMap & changedProps, const QStringList & invalidatedProps) { const QString key = QLatin1String("ActiveSession"); if (ifaceName == SYSTEMD_LOGIN1_SEAT_IFACE && (changedProps.keys().contains(key) || invalidatedProps.contains(key))) { m_activeSessionPath = getNamedPathProperty(m_sdSeatInterface.data()->path(), SYSTEMD_LOGIN1_SEAT_IFACE, key); kDebug() << "ACTIVE SESSION PATH CHANGED:" << m_activeSessionPath; onActiveSessionChanged(m_activeSessionPath); } } void PolicyAgent::onActiveSessionChanged(const QString& activeSession) { if (activeSession.isEmpty() || activeSession == "/") { kDebug() << "Switched to inactive session - leaving unchanged"; return; } else if ((!m_sdSessionInterface.isNull() && activeSession == m_sdSessionInterface.data()->path()) || (!m_ckSessionInterface.isNull() && activeSession == m_ckSessionInterface.data()->path())) { kDebug() << "Current session is now active"; m_wasLastActiveSession = true; } else { kDebug() << "Current session is now inactive"; m_wasLastActiveSession = false; } } void PolicyAgent::onServiceUnregistered(const QString& serviceName) { if (m_cookieToBusService.values().contains(serviceName)) { // Ouch - the application quit or crashed without releasing its inhibitions. Let's fix that. foreach (uint key, m_cookieToBusService.keys(serviceName)) { ReleaseInhibition(key); } } } PolicyAgent::RequiredPolicies PolicyAgent::unavailablePolicies() { RequiredPolicies retpolicies = None; if (!m_typesToCookie[ChangeProfile].isEmpty()) { retpolicies |= ChangeProfile; } if (!m_typesToCookie[ChangeScreenSettings].isEmpty()) { retpolicies |= ChangeScreenSettings; } if (!m_typesToCookie[InterruptSession].isEmpty()) { retpolicies |= InterruptSession; } return retpolicies; } PolicyAgent::RequiredPolicies PolicyAgent::requirePolicyCheck(PolicyAgent::RequiredPolicies policies) { if (!m_sdAvailable) { // No way to determine if we are on the current session, simply suppose we are kDebug() << "Can't contact systemd"; } else if (!m_sdSessionInterface.isNull()) { bool isActive = m_sdSessionInterface.data()->property("Active").toBool(); if (!isActive && !m_wasLastActiveSession) { return policies; } } if (!m_ckAvailable) { // No way to determine if we are on the current session, simply suppose we are kDebug() << "Can't contact ck"; } else if (!m_ckSessionInterface.isNull()) { QDBusPendingReply< bool > rp = m_ckSessionInterface.data()->asyncCall("IsActive"); rp.waitForFinished(); if (!(rp.isValid() && rp.value()) && !m_wasLastActiveSession) { return policies; } } // Ok, let's go then RequiredPolicies retpolicies = None; if (policies & ChangeProfile) { if (!m_typesToCookie[ChangeProfile].isEmpty()) { retpolicies |= ChangeProfile; } } if (policies & ChangeScreenSettings) { if (!m_typesToCookie[ChangeScreenSettings].isEmpty()) { retpolicies |= ChangeScreenSettings; } } if (policies & InterruptSession) { if (m_sessionIsBeingInterrupted || !m_typesToCookie[InterruptSession].isEmpty()) { retpolicies |= InterruptSession; } } return retpolicies; } void PolicyAgent::startSessionInterruption() { m_sessionIsBeingInterrupted = true; } void PolicyAgent::finishSessionInterruption() { m_sessionIsBeingInterrupted = false; } uint PolicyAgent::addInhibitionWithExplicitDBusService(uint types, const QString& appName, const QString& reason, const QString& service) { ++m_lastCookie; m_cookieToAppName.insert(m_lastCookie, qMakePair(appName, reason)); if (!m_busWatcher.isNull() && !service.isEmpty()) { m_cookieToBusService.insert(m_lastCookie, service); m_busWatcher.data()->addWatchedService(service); } kDebug() << "Added inhibition from an explicit DBus service, " << service << ", with cookie " << m_lastCookie << " from " << appName << " with " << reason; addInhibitionTypeHelper(m_lastCookie, static_cast< PolicyAgent::RequiredPolicies >(types)); return m_lastCookie; } uint PolicyAgent::AddInhibition(uint types, const QString& appName, const QString& reason) { ++m_lastCookie; m_cookieToAppName.insert(m_lastCookie, qMakePair(appName, reason)); // Retrieve the service, if we've been called from DBus if (calledFromDBus() && !m_busWatcher.isNull()) { if (!message().service().isEmpty()) { kDebug() << "DBus service " << message().service() << " is requesting inhibition"; m_cookieToBusService.insert(m_lastCookie, message().service()); m_busWatcher.data()->addWatchedService(message().service()); } } kDebug() << "Added inhibition with cookie " << m_lastCookie << " from " << appName << " with " << reason; addInhibitionTypeHelper(m_lastCookie, static_cast< PolicyAgent::RequiredPolicies >(types)); return m_lastCookie; } void PolicyAgent::addInhibitionTypeHelper(uint cookie, PolicyAgent::RequiredPolicies types) { // Look through all of the inhibition types bool notify = false; if (types & ChangeProfile) { // Check if we have to notify if (m_typesToCookie[ChangeProfile].isEmpty()) { kDebug() << "Added change profile"; notify = true; } m_typesToCookie[ChangeProfile].append(cookie); } if (types & ChangeScreenSettings) { // Check if we have to notify kDebug() << "Added change screen settings"; if (m_typesToCookie[ChangeScreenSettings].isEmpty()) { notify = true; } m_typesToCookie[ChangeScreenSettings].append(cookie); types |= InterruptSession; // implied by ChangeScreenSettings } if (types & InterruptSession) { // Check if we have to notify kDebug() << "Added interrupt session"; if (m_typesToCookie[InterruptSession].isEmpty()) { notify = true; } m_typesToCookie[InterruptSession].append(cookie); } if (notify) { // Emit the signal - inhibition has changed emit unavailablePoliciesChanged(unavailablePolicies()); } } void PolicyAgent::ReleaseInhibition(uint cookie) { kDebug() << "Released inhibition with cookie " << cookie; m_cookieToAppName.remove(cookie); QString service = m_cookieToBusService.take(cookie); if (!m_busWatcher.isNull() && !service.isEmpty() && !m_cookieToBusService.key(service)) { // no cookies from service left m_busWatcher.data()->removeWatchedService(service); } // Look through all of the inhibition types bool notify = false; if (m_typesToCookie[ChangeProfile].contains(cookie)) { m_typesToCookie[ChangeProfile].removeOne(cookie); // Check if we have to notify if (m_typesToCookie[ChangeProfile].isEmpty()) { notify = true; } } if (m_typesToCookie[ChangeScreenSettings].contains(cookie)) { m_typesToCookie[ChangeScreenSettings].removeOne(cookie); // Check if we have to notify if (m_typesToCookie[ChangeScreenSettings].isEmpty()) { notify = true; } } if (m_typesToCookie[InterruptSession].contains(cookie)) { m_typesToCookie[InterruptSession].removeOne(cookie); // Check if we have to notify if (m_typesToCookie[InterruptSession].isEmpty()) { notify = true; } } if (notify) { // Emit the signal - inhibition has changed emit unavailablePoliciesChanged(unavailablePolicies()); } } void PolicyAgent::releaseAllInhibitions() { QList< uint > allCookies = m_cookieToAppName.keys(); foreach (uint cookie, allCookies) { ReleaseInhibition(cookie); } } void PolicyAgent::setupSystemdInhibition() { if (m_systemdInhibitFd.fileDescriptor() != -1) return; // inhibit systemd handling of power/sleep/lid buttons // http://www.freedesktop.org/wiki/Software/systemd/inhibit QDBusInterface managerIface(SYSTEMD_LOGIN1_SERVICE, SYSTEMD_LOGIN1_PATH, SYSTEMD_LOGIN1_MANAGER_IFACE, QDBusConnection::systemBus()); kDebug() << "fd passing available:" << bool(managerIface.connection().connectionCapabilities() & QDBusConnection::UnixFileDescriptorPassing); QVariantList args; args << "handle-power-key:handle-suspend-key:handle-hibernate-key:handle-lid-switch"; // what args << "PowerDevil"; // who args << "KDE handles power events"; // why args << "block"; // mode QDBusPendingReply desc = managerIface.asyncCallWithArgumentList("Inhibit", args); desc.waitForFinished(); if (desc.isValid()) { m_systemdInhibitFd = desc.value(); kDebug() << "systemd powersave events handling inhibited, descriptor:" << m_systemdInhibitFd.fileDescriptor(); } else kWarning() << "failed to inhibit systemd powersave handling"; } } #include "moc_powerdevilpolicyagent.cpp"