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::SM_BUSNAME().call(msg);
68  if (reply.isValid()) {
69  logindSessionPath = reply.value().path();
70 
71  // start watching the Active property
72  QDBusConnection::SM_BUSNAME().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::SM_BUSNAME().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::SM_BUSNAME().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::SM_BUSNAME().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::SM_BUSNAME().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::SM_BUSNAME().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::SM_BUSNAME().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::SM_BUSNAME().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 PromptLock() slot to the logind's session Lock() signal
247  QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Lock"), this, SLOT(PromptLock()));
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::SM_BUSNAME().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Unlock"), this, SLOT(doUnlock()));
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  // Prompt as in quick. No locking animation needed. Usually used by
336  // indicator-session in combination with a switch to greeter or other
337  // user session.
338  Q_EMIT LockRequested();
339  Q_EMIT lockRequested();
340 }
341 
343 {
344  // Normal lock (with animation, as compared to PromptLock above). Usually
345  // used by indicator-session to lock the session in place.
346  //
347  // FIXME: We also -- as a bit of a hack around indicator-session not fully
348  // supporting a phone profile -- switch to greeter here. The unity7 flow is
349  // that the user chooses "Lock/Switch" from the indicator, and then can go
350  // to greeter by selecting "Switch" again from the indicator, which is now
351  // exposed by the desktop_lockscreen profile. But since in unity8, we try
352  // to expose most things all the time, we don't use the separate lockscreen
353  // profile. Instead, we just go directly to the greeter the first time
354  // a user presses "Lock/Switch". This isn't what this DBus call is
355  // supposed to do, but we can live with it for now.
356  //
357  // Here's a bug about indicator-session growing a converged Touch profile:
358  // https://launchpad.net/bugs/1557716
359  //
360  // We only do this here in the animated-lock call because that's the only
361  // time the indicator locks without also asking the display manager to
362  // switch sessions on us. And since we are switching screens, we also
363  // don't bother respecting the animate request, simply doing a PromptLock.
364  PromptLock();
365  switchToGreeter();
366 }
367 
368 void DBusUnitySessionService::switchToGreeter()
369 {
370  // lock the session using the org.freedesktop.DisplayManager system DBUS service
371  const QString sessionPath = QString::fromLocal8Bit(qgetenv("XDG_SESSION_PATH"));
372  QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DisplayManager"),
373  sessionPath,
374  QStringLiteral("org.freedesktop.DisplayManager.Session"),
375  QStringLiteral("Lock"));
376 
377  QDBusPendingCall pendingCall = QDBusConnection::SM_BUSNAME().asyncCall(msg);
378  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
379  connect(watcher, &QDBusPendingCallWatcher::finished,
380  this, [this](QDBusPendingCallWatcher* watcher) {
381 
382  QDBusPendingReply<void> reply = *watcher;
383  watcher->deleteLater();
384  if (reply.isError()) {
385  qWarning() << "Lock call failed" << reply.error().message();
386  return;
387  }
388 
389  // emit Locked when the call succeeds
390  Q_EMIT Locked();
391  });
392 }
393 
394 void DBusUnitySessionService::doUnlock()
395 {
396  Q_EMIT Unlocked();
397  Q_EMIT unlocked();
398 }
399 
401 {
402  return !d->isSessionActive;
403 }
404 
406 {
407  Q_EMIT LogoutRequested(false);
408  Q_EMIT logoutRequested(false);
409 }
410 
412 {
413  d->makeLogin1Call(QStringLiteral("Reboot"), {false});
414 }
415 
417 {
418  Q_EMIT RebootRequested(false);
419  Q_EMIT rebootRequested(false);
420 }
421 
423 {
424  d->makeLogin1Call(QStringLiteral("PowerOff"), {false});
425 }
426 
428 {
429  PromptLock();
430  d->makeLogin1Call(QStringLiteral("Suspend"), {false});
431 }
432 
434 {
435  PromptLock();
436  d->makeLogin1Call(QStringLiteral("Hibernate"), {false});
437 }
438 
440 {
441  PromptLock();
442  d->makeLogin1Call(QStringLiteral("HybridSleep"), {false});
443 }
444 
446 {
447  Q_EMIT ShutdownRequested(false);
448  Q_EMIT shutdownRequested(false);
449 }
450 
451 enum class Action : unsigned
452 {
453  LOGOUT = 0,
454  SHUTDOWN,
455  REBOOT,
456  NONE
457 };
458 
459 
460 void performAsyncUnityCall(const QString &method)
461 {
462  const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.Unity"),
463  QStringLiteral("/com/canonical/Unity/Session"),
464  QStringLiteral("com.canonical.Unity.Session"),
465  method);
466  QDBusConnection::sessionBus().asyncCall(msg);
467 }
468 
469 
470 DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
471  : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager/EndSessionDialog"), QStringLiteral("com.canonical.Unity"))
472 {
473 }
474 
475 void DBusGnomeSessionManagerWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
476 {
477  Q_UNUSED(arg_1);
478  Q_UNUSED(max_wait);
479  Q_UNUSED(inhibitors);
480 
481  switch (static_cast<Action>(type))
482  {
483  case Action::LOGOUT:
484  performAsyncUnityCall(QStringLiteral("RequestLogout"));
485  break;
486 
487  case Action::REBOOT:
488  performAsyncUnityCall(QStringLiteral("RequestReboot"));
489  break;
490 
491  case Action::SHUTDOWN:
492  performAsyncUnityCall(QStringLiteral("RequestShutdown"));
493  break;
494 
495  default:
496  break;
497  }
498 }
499 
500 
501 DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
502  : UnityDBusObject(QStringLiteral("/org/gnome/ScreenSaver"), QStringLiteral("org.gnome.ScreenSaver"))
503 {
504  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
505 }
506 
507 bool DBusGnomeScreensaverWrapper::GetActive() const
508 {
509  return !d->isSessionActive; // return whether the session is not active
510 }
511 
512 void DBusGnomeScreensaverWrapper::SetActive(bool lock)
513 {
514  if (lock) {
515  Lock();
516  }
517 }
518 
519 void DBusGnomeScreensaverWrapper::Lock()
520 {
521  performAsyncUnityCall(QStringLiteral("PromptLock"));
522 }
523 
524 quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
525 {
526  return d->screensaverActiveTime();
527 }
528 
529 void DBusGnomeScreensaverWrapper::SimulateUserActivity()
530 {
531  d->setIdleHint(false);
532 }
533 
534 
535 DBusScreensaverWrapper::DBusScreensaverWrapper()
536  : UnityDBusObject(QStringLiteral("/org/freedesktop/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"))
537 {
538  QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this, QDBusConnection::ExportScriptableContents); // compat path, also register here
539  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
540 }
541 
542 bool DBusScreensaverWrapper::GetActive() const
543 {
544  return !d->isSessionActive; // return whether the session is not active
545 }
546 
547 bool DBusScreensaverWrapper::SetActive(bool lock)
548 {
549  if (lock) {
550  Lock();
551  return true;
552  }
553  return false;
554 }
555 
556 void DBusScreensaverWrapper::Lock()
557 {
558  performAsyncUnityCall(QStringLiteral("PromptLock"));
559 }
560 
561 quint32 DBusScreensaverWrapper::GetActiveTime() const
562 {
563  return d->screensaverActiveTime();
564 }
565 
566 quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
567 {
568  return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
569 }
570 
571 void DBusScreensaverWrapper::SimulateUserActivity()
572 {
573  d->setIdleHint(false);
574 }
575 
576 #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