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  // lock the session using the org.freedesktop.DisplayManager system DBUS service
340  const QString sessionPath = QString::fromLocal8Bit(qgetenv("XDG_SESSION_PATH"));
341  QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DisplayManager"),
342  sessionPath,
343  QStringLiteral("org.freedesktop.DisplayManager.Session"),
344  QStringLiteral("Lock"));
345 
346  QDBusPendingCall pendingCall = QDBusConnection::systemBus().asyncCall(msg);
347  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
348  connect(watcher, &QDBusPendingCallWatcher::finished,
349  this, [this](QDBusPendingCallWatcher* watcher) {
350 
351  QDBusPendingReply<QVariant> reply = *watcher;
352  watcher->deleteLater();
353  if (reply.isError()) {
354  qWarning() << "Lock call failed" << reply.error().message();
355  return;
356  }
357 
358  // emit Locked when the call succeeds
359  Q_EMIT Locked();
360  });
361 }
362 
364 {
365  return !d->isSessionActive;
366 }
367 
369 {
370  Q_EMIT LogoutRequested(false);
371  Q_EMIT logoutRequested(false);
372 }
373 
375 {
376  d->makeLogin1Call(QStringLiteral("Reboot"), {false});
377 }
378 
380 {
381  Q_EMIT RebootRequested(false);
382  Q_EMIT rebootRequested(false);
383 }
384 
386 {
387  d->makeLogin1Call(QStringLiteral("PowerOff"), {false});
388 }
389 
391 {
392  d->makeLogin1Call(QStringLiteral("Suspend"), {false});
393 }
394 
396 {
397  d->makeLogin1Call(QStringLiteral("Hibernate"), {false});
398 }
399 
401 {
402  d->makeLogin1Call(QStringLiteral("HybridSleep"), {false});
403 }
404 
406 {
407  Q_EMIT ShutdownRequested(false);
408  Q_EMIT shutdownRequested(false);
409 }
410 
411 enum class Action : unsigned
412 {
413  LOGOUT = 0,
414  SHUTDOWN,
415  REBOOT,
416  NONE
417 };
418 
419 
420 void performAsyncUnityCall(const QString &method)
421 {
422  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.Unity"),
423  QStringLiteral("/com/canonical/Unity/Session"),
424  QStringLiteral("com.canonical.Unity.Session"),
425  method);
426  QDBusConnection::sessionBus().asyncCall(msg);
427 }
428 
429 
430 DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
431  : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager/EndSessionDialog"), QStringLiteral("com.canonical.Unity"))
432 {
433 }
434 
435 void DBusGnomeSessionManagerWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
436 {
437  Q_UNUSED(arg_1);
438  Q_UNUSED(max_wait);
439  Q_UNUSED(inhibitors);
440 
441  switch (static_cast<Action>(type))
442  {
443  case Action::LOGOUT:
444  performAsyncUnityCall(QStringLiteral("RequestLogout"));
445  break;
446 
447  case Action::REBOOT:
448  performAsyncUnityCall(QStringLiteral("RequestReboot"));
449  break;
450 
451  case Action::SHUTDOWN:
452  performAsyncUnityCall(QStringLiteral("RequestShutdown"));
453  break;
454 
455  default:
456  break;
457  }
458 }
459 
460 
461 DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
462  : UnityDBusObject(QStringLiteral("/org/gnome/ScreenSaver"), QStringLiteral("org.gnome.ScreenSaver"))
463 {
464  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
465 }
466 
467 bool DBusGnomeScreensaverWrapper::GetActive() const
468 {
469  return !d->isSessionActive; // return whether the session is not active
470 }
471 
472 void DBusGnomeScreensaverWrapper::SetActive(bool lock)
473 {
474  if (lock) {
475  Lock();
476  }
477 }
478 
479 void DBusGnomeScreensaverWrapper::Lock()
480 {
481  performAsyncUnityCall(QStringLiteral("Lock"));
482 }
483 
484 quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
485 {
486  return d->screensaverActiveTime();
487 }
488 
489 void DBusGnomeScreensaverWrapper::SimulateUserActivity()
490 {
491  d->setIdleHint(false);
492 }
493 
494 
495 DBusScreensaverWrapper::DBusScreensaverWrapper()
496  : UnityDBusObject(QStringLiteral("/org/freedesktop/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"))
497 {
498  QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this, QDBusConnection::ExportScriptableContents); // compat path, also register here
499  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
500 }
501 
502 bool DBusScreensaverWrapper::GetActive() const
503 {
504  return !d->isSessionActive; // return whether the session is not active
505 }
506 
507 bool DBusScreensaverWrapper::SetActive(bool lock)
508 {
509  if (lock) {
510  Lock();
511  return true;
512  }
513  return false;
514 }
515 
516 void DBusScreensaverWrapper::Lock()
517 {
518  performAsyncUnityCall(QStringLiteral("Lock"));
519 }
520 
521 quint32 DBusScreensaverWrapper::GetActiveTime() const
522 {
523  return d->screensaverActiveTime();
524 }
525 
526 quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
527 {
528  return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
529 }
530 
531 void DBusScreensaverWrapper::SimulateUserActivity()
532 {
533  d->setIdleHint(false);
534 }
535 
536 #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