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 <grp.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 #include <pwd.h>
25 
26 // Qt
27 #include <QDebug>
28 #include <QDBusPendingCall>
29 #include <QDBusReply>
30 #include <QElapsedTimer>
31 #include <QDateTime>
32 #include <QDBusUnixFileDescriptor>
33 
34 // Glib
35 #include <glib.h>
36 
37 #define LOGIN1_SERVICE QStringLiteral("org.freedesktop.login1")
38 #define LOGIN1_PATH QStringLiteral("/org/freedesktop/login1")
39 #define LOGIN1_IFACE QStringLiteral("org.freedesktop.login1.Manager")
40 #define LOGIN1_SESSION_IFACE QStringLiteral("org.freedesktop.login1.Session")
41 
42 #define ACTIVE_KEY QStringLiteral("Active")
43 #define IDLE_SINCE_KEY QStringLiteral("IdleSinceHint")
44 
45 class DBusUnitySessionServicePrivate: public QObject
46 {
47  Q_OBJECT
48 public:
49  QString logindSessionPath;
50  bool isSessionActive = true;
51  QElapsedTimer screensaverActiveTimer;
52  QDBusUnixFileDescriptor m_systemdInhibitFd;
53 
54  DBusUnitySessionServicePrivate(): QObject() {
55  init();
56  checkActive();
57  }
58 
59  void init()
60  {
61  // get our logind session path
62  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
63  LOGIN1_PATH,
64  LOGIN1_IFACE,
65  QStringLiteral("GetSessionByPID"));
66  msg << (quint32) getpid();
67 
68  QDBusReply<QDBusObjectPath> reply = QDBusConnection::SM_BUSNAME().call(msg);
69  if (reply.isValid()) {
70  logindSessionPath = reply.value().path();
71 
72  // start watching the Active property
73  QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, logindSessionPath, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"),
74  this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
75 
76  setupSystemdInhibition();
77 
78  // re-enable the inhibition upon resume from sleep
79  QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("PrepareForSleep"),
80  this, SLOT(onResuming(bool)));
81  } else {
82  qWarning() << "Failed to get logind session path" << reply.error().message();
83  }
84  }
85 
86  void setupSystemdInhibition()
87  {
88  if (m_systemdInhibitFd.isValid())
89  return;
90 
91  // inhibit systemd handling of power/sleep/hibernate buttons
92  // http://www.freedesktop.org/wiki/Software/systemd/inhibit
93 
94  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("Inhibit"));
95  msg << "handle-power-key:handle-suspend-key:handle-hibernate-key"; // what
96  msg << "Unity"; // who
97  msg << "Unity8 handles power events"; // why
98  msg << "block"; // mode
99 
100  QDBusPendingCall pendingCall = QDBusConnection::SM_BUSNAME().asyncCall(msg);
101  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
102  connect(watcher, &QDBusPendingCallWatcher::finished,
103  this, [this](QDBusPendingCallWatcher* watcher) {
104  QDBusPendingReply<QDBusUnixFileDescriptor> reply = *watcher;
105  watcher->deleteLater();
106  if (reply.isError()) {
107  qWarning() << "Failed to inhibit systemd powersave handling" << reply.error().message();
108  return;
109  }
110 
111  m_systemdInhibitFd = reply.value();
112  });
113  }
114 
115  bool checkLogin1Call(const QString &method) const
116  {
117  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, method);
118  QDBusReply<QString> reply = QDBusConnection::SM_BUSNAME().call(msg);
119  return reply.isValid() && (reply == QStringLiteral("yes") || reply == QStringLiteral("challenge"));
120  }
121 
122  void makeLogin1Call(const QString &method, const QVariantList &args)
123  {
124  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
125  LOGIN1_PATH,
126  LOGIN1_IFACE,
127  method);
128  msg.setArguments(args);
129  QDBusConnection::SM_BUSNAME().asyncCall(msg);
130  }
131 
132  void setActive(bool active)
133  {
134  isSessionActive = active;
135 
136  Q_EMIT screensaverActiveChanged(!isSessionActive);
137 
138  if (isSessionActive) {
139  screensaverActiveTimer.invalidate();
140  setIdleHint(false);
141  } else {
142  screensaverActiveTimer.start();
143  setIdleHint(true);
144  }
145  }
146 
147  void checkActive()
148  {
149  if (logindSessionPath.isEmpty()) {
150  qWarning() << "Invalid session path";
151  return;
152  }
153 
154  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
155  logindSessionPath,
156  QStringLiteral("org.freedesktop.DBus.Properties"),
157  QStringLiteral("Get"));
158  msg << LOGIN1_SESSION_IFACE;
159  msg << ACTIVE_KEY;
160 
161  QDBusPendingCall pendingCall = QDBusConnection::SM_BUSNAME().asyncCall(msg);
162  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
163  connect(watcher, &QDBusPendingCallWatcher::finished,
164  this, [this](QDBusPendingCallWatcher* watcher) {
165 
166  QDBusPendingReply<QVariant> reply = *watcher;
167  watcher->deleteLater();
168  if (reply.isError()) {
169  qWarning() << "Failed to get Active property" << reply.error().message();
170  return;
171  }
172 
173  setActive(reply.value().toBool());
174  });
175  }
176 
177  quint32 screensaverActiveTime() const
178  {
179  if (!isSessionActive && screensaverActiveTimer.isValid()) {
180  return screensaverActiveTimer.elapsed() / 1000;
181  }
182 
183  return 0;
184  }
185 
186  quint64 idleSinceUSecTimestamp() const
187  {
188  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
189  logindSessionPath,
190  QStringLiteral("org.freedesktop.DBus.Properties"),
191  QStringLiteral("Get"));
192  msg << LOGIN1_SESSION_IFACE;
193  msg << IDLE_SINCE_KEY;
194 
195  QDBusReply<QVariant> reply = QDBusConnection::SM_BUSNAME().call(msg);
196  if (reply.isValid()) {
197  return reply.value().value<quint64>();
198  } else {
199  qWarning() << "Failed to get IdleSinceHint property" << reply.error().message();
200  }
201 
202  return 0;
203  }
204 
205  void setIdleHint(bool idle)
206  {
207  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
208  logindSessionPath,
209  LOGIN1_SESSION_IFACE,
210  QStringLiteral("SetIdleHint"));
211  msg << idle;
212  QDBusConnection::SM_BUSNAME().asyncCall(msg);
213  }
214 
215  bool isUserInGroup(const QString &user, const QString &groupName) const
216  {
217  auto group = getgrnam(groupName.toUtf8().data());
218 
219  if (group && group->gr_mem)
220  {
221  for (int i = 0; group->gr_mem[i]; ++i)
222  {
223  if (g_strcmp0(group->gr_mem[i], user.toUtf8().data()) == 0) {
224  return true;
225  }
226  }
227  }
228 
229  return false;
230  }
231 
232 private Q_SLOTS:
233  void onPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
234  {
235  Q_UNUSED(iface)
236 
237  if (changedProps.contains(ACTIVE_KEY)) {
238  setActive(changedProps.value(ACTIVE_KEY).toBool());
239  } else if (invalidatedProps.contains(ACTIVE_KEY)) {
240  checkActive();
241  }
242  }
243 
244  void onResuming(bool active)
245  {
246  if (!active) {
247  setupSystemdInhibition();
248  } else {
249  Q_EMIT prepareForSleep();
250  }
251  }
252 
253 Q_SIGNALS:
254  void screensaverActiveChanged(bool active);
255  void prepareForSleep();
256 };
257 
258 Q_GLOBAL_STATIC(DBusUnitySessionServicePrivate, d)
259 
261  : UnityDBusObject(QStringLiteral("/com/canonical/Unity/Session"), QStringLiteral("com.canonical.Unity"))
262 {
263  if (!d->logindSessionPath.isEmpty()) {
264  // connect our PromptLock() slot to the logind's session Lock() signal
265  QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Lock"), this, SLOT(PromptLock()));
266  // ... and our Unlocked() signal to the logind's session Unlock() signal
267  // (lightdm handles the unlocking by calling logind's Unlock method which in turn emits this signal we connect to)
268  QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Unlock"), this, SLOT(doUnlock()));
269  connect(d, &DBusUnitySessionServicePrivate::prepareForSleep, this, &DBusUnitySessionService::PromptLock);
270  } else {
271  qWarning() << "Failed to connect to logind's session Lock/Unlock signals";
272  }
273 }
274 
276 {
277  // TODO ask the apps to quit and then emit the signal
278  Q_EMIT LogoutReady();
279  Q_EMIT logoutReady();
280 }
281 
283 {
284  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.ubuntu.Upstart"),
285  QStringLiteral("/com/ubuntu/Upstart"),
286  QStringLiteral("com.ubuntu.Upstart0_6"),
287  QStringLiteral("EndSession"));
288  QDBusConnection::sessionBus().asyncCall(msg);
289 }
290 
292 {
293  return d->checkLogin1Call(QStringLiteral("CanHibernate"));
294 }
295 
297 {
298  return d->checkLogin1Call(QStringLiteral("CanSuspend"));
299 }
300 
302 {
303  return d->checkLogin1Call(QStringLiteral("CanHybridSleep"));
304 }
305 
307 {
308  return d->checkLogin1Call(QStringLiteral("CanReboot"));
309 }
310 
312 {
313  return d->checkLogin1Call(QStringLiteral("CanPowerOff"));
314 }
315 
317 {
318  auto user = UserName();
319  if (user.startsWith(QStringLiteral("guest-")) ||
320  d->isUserInGroup(user, QStringLiteral("nopasswdlogin"))) {
321  return false;
322  } else {
323  return true;
324  }
325 }
326 
328 {
329  return QString::fromUtf8(g_get_user_name());
330 }
331 
333 {
334  struct passwd *p = getpwuid(geteuid());
335  if (p) {
336  const QString gecos = QString::fromLocal8Bit(p->pw_gecos);
337  if (!gecos.isEmpty()) {
338  const QStringList splitGecos = gecos.split(QLatin1Char(','));
339  return splitGecos.first();
340  }
341  }
342 
343  return QString();
344 }
345 
347 {
348  char hostName[512];
349  if (gethostname(hostName, sizeof(hostName)) == -1) {
350  qWarning() << "Could not determine local hostname";
351  return QString();
352  }
353  hostName[sizeof(hostName) - 1] = '\0';
354  return QString::fromLocal8Bit(hostName);
355 }
356 
358 {
359  // Prompt as in quick. No locking animation needed. Usually used by
360  // indicator-session in combination with a switch to greeter or other
361  // user session.
362  if (CanLock()) {
363  Q_EMIT LockRequested();
364  Q_EMIT lockRequested();
365  }
366 }
367 
369 {
370  // Normal lock (with animation, as compared to PromptLock above). Usually
371  // used by indicator-session to lock the session in place.
372  //
373  // FIXME: We also -- as a bit of a hack around indicator-session not fully
374  // supporting a phone profile -- switch to greeter here. The unity7 flow is
375  // that the user chooses "Lock/Switch" from the indicator, and then can go
376  // to greeter by selecting "Switch" again from the indicator, which is now
377  // exposed by the desktop_lockscreen profile. But since in unity8, we try
378  // to expose most things all the time, we don't use the separate lockscreen
379  // profile. Instead, we just go directly to the greeter the first time
380  // a user presses "Lock/Switch". This isn't what this DBus call is
381  // supposed to do, but we can live with it for now.
382  //
383  // Here's a bug about indicator-session growing a converged Touch profile:
384  // https://launchpad.net/bugs/1557716
385  //
386  // We only do this here in the animated-lock call because that's the only
387  // time the indicator locks without also asking the display manager to
388  // switch sessions on us. And since we are switching screens, we also
389  // don't bother respecting the animate request, simply doing a PromptLock.
390  PromptLock();
391  switchToGreeter();
392 }
393 
394 void DBusUnitySessionService::switchToGreeter()
395 {
396  // lock the session using the org.freedesktop.DisplayManager system DBUS service
397  const QString sessionPath = QString::fromLocal8Bit(qgetenv("XDG_SESSION_PATH"));
398  QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DisplayManager"),
399  sessionPath,
400  QStringLiteral("org.freedesktop.DisplayManager.Session"),
401  QStringLiteral("Lock"));
402 
403  QDBusPendingCall pendingCall = QDBusConnection::SM_BUSNAME().asyncCall(msg);
404  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
405  connect(watcher, &QDBusPendingCallWatcher::finished,
406  this, [this](QDBusPendingCallWatcher* watcher) {
407 
408  QDBusPendingReply<void> reply = *watcher;
409  watcher->deleteLater();
410  if (reply.isError()) {
411  qWarning() << "Lock call failed" << reply.error().message();
412  return;
413  }
414 
415  // emit Locked when the call succeeds
416  Q_EMIT Locked();
417  });
418 }
419 
420 void DBusUnitySessionService::doUnlock()
421 {
422  Q_EMIT Unlocked();
423  Q_EMIT unlocked();
424 }
425 
427 {
428  return !d->isSessionActive;
429 }
430 
432 {
433  Q_EMIT LogoutRequested(false);
434  Q_EMIT logoutRequested(false);
435 }
436 
438 {
439  d->makeLogin1Call(QStringLiteral("Reboot"), {false});
440 }
441 
443 {
444  Q_EMIT RebootRequested(false);
445  Q_EMIT rebootRequested(false);
446 }
447 
449 {
450  d->makeLogin1Call(QStringLiteral("PowerOff"), {false});
451 }
452 
454 {
455  PromptLock();
456  d->makeLogin1Call(QStringLiteral("Suspend"), {false});
457 }
458 
460 {
461  PromptLock();
462  d->makeLogin1Call(QStringLiteral("Hibernate"), {false});
463 }
464 
466 {
467  PromptLock();
468  d->makeLogin1Call(QStringLiteral("HybridSleep"), {false});
469 }
470 
472 {
473  Q_EMIT ShutdownRequested(false);
474  Q_EMIT shutdownRequested(false);
475 }
476 
477 enum class Action : unsigned
478 {
479  LOGOUT = 0,
480  SHUTDOWN,
481  REBOOT,
482  NONE
483 };
484 
485 
486 void performAsyncUnityCall(const QString &method)
487 {
488  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.Unity"),
489  QStringLiteral("/com/canonical/Unity/Session"),
490  QStringLiteral("com.canonical.Unity.Session"),
491  method);
492  QDBusConnection::sessionBus().asyncCall(msg);
493 }
494 
495 
496 DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
497  : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager"), QStringLiteral("org.gnome.SessionManager"))
498 {
499 }
500 
501 void DBusGnomeSessionManagerWrapper::Logout(quint32 mode)
502 {
503  auto call = QStringLiteral("RequestLogout");
504 
505  // These modes are documented as bitwise flags, not an enum, even though
506  // they only ever seem to be used as enums.
507 
508  if (mode & 1) // without dialog
509  call = QStringLiteral("Logout");
510  if (mode & 2) // without dialog, ignoring inhibitors (which we don't have)
511  call = QStringLiteral("Logout");
512 
513  performAsyncUnityCall(call);
514 }
515 
516 void DBusGnomeSessionManagerWrapper::Reboot()
517 {
518  // GNOME's Reboot means with dialog (they use Request differently than us).
519  performAsyncUnityCall(QStringLiteral("RequestReboot"));
520 }
521 
522 void DBusGnomeSessionManagerWrapper::RequestReboot()
523 {
524  // GNOME's RequestReboot means no dialog (they use Request differently than us).
525  performAsyncUnityCall(QStringLiteral("Reboot"));
526 }
527 
528 void DBusGnomeSessionManagerWrapper::RequestShutdown()
529 {
530  // GNOME's RequestShutdown means no dialog (they use Request differently than us).
531  performAsyncUnityCall(QStringLiteral("Shutdown"));
532 }
533 
534 void DBusGnomeSessionManagerWrapper::Shutdown()
535 {
536  // GNOME's Shutdown means with dialog (they use Request differently than us).
537  performAsyncUnityCall(QStringLiteral("RequestShutdown"));
538 }
539 
540 
541 DBusGnomeSessionManagerDialogWrapper::DBusGnomeSessionManagerDialogWrapper()
542  : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager/EndSessionDialog"), QStringLiteral("com.canonical.Unity"))
543 {
544 }
545 
546 void DBusGnomeSessionManagerDialogWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
547 {
548  Q_UNUSED(arg_1);
549  Q_UNUSED(max_wait);
550  Q_UNUSED(inhibitors);
551 
552  switch (static_cast<Action>(type))
553  {
554  case Action::LOGOUT:
555  performAsyncUnityCall(QStringLiteral("RequestLogout"));
556  break;
557 
558  case Action::REBOOT:
559  performAsyncUnityCall(QStringLiteral("RequestReboot"));
560  break;
561 
562  case Action::SHUTDOWN:
563  performAsyncUnityCall(QStringLiteral("RequestShutdown"));
564  break;
565 
566  default:
567  break;
568  }
569 }
570 
571 
572 DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
573  : UnityDBusObject(QStringLiteral("/org/gnome/ScreenSaver"), QStringLiteral("org.gnome.ScreenSaver"))
574 {
575  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
576 }
577 
578 bool DBusGnomeScreensaverWrapper::GetActive() const
579 {
580  return !d->isSessionActive; // return whether the session is not active
581 }
582 
583 void DBusGnomeScreensaverWrapper::SetActive(bool lock)
584 {
585  if (lock) {
586  Lock();
587  }
588 }
589 
590 void DBusGnomeScreensaverWrapper::Lock()
591 {
592  performAsyncUnityCall(QStringLiteral("PromptLock"));
593 }
594 
595 quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
596 {
597  return d->screensaverActiveTime();
598 }
599 
600 void DBusGnomeScreensaverWrapper::SimulateUserActivity()
601 {
602  d->setIdleHint(false);
603 }
604 
605 
606 DBusScreensaverWrapper::DBusScreensaverWrapper()
607  : UnityDBusObject(QStringLiteral("/org/freedesktop/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"))
608 {
609  QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this, QDBusConnection::ExportScriptableContents); // compat path, also register here
610  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
611 }
612 
613 bool DBusScreensaverWrapper::GetActive() const
614 {
615  return !d->isSessionActive; // return whether the session is not active
616 }
617 
618 bool DBusScreensaverWrapper::SetActive(bool lock)
619 {
620  if (lock) {
621  Lock();
622  return true;
623  }
624  return false;
625 }
626 
627 void DBusScreensaverWrapper::Lock()
628 {
629  performAsyncUnityCall(QStringLiteral("PromptLock"));
630 }
631 
632 quint32 DBusScreensaverWrapper::GetActiveTime() const
633 {
634  return d->screensaverActiveTime();
635 }
636 
637 quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
638 {
639  return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
640 }
641 
642 void DBusScreensaverWrapper::SimulateUserActivity()
643 {
644  d->setIdleHint(false);
645 }
646 
647 #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