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