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 // Glib
34 #include <glib.h>
35 
36 #define LOGIN1_SERVICE QStringLiteral("org.freedesktop.login1")
37 #define LOGIN1_PATH QStringLiteral("/org/freedesktop/login1")
38 #define LOGIN1_IFACE QStringLiteral("org.freedesktop.login1.Manager")
39 #define LOGIN1_SESSION_IFACE QStringLiteral("org.freedesktop.login1.Session")
40 
41 #define ACTIVE_KEY QStringLiteral("Active")
42 #define IDLE_SINCE_KEY QStringLiteral("IdleSinceHint")
43 
44 class DBusUnitySessionServicePrivate: public QObject
45 {
46  Q_OBJECT
47 public:
48  QString logindSessionPath;
49  bool isSessionActive = true;
50  QElapsedTimer screensaverActiveTimer;
51  QDBusUnixFileDescriptor m_systemdInhibitFd;
52 
53  DBusUnitySessionServicePrivate(): QObject() {
54  init();
55  checkActive();
56  }
57 
58  void init()
59  {
60  // get our logind session path
61  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
62  LOGIN1_PATH,
63  LOGIN1_IFACE,
64  QStringLiteral("GetSessionByPID"));
65  msg << (quint32) getpid();
66 
67  QDBusReply<QDBusObjectPath> reply = QDBusConnection::systemBus().call(msg);
68  if (reply.isValid()) {
69  logindSessionPath = reply.value().path();
70 
71  // start watching the Active property
72  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, logindSessionPath, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"),
73  this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
74 
75  setupSystemdInhibition();
76 
77  // re-enable the inhibition upon resume from sleep
78  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("PrepareForSleep"),
79  this, SLOT(onResuming(bool)));
80  } else {
81  qWarning() << "Failed to get logind session path" << reply.error().message();
82  }
83  }
84 
85  void setupSystemdInhibition()
86  {
87  if (m_systemdInhibitFd.isValid())
88  return;
89 
90  // inhibit systemd handling of power/sleep/hibernate buttons
91  // http://www.freedesktop.org/wiki/Software/systemd/inhibit
92 
93  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("Inhibit"));
94  msg << "handle-power-key:handle-suspend-key:handle-hibernate-key"; // what
95  msg << "Unity"; // who
96  msg << "Unity8 handles power events"; // why
97  msg << "block"; // mode
98 
99  QDBusPendingCall pendingCall = QDBusConnection::systemBus().asyncCall(msg);
100  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
101  connect(watcher, &QDBusPendingCallWatcher::finished,
102  this, [this](QDBusPendingCallWatcher* watcher) {
103  QDBusPendingReply<QDBusUnixFileDescriptor> reply = *watcher;
104  watcher->deleteLater();
105  if (reply.isError()) {
106  qWarning() << "Failed to inhibit systemd powersave handling" << reply.error().message();
107  return;
108  }
109 
110  m_systemdInhibitFd = reply.value();
111  });
112  }
113 
114  bool checkLogin1Call(const QString &method) const
115  {
116  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, method);
117  QDBusReply<QString> reply = QDBusConnection::systemBus().call(msg);
118  return reply.isValid() && (reply == QStringLiteral("yes") || reply == QStringLiteral("challenge"));
119  }
120 
121  void makeLogin1Call(const QString &method, const QVariantList &args)
122  {
123  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
124  LOGIN1_PATH,
125  LOGIN1_IFACE,
126  method);
127  msg.setArguments(args);
128  QDBusConnection::systemBus().asyncCall(msg);
129  }
130 
131  void setActive(bool active)
132  {
133  isSessionActive = active;
134 
135  Q_EMIT screensaverActiveChanged(!isSessionActive);
136 
137  if (isSessionActive) {
138  screensaverActiveTimer.invalidate();
139  setIdleHint(false);
140  } else {
141  screensaverActiveTimer.start();
142  setIdleHint(true);
143  }
144  }
145 
146  void checkActive()
147  {
148  if (logindSessionPath.isEmpty()) {
149  qWarning() << "Invalid session path";
150  return;
151  }
152 
153  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
154  logindSessionPath,
155  QStringLiteral("org.freedesktop.DBus.Properties"),
156  QStringLiteral("Get"));
157  msg << LOGIN1_SESSION_IFACE;
158  msg << ACTIVE_KEY;
159 
160  QDBusPendingCall pendingCall = QDBusConnection::systemBus().asyncCall(msg);
161  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
162  connect(watcher, &QDBusPendingCallWatcher::finished,
163  this, [this](QDBusPendingCallWatcher* watcher) {
164 
165  QDBusPendingReply<QVariant> reply = *watcher;
166  watcher->deleteLater();
167  if (reply.isError()) {
168  qWarning() << "Failed to get Active property" << reply.error().message();
169  return;
170  }
171 
172  setActive(reply.value().toBool());
173  });
174  }
175 
176  quint32 screensaverActiveTime() const
177  {
178  if (!isSessionActive && screensaverActiveTimer.isValid()) {
179  return screensaverActiveTimer.elapsed() / 1000;
180  }
181 
182  return 0;
183  }
184 
185  quint64 idleSinceUSecTimestamp() const
186  {
187  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
188  logindSessionPath,
189  QStringLiteral("org.freedesktop.DBus.Properties"),
190  QStringLiteral("Get"));
191  msg << LOGIN1_SESSION_IFACE;
192  msg << IDLE_SINCE_KEY;
193 
194  QDBusReply<QVariant> reply = QDBusConnection::systemBus().call(msg);
195  if (reply.isValid()) {
196  return reply.value().value<quint64>();
197  } else {
198  qWarning() << "Failed to get IdleSinceHint property" << reply.error().message();
199  }
200 
201  return 0;
202  }
203 
204  void setIdleHint(bool idle)
205  {
206  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
207  logindSessionPath,
208  LOGIN1_SESSION_IFACE,
209  QStringLiteral("SetIdleHint"));
210  msg << idle;
211  QDBusConnection::systemBus().asyncCall(msg);
212  }
213 
214 private Q_SLOTS:
215  void onPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
216  {
217  Q_UNUSED(iface)
218 
219  if (changedProps.contains(ACTIVE_KEY)) {
220  setActive(changedProps.value(ACTIVE_KEY).toBool());
221  } else if (invalidatedProps.contains(ACTIVE_KEY)) {
222  checkActive();
223  }
224  }
225 
226  void onResuming(bool active)
227  {
228  if (!active) {
229  setupSystemdInhibition();
230  } else {
231  Q_EMIT prepareForSleep();
232  }
233  }
234 
235 Q_SIGNALS:
236  void screensaverActiveChanged(bool active);
237  void prepareForSleep();
238 };
239 
240 Q_GLOBAL_STATIC(DBusUnitySessionServicePrivate, d)
241 
243  : UnityDBusObject(QStringLiteral("/com/canonical/Unity/Session"), QStringLiteral("com.canonical.Unity"))
244 {
245  if (!d->logindSessionPath.isEmpty()) {
246  // connect our Lock() slot to the logind's session Lock() signal
247  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Lock"), this, SLOT(Lock()));
248  // ... and our Unlocked() signal to the logind's session Unlock() signal
249  // (lightdm handles the unlocking by calling logind's Unlock method which in turn emits this signal we connect to)
250  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Unlock"), this, SIGNAL(Unlocked()));
251  connect(d, &DBusUnitySessionServicePrivate::prepareForSleep, this, &DBusUnitySessionService::PromptLock);
252  } else {
253  qWarning() << "Failed to connect to logind's session Lock/Unlock signals";
254  }
255 }
256 
258 {
259  // TODO ask the apps to quit and then emit the signal
260  Q_EMIT LogoutReady();
261  Q_EMIT logoutReady();
262 }
263 
265 {
266  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.ubuntu.Upstart"),
267  QStringLiteral("/com/ubuntu/Upstart"),
268  QStringLiteral("com.ubuntu.Upstart0_6"),
269  QStringLiteral("EndSession"));
270  QDBusConnection::sessionBus().asyncCall(msg);
271 }
272 
274 {
275  return d->checkLogin1Call(QStringLiteral("CanHibernate"));
276 }
277 
279 {
280  return d->checkLogin1Call(QStringLiteral("CanSuspend"));
281 }
282 
284 {
285  return d->checkLogin1Call(QStringLiteral("CanHybridSleep"));
286 }
287 
289 {
290  return d->checkLogin1Call(QStringLiteral("CanReboot"));
291 }
292 
294 {
295  return d->checkLogin1Call(QStringLiteral("CanPowerOff"));
296 }
297 
299 {
300  return true; // FIXME
301 }
302 
304 {
305  return QString::fromUtf8(g_get_user_name());
306 }
307 
309 {
310  struct passwd *p = getpwuid(geteuid());
311  if (p) {
312  const QString gecos = QString::fromLocal8Bit(p->pw_gecos);
313  if (!gecos.isEmpty()) {
314  const QStringList splitGecos = gecos.split(QLatin1Char(','));
315  return splitGecos.first();
316  }
317  }
318 
319  return QString();
320 }
321 
323 {
324  char hostName[512];
325  if (gethostname(hostName, sizeof(hostName)) == -1) {
326  qWarning() << "Could not determine local hostname";
327  return QString();
328  }
329  hostName[sizeof(hostName) - 1] = '\0';
330  return QString::fromLocal8Bit(hostName);
331 }
332 
334 {
335  Q_EMIT LockRequested();
336  Q_EMIT lockRequested();
337 }
338 
340 {
341  // signal u8 to show the lockscreen/greeter
342  PromptLock();
343 
344  // lock the session using the org.freedesktop.DisplayManager system DBUS service
345  const QString sessionPath = QString::fromLocal8Bit(qgetenv("XDG_SESSION_PATH"));
346  QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DisplayManager"),
347  sessionPath,
348  QStringLiteral("org.freedesktop.DisplayManager.Session"),
349  QStringLiteral("Lock"));
350 
351  QDBusPendingCall pendingCall = QDBusConnection::systemBus().asyncCall(msg);
352  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
353  connect(watcher, &QDBusPendingCallWatcher::finished,
354  this, [this](QDBusPendingCallWatcher* watcher) {
355 
356  QDBusPendingReply<void> reply = *watcher;
357  watcher->deleteLater();
358  if (reply.isError()) {
359  qWarning() << "Lock call failed" << reply.error().message();
360  return;
361  }
362 
363  // emit Locked when the call succeeds
364  Q_EMIT Locked();
365  });
366 }
367 
369 {
370  return !d->isSessionActive;
371 }
372 
374 {
375  Q_EMIT LogoutRequested(false);
376  Q_EMIT logoutRequested(false);
377 }
378 
380 {
381  d->makeLogin1Call(QStringLiteral("Reboot"), {false});
382 }
383 
385 {
386  Q_EMIT RebootRequested(false);
387  Q_EMIT rebootRequested(false);
388 }
389 
391 {
392  d->makeLogin1Call(QStringLiteral("PowerOff"), {false});
393 }
394 
396 {
397  PromptLock();
398  d->makeLogin1Call(QStringLiteral("Suspend"), {false});
399 }
400 
402 {
403  PromptLock();
404  d->makeLogin1Call(QStringLiteral("Hibernate"), {false});
405 }
406 
408 {
409  PromptLock();
410  d->makeLogin1Call(QStringLiteral("HybridSleep"), {false});
411 }
412 
414 {
415  Q_EMIT ShutdownRequested(false);
416  Q_EMIT shutdownRequested(false);
417 }
418 
419 enum class Action : unsigned
420 {
421  LOGOUT = 0,
422  SHUTDOWN,
423  REBOOT,
424  NONE
425 };
426 
427 
428 void performAsyncUnityCall(const QString &method)
429 {
430  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.Unity"),
431  QStringLiteral("/com/canonical/Unity/Session"),
432  QStringLiteral("com.canonical.Unity.Session"),
433  method);
434  QDBusConnection::sessionBus().asyncCall(msg);
435 }
436 
437 
438 DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
439  : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager/EndSessionDialog"), QStringLiteral("com.canonical.Unity"))
440 {
441 }
442 
443 void DBusGnomeSessionManagerWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
444 {
445  Q_UNUSED(arg_1);
446  Q_UNUSED(max_wait);
447  Q_UNUSED(inhibitors);
448 
449  switch (static_cast<Action>(type))
450  {
451  case Action::LOGOUT:
452  performAsyncUnityCall(QStringLiteral("RequestLogout"));
453  break;
454 
455  case Action::REBOOT:
456  performAsyncUnityCall(QStringLiteral("RequestReboot"));
457  break;
458 
459  case Action::SHUTDOWN:
460  performAsyncUnityCall(QStringLiteral("RequestShutdown"));
461  break;
462 
463  default:
464  break;
465  }
466 }
467 
468 
469 DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
470  : UnityDBusObject(QStringLiteral("/org/gnome/ScreenSaver"), QStringLiteral("org.gnome.ScreenSaver"))
471 {
472  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
473 }
474 
475 bool DBusGnomeScreensaverWrapper::GetActive() const
476 {
477  return !d->isSessionActive; // return whether the session is not active
478 }
479 
480 void DBusGnomeScreensaverWrapper::SetActive(bool lock)
481 {
482  if (lock) {
483  Lock();
484  }
485 }
486 
487 void DBusGnomeScreensaverWrapper::Lock()
488 {
489  performAsyncUnityCall(QStringLiteral("Lock"));
490 }
491 
492 quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
493 {
494  return d->screensaverActiveTime();
495 }
496 
497 void DBusGnomeScreensaverWrapper::SimulateUserActivity()
498 {
499  d->setIdleHint(false);
500 }
501 
502 
503 DBusScreensaverWrapper::DBusScreensaverWrapper()
504  : UnityDBusObject(QStringLiteral("/org/freedesktop/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"))
505 {
506  QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this, QDBusConnection::ExportScriptableContents); // compat path, also register here
507  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
508 }
509 
510 bool DBusScreensaverWrapper::GetActive() const
511 {
512  return !d->isSessionActive; // return whether the session is not active
513 }
514 
515 bool DBusScreensaverWrapper::SetActive(bool lock)
516 {
517  if (lock) {
518  Lock();
519  return true;
520  }
521  return false;
522 }
523 
524 void DBusScreensaverWrapper::Lock()
525 {
526  performAsyncUnityCall(QStringLiteral("Lock"));
527 }
528 
529 quint32 DBusScreensaverWrapper::GetActiveTime() const
530 {
531  return d->screensaverActiveTime();
532 }
533 
534 quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
535 {
536  return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
537 }
538 
539 void DBusScreensaverWrapper::SimulateUserActivity()
540 {
541  d->setIdleHint(false);
542 }
543 
544 #include "dbusunitysessionservice.moc"
Q_SCRIPTABLE bool CanShutdown() const
Q_SCRIPTABLE QString RealName() const
Q_SCRIPTABLE bool CanLock() const
Q_SCRIPTABLE bool CanHibernate() const
Q_SCRIPTABLE QString UserName() const
Q_SCRIPTABLE bool CanSuspend() const
Q_SCRIPTABLE bool IsLocked() const
Q_SCRIPTABLE bool CanReboot() const
Q_SCRIPTABLE QString HostName() const
Q_SCRIPTABLE bool CanHybridSleep() const