Unity 8
dbusunitysessionservice.cpp
1 /*
2  * Copyright (C) 2014, 2015 Canonical, Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it under
5  * the terms of the GNU Lesser General Public License version 3, as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10  * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 // local
18 #include "dbusunitysessionservice.h"
19 
20 // system
21 #include <sys/types.h>
22 #include <unistd.h>
23 #include <pwd.h>
24 
25 // Qt
26 #include <QDebug>
27 #include <QDBusPendingCall>
28 #include <QDBusReply>
29 #include <QElapsedTimer>
30 #include <QDateTime>
31 #include <QDBusUnixFileDescriptor>
32 
33 #define LOGIN1_SERVICE QStringLiteral("org.freedesktop.login1")
34 #define LOGIN1_PATH QStringLiteral("/org/freedesktop/login1")
35 #define LOGIN1_IFACE QStringLiteral("org.freedesktop.login1.Manager")
36 #define LOGIN1_SESSION_IFACE QStringLiteral("org.freedesktop.login1.Session")
37 
38 #define ACTIVE_KEY QStringLiteral("Active")
39 #define IDLE_SINCE_KEY QStringLiteral("IdleSinceHint")
40 
41 class DBusUnitySessionServicePrivate: public QObject
42 {
43  Q_OBJECT
44 public:
45  QString logindSessionPath;
46  bool isSessionActive = true;
47  QElapsedTimer screensaverActiveTimer;
48  QDBusUnixFileDescriptor m_systemdInhibitFd;
49 
50  DBusUnitySessionServicePrivate(): QObject() {
51  init();
52  checkActive();
53  }
54 
55  void init()
56  {
57  // get our logind session path
58  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
59  LOGIN1_PATH,
60  LOGIN1_IFACE,
61  QStringLiteral("GetSessionByPID"));
62  msg << (quint32) getpid();
63 
64  QDBusReply<QDBusObjectPath> reply = QDBusConnection::systemBus().asyncCall(msg);
65  if (reply.isValid()) {
66  logindSessionPath = reply.value().path();
67 
68  // start watching the Active property
69  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, logindSessionPath, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"),
70  this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
71 
72  setupSystemdInhibition();
73 
74  // re-enable the inhibition upon resume from sleep
75  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("PrepareForSleep"),
76  this, SLOT(onResuming(bool)));
77  } else {
78  qWarning() << "Failed to get logind session path" << reply.error().message();
79  }
80  }
81 
82  void setupSystemdInhibition()
83  {
84  if (m_systemdInhibitFd.isValid())
85  return;
86 
87  // inhibit systemd handling of power/sleep/lid buttons
88  // http://www.freedesktop.org/wiki/Software/systemd/inhibit
89 
90  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("Inhibit"));
91  msg << "handle-power-key:handle-suspend-key:handle-hibernate-key:handle-lid-switch"; // what
92  msg << "Unity"; // who
93  msg << "Unity8 handles power events"; // why
94  msg << "block"; // mode
95  QDBusReply<QDBusUnixFileDescriptor> desc = QDBusConnection::systemBus().asyncCall(msg);
96 
97  if (desc.isValid()) {
98  m_systemdInhibitFd = desc.value();
99  } else {
100  qWarning() << "failed to inhibit systemd powersave handling";
101  }
102  }
103 
104 
105  bool checkLogin1Call(const QString &method) const
106  {
107  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, method);
108  QDBusReply<QString> reply = QDBusConnection::systemBus().asyncCall(msg);
109  return reply.isValid() && (reply == QStringLiteral("yes") || reply == QStringLiteral("challenge"));
110  }
111 
112  void makeLogin1Call(const QString &method, const QVariantList &args)
113  {
114  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
115  LOGIN1_PATH,
116  LOGIN1_IFACE,
117  method);
118  msg.setArguments(args);
119  QDBusConnection::systemBus().asyncCall(msg);
120  }
121 
122  void checkActive()
123  {
124  if (logindSessionPath.isEmpty()) {
125  qWarning() << "Invalid session path";
126  return;
127  }
128 
129  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
130  logindSessionPath,
131  QStringLiteral("org.freedesktop.DBus.Properties"),
132  QStringLiteral("Get"));
133  msg << LOGIN1_SESSION_IFACE;
134  msg << ACTIVE_KEY;
135 
136  QDBusReply<QVariant> reply = QDBusConnection::systemBus().asyncCall(msg);
137  if (reply.isValid()) {
138  isSessionActive = reply.value().toBool();
139  } else {
140  qWarning() << "Failed to get Active property" << reply.error().message();
141  }
142  }
143 
144  quint32 screensaverActiveTime() const
145  {
146  if (!isSessionActive && screensaverActiveTimer.isValid()) {
147  return screensaverActiveTimer.elapsed() / 1000;
148  }
149 
150  return 0;
151  }
152 
153  quint64 idleSinceUSecTimestamp() const
154  {
155  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
156  logindSessionPath,
157  QStringLiteral("org.freedesktop.DBus.Properties"),
158  QStringLiteral("Get"));
159  msg << LOGIN1_SESSION_IFACE;
160  msg << IDLE_SINCE_KEY;
161 
162  QDBusReply<QVariant> reply = QDBusConnection::systemBus().asyncCall(msg);
163  if (reply.isValid()) {
164  return reply.value().value<quint64>();
165  } else {
166  qWarning() << "Failed to get IdleSinceHint property" << reply.error().message();
167  }
168 
169  return 0;
170  }
171 
172  void setIdleHint(bool idle)
173  {
174  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
175  logindSessionPath,
176  LOGIN1_SESSION_IFACE,
177  QStringLiteral("SetIdleHint"));
178  msg << idle;
179  QDBusConnection::systemBus().asyncCall(msg);
180  }
181 
182 private Q_SLOTS:
183  void onPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
184  {
185  Q_UNUSED(iface)
186 
187  if (changedProps.contains(ACTIVE_KEY) || invalidatedProps.contains(ACTIVE_KEY)) {
188  if (changedProps.value(ACTIVE_KEY).isValid()) {
189  isSessionActive = changedProps.value(ACTIVE_KEY).toBool();
190  } else {
191  checkActive();
192  }
193 
194  Q_EMIT screensaverActiveChanged(!isSessionActive);
195 
196  if (isSessionActive) {
197  screensaverActiveTimer.invalidate();
198  setIdleHint(false);
199  } else {
200  screensaverActiveTimer.start();
201  setIdleHint(true);
202  }
203  }
204  }
205 
206  void onResuming(bool active)
207  {
208  if (!active) {
209  setupSystemdInhibition();
210  }
211  }
212 
213 Q_SIGNALS:
214  void screensaverActiveChanged(bool active);
215 };
216 
217 Q_GLOBAL_STATIC(DBusUnitySessionServicePrivate, d)
218 
220  : UnityDBusObject(QStringLiteral("/com/canonical/Unity/Session"), QStringLiteral("com.canonical.Unity"))
221 {
222  if (!d->logindSessionPath.isEmpty()) {
223  // connect our Lock() slot to the logind's session Lock() signal
224  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Lock"), this, SLOT(Lock()));
225  // ... and our Unlocked() signal to the logind's session Unlock() signal
226  // (lightdm handles the unlocking by calling logind's Unlock method which in turn emits this signal we connect to)
227  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Unlock"), this, SIGNAL(Unlocked()));
228  } else {
229  qWarning() << "Failed to connect to logind's session Lock/Unlock signals";
230  }
231 }
232 
234 {
235  // TODO ask the apps to quit and then emit the signal
236  Q_EMIT LogoutReady();
237  Q_EMIT logoutReady();
238 }
239 
241 {
242  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.ubuntu.Upstart"),
243  QStringLiteral("/com/ubuntu/Upstart"),
244  QStringLiteral("com.ubuntu.Upstart0_6"),
245  QStringLiteral("EndSession"));
246  QDBusConnection::sessionBus().asyncCall(msg);
247 }
248 
250 {
251  return d->checkLogin1Call(QStringLiteral("CanHibernate"));
252 }
253 
255 {
256  return d->checkLogin1Call(QStringLiteral("CanSuspend"));
257 }
258 
260 {
261  return d->checkLogin1Call(QStringLiteral("CanHybridSleep"));
262 }
263 
265 {
266  return d->checkLogin1Call(QStringLiteral("CanReboot"));
267 }
268 
270 {
271  return d->checkLogin1Call(QStringLiteral("CanPowerOff"));
272 }
273 
275 {
276  return true; // FIXME
277 }
278 
280 {
281  struct passwd *p = getpwuid(geteuid());
282  if (p) {
283  return QString::fromUtf8(p->pw_name);
284  }
285 
286  return QString();
287 }
288 
290 {
291  struct passwd *p = getpwuid(geteuid());
292  if (p) {
293  const QString gecos = QString::fromLocal8Bit(p->pw_gecos);
294  if (!gecos.isEmpty()) {
295  const QStringList splitGecos = gecos.split(QLatin1Char(','));
296  return splitGecos.first();
297  }
298  }
299 
300  return QString();
301 }
302 
304 {
305  char hostName[512];
306  if (gethostname(hostName, sizeof(hostName)) == -1) {
307  qWarning() << "Could not determine local hostname";
308  return QString();
309  }
310  hostName[sizeof(hostName) - 1] = '\0';
311  return QString::fromLocal8Bit(hostName);
312 }
313 
315 {
316  Q_EMIT LockRequested();
317  Q_EMIT lockRequested();
318 }
319 
321 {
322  // lock the session using the org.freedesktop.DisplayManager system DBUS service
323  const QString sessionPath = QString::fromLocal8Bit(qgetenv("XDG_SESSION_PATH"));
324  QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DisplayManager"),
325  sessionPath,
326  QStringLiteral("org.freedesktop.DisplayManager.Session"),
327  QStringLiteral("Lock"));
328  QDBusReply<void> reply = QDBusConnection::systemBus().asyncCall(msg);
329  if (!reply.isValid()) {
330  qWarning() << "Lock call failed" << reply.error().message();
331  } else {
332  // emit Locked when the call succeeds
333  Q_EMIT Locked();
334  }
335 }
336 
338 {
339  return !d->isSessionActive;
340 }
341 
343 {
344  Q_EMIT LogoutRequested(false);
345  Q_EMIT logoutRequested(false);
346 }
347 
349 {
350  d->makeLogin1Call(QStringLiteral("Reboot"), {false});
351 }
352 
354 {
355  Q_EMIT RebootRequested(false);
356  Q_EMIT rebootRequested(false);
357 }
358 
360 {
361  d->makeLogin1Call(QStringLiteral("PowerOff"), {false});
362 }
363 
365 {
366  d->makeLogin1Call(QStringLiteral("Suspend"), {false});
367 }
368 
370 {
371  d->makeLogin1Call(QStringLiteral("Hibernate"), {false});
372 }
373 
375 {
376  d->makeLogin1Call(QStringLiteral("HybridSleep"), {false});
377 }
378 
380 {
381  Q_EMIT ShutdownRequested(false);
382  Q_EMIT shutdownRequested(false);
383 }
384 
385 enum class Action : unsigned
386 {
387  LOGOUT = 0,
388  SHUTDOWN,
389  REBOOT,
390  NONE
391 };
392 
393 
394 void performAsyncUnityCall(const QString &method)
395 {
396  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.Unity"),
397  QStringLiteral("/com/canonical/Unity/Session"),
398  QStringLiteral("com.canonical.Unity.Session"),
399  method);
400  QDBusConnection::sessionBus().asyncCall(msg);
401 }
402 
403 
404 DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
405  : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager/EndSessionDialog"), QStringLiteral("com.canonical.Unity"))
406 {
407 }
408 
409 void DBusGnomeSessionManagerWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
410 {
411  Q_UNUSED(arg_1);
412  Q_UNUSED(max_wait);
413  Q_UNUSED(inhibitors);
414 
415  switch (static_cast<Action>(type))
416  {
417  case Action::LOGOUT:
418  performAsyncUnityCall(QStringLiteral("RequestLogout"));
419  break;
420 
421  case Action::REBOOT:
422  performAsyncUnityCall(QStringLiteral("RequestReboot"));
423  break;
424 
425  case Action::SHUTDOWN:
426  performAsyncUnityCall(QStringLiteral("RequestShutdown"));
427  break;
428 
429  default:
430  break;
431  }
432 }
433 
434 
435 DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
436  : UnityDBusObject(QStringLiteral("/org/gnome/ScreenSaver"), QStringLiteral("org.gnome.ScreenSaver"))
437 {
438  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
439 }
440 
441 bool DBusGnomeScreensaverWrapper::GetActive() const
442 {
443  return !d->isSessionActive; // return whether the session is not active
444 }
445 
446 void DBusGnomeScreensaverWrapper::SetActive(bool lock)
447 {
448  if (lock) {
449  Lock();
450  }
451 }
452 
453 void DBusGnomeScreensaverWrapper::Lock()
454 {
455  performAsyncUnityCall(QStringLiteral("Lock"));
456 }
457 
458 quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
459 {
460  return d->screensaverActiveTime();
461 }
462 
463 void DBusGnomeScreensaverWrapper::SimulateUserActivity()
464 {
465  d->setIdleHint(false);
466 }
467 
468 
469 DBusScreensaverWrapper::DBusScreensaverWrapper()
470  : UnityDBusObject(QStringLiteral("/org/freedesktop/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"))
471 {
472  QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this, QDBusConnection::ExportScriptableContents); // compat path, also register here
473  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
474 }
475 
476 bool DBusScreensaverWrapper::GetActive() const
477 {
478  return !d->isSessionActive; // return whether the session is not active
479 }
480 
481 bool DBusScreensaverWrapper::SetActive(bool lock)
482 {
483  if (lock) {
484  Lock();
485  return true;
486  }
487  return false;
488 }
489 
490 void DBusScreensaverWrapper::Lock()
491 {
492  performAsyncUnityCall(QStringLiteral("Lock"));
493 }
494 
495 quint32 DBusScreensaverWrapper::GetActiveTime() const
496 {
497  return d->screensaverActiveTime();
498 }
499 
500 quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
501 {
502  return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
503 }
504 
505 void DBusScreensaverWrapper::SimulateUserActivity()
506 {
507  d->setIdleHint(false);
508 }
509 
510 #include "dbusunitysessionservice.moc"
Q_SCRIPTABLE void LogoutReady()
Q_SCRIPTABLE bool CanShutdown() const
Q_SCRIPTABLE void RebootRequested(bool have_inhibitors)
Q_SCRIPTABLE QString RealName() const
Q_SCRIPTABLE bool CanLock() const
Q_SCRIPTABLE bool CanHibernate() const
Q_SCRIPTABLE void ShutdownRequested(bool have_inhibitors)
Q_SCRIPTABLE void Locked()
Q_SCRIPTABLE QString UserName() const
Q_SCRIPTABLE bool CanSuspend() const
Q_SCRIPTABLE bool IsLocked() const
Q_SCRIPTABLE void LogoutRequested(bool have_inhibitors)
Q_SCRIPTABLE bool CanReboot() const
Q_SCRIPTABLE void LockRequested()
Q_SCRIPTABLE QString HostName() const
Q_SCRIPTABLE bool CanHybridSleep() const