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