implement systemd support for PowerDevil Policy Agent
[kde-workspace-appmenu:kde-workspace-appmenu.git] / powerdevil / daemon / powerdevilpolicyagent.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 by Dario Freddi <drf@kde.org>                      *
3  *   Copyright (C) 2012 Lukáš Tinkl <ltinkl@redhat.com>                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
19  ***************************************************************************/
20
21
22 #include "powerdevilpolicyagent.h"
23
24 #include <QtCore/QCoreApplication>
25 #include <QtDBus/QDBusObjectPath>
26 #include <QtDBus/QDBusArgument>
27 #include <QtCore/QMetaType>
28 #include <QtDBus/QDBusMetaType>
29
30 #include <QtDBus/QDBusConnection>
31 #include <QtDBus/QDBusInterface>
32 #include <QtDBus/QDBusPendingReply>
33 #include <QtDBus/QDBusConnectionInterface>
34 #include <QtDBus/QDBusServiceWatcher>
35
36 #include <KGlobal>
37 #include <KDebug>
38
39 struct NamedDBusObjectPath
40 {
41     QString name;
42     QDBusObjectPath path;
43 };
44
45 // Marshall the NamedDBusObjectPath data into a D-Bus argument
46 QDBusArgument &operator<<(QDBusArgument &argument, const NamedDBusObjectPath &namedPath)
47 {
48     argument.beginStructure();
49     argument << namedPath.name << namedPath.path;
50     argument.endStructure();
51     return argument;
52 }
53
54 // Retrieve the NamedDBusObjectPath data from the D-Bus argument
55 const QDBusArgument &operator>>(const QDBusArgument &argument, NamedDBusObjectPath &namedPath)
56 {
57     argument.beginStructure();
58     argument >> namedPath.name >> namedPath.path;
59     argument.endStructure();
60     return argument;
61 }
62
63 Q_DECLARE_METATYPE(NamedDBusObjectPath)
64
65 namespace PowerDevil
66 {
67
68 class PolicyAgentHelper
69 {
70 public:
71     PolicyAgentHelper() : q(0) { }
72     ~PolicyAgentHelper() {
73         delete q;
74     }
75     PolicyAgent *q;
76 };
77
78 K_GLOBAL_STATIC(PolicyAgentHelper, s_globalPolicyAgent)
79
80 PolicyAgent *PolicyAgent::instance()
81 {
82     if (!s_globalPolicyAgent->q) {
83         new PolicyAgent;
84     }
85
86     return s_globalPolicyAgent->q;
87 }
88
89 PolicyAgent::PolicyAgent(QObject* parent)
90     : QObject(parent)
91     , m_sdAvailable(false)
92     , m_ckAvailable(false)
93     , m_sessionIsBeingInterrupted(false)
94     , m_lastCookie(0)
95     , m_busWatcher(new QDBusServiceWatcher(this))
96     , m_sdWatcher(new QDBusServiceWatcher(this))
97     , m_ckWatcher(new QDBusServiceWatcher(this))
98 {
99     Q_ASSERT(!s_globalPolicyAgent->q);
100     s_globalPolicyAgent->q = this;
101 }
102
103 PolicyAgent::~PolicyAgent()
104 {
105
106 }
107
108 void PolicyAgent::init()
109 {
110     // Watch over the systemd service
111     m_sdWatcher.data()->setConnection(QDBusConnection::systemBus());
112     m_sdWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration |
113                                      QDBusServiceWatcher::WatchForRegistration);
114     m_sdWatcher.data()->addWatchedService(SYSTEMD_LOGIN1_SERVICE);
115
116     connect(m_sdWatcher.data(), SIGNAL(serviceRegistered(QString)),
117             this, SLOT(onSessionHandlerRegistered(QString)));
118     connect(m_sdWatcher.data(), SIGNAL(serviceUnregistered(QString)),
119             this, SLOT(onSessionHandlerUnregistered(QString)));
120     // If it's up and running already, let's cache it
121     if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_LOGIN1_SERVICE)) {
122         onSessionHandlerRegistered(SYSTEMD_LOGIN1_SERVICE);
123     }
124
125     // Watch over the ConsoleKit service
126     m_ckWatcher.data()->setConnection(QDBusConnection::sessionBus());
127     m_ckWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration |
128                                      QDBusServiceWatcher::WatchForRegistration);
129     m_ckWatcher.data()->addWatchedService(CONSOLEKIT_SERVICE);
130
131     connect(m_ckWatcher.data(), SIGNAL(serviceRegistered(QString)),
132             this, SLOT(onSessionHandlerRegistered(QString)));
133     connect(m_ckWatcher.data(), SIGNAL(serviceUnregistered(QString)),
134             this, SLOT(onSessionHandlerUnregistered(QString)));
135     // If it's up and running already, let's cache it
136     if (QDBusConnection::systemBus().interface()->isServiceRegistered(CONSOLEKIT_SERVICE)) {
137         onSessionHandlerRegistered(CONSOLEKIT_SERVICE);
138     }
139
140     // Now set up our service watcher
141     m_busWatcher.data()->setConnection(QDBusConnection::sessionBus());
142     m_busWatcher.data()->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
143
144     connect(m_busWatcher.data(), SIGNAL(serviceUnregistered(QString)),
145             this, SLOT(onServiceUnregistered(QString)));
146 }
147
148 QString PolicyAgent::getNamedPathProperty(const QString &path, const QString &iface, const QString &prop) const
149 {
150     QDBusMessage message = QDBusMessage::createMethodCall(SYSTEMD_LOGIN1_SERVICE, path,
151                                                           QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get"));
152     message << iface << prop;
153     QDBusMessage reply = QDBusConnection::systemBus().call(message);
154
155     QVariantList args = reply.arguments();
156     if (!args.isEmpty()) {
157         NamedDBusObjectPath namedPath;
158         args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> namedPath;
159         return namedPath.path.path();
160     }
161
162     return QString();
163 }
164
165 void PolicyAgent::onSessionHandlerRegistered(const QString & serviceName)
166 {
167     if (serviceName == SYSTEMD_LOGIN1_SERVICE) {
168         m_sdAvailable = true;
169
170         qRegisterMetaType<NamedDBusObjectPath>();
171         qDBusRegisterMetaType<NamedDBusObjectPath>();
172
173         // get the current session
174         QDBusInterface managerIface(SYSTEMD_LOGIN1_SERVICE, SYSTEMD_LOGIN1_PATH, SYSTEMD_LOGIN1_MANAGER_IFACE, QDBusConnection::systemBus());
175         QDBusPendingReply<QDBusObjectPath> session = managerIface.asyncCall(QLatin1String("GetSessionByPID"), (quint32) QCoreApplication::applicationPid());
176         session.waitForFinished();
177
178         if (!session.isValid()) {
179             kDebug() << "The session is not registered with systemd";
180             m_sdAvailable = false;
181             return;
182         }
183
184         QString sessionPath = session.value().path();
185         kDebug() << "Session path:" << sessionPath;
186
187         m_sdSessionInterface = new QDBusInterface(SYSTEMD_LOGIN1_SERVICE, sessionPath,
188                                                   SYSTEMD_LOGIN1_SESSION_IFACE, QDBusConnection::systemBus(), this);
189         if (!m_sdSessionInterface.data()->isValid()) {
190             // As above
191             kDebug() << "Can't contact session iface";
192             m_sdAvailable = false;
193             delete m_sdSessionInterface.data();
194             return;
195         }
196
197
198         // now let's obtain the seat
199         QString seatPath = getNamedPathProperty(sessionPath, SYSTEMD_LOGIN1_SESSION_IFACE, "Seat");
200
201         if (seatPath.isEmpty() || seatPath == "/") {
202             kDebug() << "Unable to associate systemd session with a seat" << seatPath;
203             m_sdAvailable = false;
204             return;
205         }
206
207         // get the current seat
208         m_sdSeatInterface = new QDBusInterface(SYSTEMD_LOGIN1_SERVICE, seatPath,
209                                                SYSTEMD_LOGIN1_SEAT_IFACE, QDBusConnection::systemBus(), this);
210
211         if (!m_sdSeatInterface.data()->isValid()) {
212             // As above
213             kDebug() << "Can't contact seat iface";
214             m_sdAvailable = false;
215             delete m_sdSeatInterface.data();
216             return;
217         }
218
219         // finally get the active session path and watch for its changes
220         m_activeSessionPath = getNamedPathProperty(seatPath, SYSTEMD_LOGIN1_SEAT_IFACE, "ActiveSession");
221
222         kDebug() << "ACTIVE SESSION PATH:" << m_activeSessionPath;
223         QDBusConnection::systemBus().connect(SYSTEMD_LOGIN1_SERVICE, seatPath, "org.freedesktop.DBus.Properties", "PropertiesChanged", this,
224                                              SLOT(onActiveSessionChanged(QString,QVariantMap,QStringList)));
225
226         onActiveSessionChanged(m_activeSessionPath);
227
228         kDebug() << "systemd support initialized";
229     } else if (serviceName == CONSOLEKIT_SERVICE) {
230         m_ckAvailable = true;
231
232         // Otherwise, let's ask ConsoleKit
233         QDBusInterface ckiface(CONSOLEKIT_SERVICE, "/org/freedesktop/ConsoleKit/Manager",
234                                "org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus());
235
236         QDBusPendingReply<QDBusObjectPath> sessionPath = ckiface.asyncCall("GetCurrentSession");
237
238         sessionPath.waitForFinished();
239
240         if (!sessionPath.isValid() || sessionPath.value().path().isEmpty()) {
241             kDebug() << "The session is not registered with ck";
242             m_ckAvailable = false;
243             return;
244         }
245
246         m_ckSessionInterface = new QDBusInterface(CONSOLEKIT_SERVICE, sessionPath.value().path(),
247                                                   "org.freedesktop.ConsoleKit.Session", QDBusConnection::systemBus());
248
249         if (!m_ckSessionInterface.data()->isValid()) {
250             // As above
251             kDebug() << "Can't contact iface";
252             m_ckAvailable = false;
253             return;
254         }
255
256         // Now let's obtain the seat
257         QDBusPendingReply< QDBusObjectPath > seatPath = m_ckSessionInterface.data()->asyncCall("GetSeatId");
258         seatPath.waitForFinished();
259
260         if (!seatPath.isValid() || seatPath.value().path().isEmpty()) {
261             kDebug() << "Unable to associate ck session with a seat";
262             m_ckAvailable = false;
263             return;
264         }
265
266         if (!QDBusConnection::systemBus().connect(CONSOLEKIT_SERVICE, seatPath.value().path(),
267                                                   "org.freedesktop.ConsoleKit.Seat", "ActiveSessionChanged",
268                                                   this, SLOT(onActiveSessionChanged(QString)))) {
269             kDebug() << "Unable to connect to ActiveSessionChanged";
270             m_ckAvailable = false;
271             return;
272         }
273
274         // Force triggering of active session changed
275         QDBusMessage call = QDBusMessage::createMethodCall(CONSOLEKIT_SERVICE, seatPath.value().path(),
276                                                            "org.freedesktop.ConsoleKit.Seat", "GetActiveSession");
277         QDBusPendingReply< QDBusObjectPath > activeSession = QDBusConnection::systemBus().asyncCall(call);
278         activeSession.waitForFinished();
279
280         onActiveSessionChanged(activeSession.value().path());
281
282         kDebug() << "ConsoleKit support initialized";
283     }
284     else
285         kdWarning() << "Unhandled service registered:" << serviceName;
286 }
287
288 void PolicyAgent::onSessionHandlerUnregistered(const QString & serviceName)
289 {
290     if (serviceName == SYSTEMD_LOGIN1_SERVICE) {
291         m_sdAvailable = false;
292         delete m_sdSessionInterface.data();
293     }
294     else if (serviceName == CONSOLEKIT_SERVICE) {
295         m_ckAvailable = false;
296         delete m_ckSessionInterface.data();
297     }
298 }
299
300 void PolicyAgent::onActiveSessionChanged(const QString & ifaceName, const QVariantMap & changedProps, const QStringList & invalidatedProps)
301 {
302     const QString key = QLatin1String("ActiveSession");
303
304     if (ifaceName == SYSTEMD_LOGIN1_SEAT_IFACE && (changedProps.keys().contains(key) || invalidatedProps.contains(key))) {
305         m_activeSessionPath = getNamedPathProperty(m_sdSeatInterface.data()->path(), SYSTEMD_LOGIN1_SEAT_IFACE, key);
306         kDebug() << "ACTIVE SESSION PATH CHANGED:" << m_activeSessionPath;
307         onActiveSessionChanged(m_activeSessionPath);
308     }
309 }
310
311 void PolicyAgent::onActiveSessionChanged(const QString& activeSession)
312 {
313     if (activeSession.isEmpty() || activeSession == "/") {
314         kDebug() << "Switched to inactive session - leaving unchanged";
315         return;
316     } else if ((!m_sdSessionInterface.isNull() && activeSession == m_sdSessionInterface.data()->path()) ||
317                (!m_ckSessionInterface.isNull() && activeSession == m_ckSessionInterface.data()->path())) {
318         kDebug() << "Current session is now active";
319         m_wasLastActiveSession = true;
320     } else {
321         kDebug() << "Current session is now inactive";
322         m_wasLastActiveSession = false;
323     }
324 }
325
326 void PolicyAgent::onServiceUnregistered(const QString& serviceName)
327 {
328     if (m_cookieToBusService.values().contains(serviceName)) {
329         // Ouch - the application quit or crashed without releasing its inhibitions. Let's fix that.
330         foreach (uint key, m_cookieToBusService.keys(serviceName)) {
331             ReleaseInhibition(key);
332         }
333     }
334 }
335
336 PolicyAgent::RequiredPolicies PolicyAgent::unavailablePolicies()
337 {
338     RequiredPolicies retpolicies = None;
339
340     if (!m_typesToCookie[ChangeProfile].isEmpty()) {
341         retpolicies |= ChangeProfile;
342     }
343     if (!m_typesToCookie[ChangeScreenSettings].isEmpty()) {
344         retpolicies |= ChangeScreenSettings;
345     }
346     if (!m_typesToCookie[InterruptSession].isEmpty()) {
347         retpolicies |= InterruptSession;
348     }
349
350     return retpolicies;
351 }
352
353 PolicyAgent::RequiredPolicies PolicyAgent::requirePolicyCheck(PolicyAgent::RequiredPolicies policies)
354 {
355     if (!m_sdAvailable) {
356         // No way to determine if we are on the current session, simply suppose we are
357         kDebug() << "Can't contact systemd";
358     } else if (!m_sdSessionInterface.isNull()) {
359         bool isActive = m_sdSessionInterface.data()->property("Active").toBool();
360
361         if (!isActive && !m_wasLastActiveSession && policies != InterruptSession) {
362             return policies;
363         }
364     }
365
366     if (!m_ckAvailable) {
367         // No way to determine if we are on the current session, simply suppose we are
368         kDebug() << "Can't contact ck";
369     } else if (!m_ckSessionInterface.isNull()) {
370         QDBusPendingReply< bool > rp = m_ckSessionInterface.data()->asyncCall("IsActive");
371         rp.waitForFinished();
372
373         if (!rp.value() && !m_wasLastActiveSession && policies != InterruptSession) {
374             return policies;
375         }
376     }
377
378     // Ok, let's go then
379     RequiredPolicies retpolicies = None;
380
381     if (policies & ChangeProfile) {
382         if (!m_typesToCookie[ChangeProfile].isEmpty()) {
383             retpolicies |= ChangeProfile;
384         }
385     }
386     if (policies & ChangeScreenSettings) {
387         if (!m_typesToCookie[ChangeScreenSettings].isEmpty()) {
388             retpolicies |= ChangeScreenSettings;
389         }
390     }
391     if (policies & InterruptSession) {
392         if (m_sessionIsBeingInterrupted || !m_typesToCookie[InterruptSession].isEmpty()) {
393             retpolicies |= InterruptSession;
394         }
395     }
396
397     return retpolicies;
398 }
399
400 void PolicyAgent::startSessionInterruption()
401 {
402     m_sessionIsBeingInterrupted = true;
403 }
404
405 void PolicyAgent::finishSessionInterruption()
406 {
407     m_sessionIsBeingInterrupted = false;
408 }
409
410 uint PolicyAgent::addInhibitionWithExplicitDBusService(uint types, const QString& appName,
411                                                        const QString& reason, const QString& service)
412 {
413     ++m_lastCookie;
414
415     m_cookieToAppName.insert(m_lastCookie, qMakePair<QString, QString>(appName, reason));
416
417     if (!m_busWatcher.isNull() && !service.isEmpty()) {
418         m_cookieToBusService.insert(m_lastCookie, service);
419         m_busWatcher.data()->addWatchedService(service);
420     }
421
422     kDebug() << "Added inhibition from an explicit DBus service, " << service << ", with cookie " <<
423             m_lastCookie << " from " << appName << " with " << reason;
424
425     addInhibitionTypeHelper(m_lastCookie, static_cast< PolicyAgent::RequiredPolicies >(types));
426
427     return m_lastCookie;
428 }
429
430 uint PolicyAgent::AddInhibition(uint types,
431                                 const QString& appName,
432                                 const QString& reason)
433 {
434     ++m_lastCookie;
435
436     m_cookieToAppName.insert(m_lastCookie, qMakePair<QString, QString>(appName, reason));
437
438     // Retrieve the service, if we've been called from DBus
439     if (calledFromDBus() && !m_busWatcher.isNull()) {
440         if (!message().service().isEmpty()) {
441             kDebug() << "DBus service " << message().service() << " is requesting inhibition";
442             m_cookieToBusService.insert(m_lastCookie, message().service());
443             m_busWatcher.data()->addWatchedService(message().service());
444         }
445     }
446
447     kDebug() << "Added inhibition with cookie " << m_lastCookie << " from " <<
448             appName << " with " << reason;
449
450     addInhibitionTypeHelper(m_lastCookie, static_cast< PolicyAgent::RequiredPolicies >(types));
451
452     return m_lastCookie;
453 }
454
455 void PolicyAgent::addInhibitionTypeHelper(uint cookie, PolicyAgent::RequiredPolicies types)
456 {
457     // Look through all of the inhibition types
458     bool notify = false;
459     if (types & ChangeProfile) {
460         // Check if we have to notify
461         if (m_typesToCookie[ChangeProfile].isEmpty()) {
462             kDebug() << "Added change profile";
463             notify = true;
464         }
465         m_typesToCookie[ChangeProfile].append(cookie);
466     }
467     if (types & ChangeScreenSettings) {
468         // Check if we have to notify
469         kDebug() << "Added change screen settings";
470         if (m_typesToCookie[ChangeScreenSettings].isEmpty()) {
471             notify = true;
472         }
473         m_typesToCookie[ChangeScreenSettings].append(cookie);
474     }
475     if (types & InterruptSession) {
476         // Check if we have to notify
477         kDebug() << "Added interrupt session";
478         if (m_typesToCookie[InterruptSession].isEmpty()) {
479             notify = true;
480         }
481         m_typesToCookie[InterruptSession].append(cookie);
482     }
483
484     if (notify) {
485         // Emit the signal - inhibition has changed
486         emit unavailablePoliciesChanged(unavailablePolicies());
487     }
488 }
489
490 void PolicyAgent::ReleaseInhibition(uint cookie)
491 {
492     kDebug() << "Released inhibition with cookie " << cookie;
493     m_cookieToAppName.remove(cookie);
494     if (m_cookieToBusService.contains(cookie)) {
495         if (!m_busWatcher.isNull()) {
496             m_busWatcher.data()->removeWatchedService(m_cookieToBusService[cookie]);
497         }
498         m_cookieToBusService.remove(cookie);
499     }
500
501     // Look through all of the inhibition types
502     bool notify = false;
503     if (m_typesToCookie[ChangeProfile].contains(cookie)) {
504         m_typesToCookie[ChangeProfile].removeOne(cookie);
505         // Check if we have to notify
506         if (m_typesToCookie[ChangeProfile].isEmpty()) {
507             notify = true;
508         }
509     }
510     if (m_typesToCookie[ChangeScreenSettings].contains(cookie)) {
511         m_typesToCookie[ChangeScreenSettings].removeOne(cookie);
512         // Check if we have to notify
513         if (m_typesToCookie[ChangeScreenSettings].isEmpty()) {
514             notify = true;
515         }
516     }
517     if (m_typesToCookie[InterruptSession].contains(cookie)) {
518         m_typesToCookie[InterruptSession].removeOne(cookie);
519         // Check if we have to notify
520         if (m_typesToCookie[InterruptSession].isEmpty()) {
521             notify = true;
522         }
523     }
524
525     if (notify) {
526         // Emit the signal - inhibition has changed
527         emit unavailablePoliciesChanged(unavailablePolicies());
528     }
529 }
530
531 void PolicyAgent::releaseAllInhibitions()
532 {
533     QList< uint > allCookies = m_cookieToAppName.keys();
534     foreach (uint cookie, allCookies) {
535         ReleaseInhibition(cookie);
536     }
537 }
538
539 }
540
541 #include "powerdevilpolicyagent.moc"