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 
32 #define LOGIN1_SERVICE QStringLiteral("org.freedesktop.login1")
33 #define LOGIN1_PATH QStringLiteral("/org/freedesktop/login1")
34 #define LOGIN1_IFACE QStringLiteral("org.freedesktop.login1.Manager")
35 #define LOGIN1_SESSION_IFACE QStringLiteral("org.freedesktop.login1.Session")
36 
37 #define ACTIVE_KEY QStringLiteral("Active")
38 #define IDLE_SINCE_KEY QStringLiteral("IdleSinceHint")
39 
40 class DBusUnitySessionServicePrivate: public QObject
41 {
42  Q_OBJECT
43 public:
44  QString logindSessionPath;
45  bool isSessionActive = true;
46  QElapsedTimer screensaverActiveTimer;
47 
48  DBusUnitySessionServicePrivate(): QObject() {
49  init();
50  checkActive();
51  }
52 
53  void init()
54  {
55  // get our logind session path
56  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
57  LOGIN1_PATH,
58  LOGIN1_IFACE,
59  "GetSessionByPID");
60  msg << (quint32) getpid();
61 
62  QDBusReply<QDBusObjectPath> reply = QDBusConnection::systemBus().asyncCall(msg);
63  if (reply.isValid()) {
64  logindSessionPath = reply.value().path();
65 
66  // start watching the Active property
67  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, logindSessionPath, "org.freedesktop.DBus.Properties", "PropertiesChanged",
68  this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
69  } else {
70  qWarning() << "Failed to get logind session path" << reply.error().message();
71  }
72  }
73 
74  bool checkLogin1Call(const QString &method) const
75  {
76  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, method);
77  QDBusReply<QString> reply = QDBusConnection::systemBus().asyncCall(msg);
78  return reply.isValid() && (reply == QStringLiteral("yes") || reply == QStringLiteral("challenge"));
79  }
80 
81  void makeLogin1Call(const QString &method, const QVariantList &args)
82  {
83  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
84  LOGIN1_PATH,
85  LOGIN1_IFACE,
86  method);
87  msg.setArguments(args);
88  QDBusConnection::systemBus().asyncCall(msg);
89  }
90 
91  void checkActive()
92  {
93  if (logindSessionPath.isEmpty()) {
94  qWarning() << "Invalid session path";
95  return;
96  }
97 
98  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
99  logindSessionPath,
100  "org.freedesktop.DBus.Properties",
101  "Get");
102  msg << LOGIN1_SESSION_IFACE;
103  msg << ACTIVE_KEY;
104 
105  QDBusReply<QVariant> reply = QDBusConnection::systemBus().asyncCall(msg);
106  if (reply.isValid()) {
107  isSessionActive = reply.value().toBool();
108  qDebug() << "Session" << logindSessionPath << "is active:" << isSessionActive;
109  } else {
110  qWarning() << "Failed to get Active property" << reply.error().message();
111  }
112  }
113 
114  quint32 screensaverActiveTime() const
115  {
116  if (!isSessionActive && screensaverActiveTimer.isValid()) {
117  return screensaverActiveTimer.elapsed() / 1000;
118  }
119 
120  return 0;
121  }
122 
123  quint64 idleSinceUSecTimestamp() const
124  {
125  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
126  logindSessionPath,
127  "org.freedesktop.DBus.Properties",
128  "Get");
129  msg << LOGIN1_SESSION_IFACE;
130  msg << IDLE_SINCE_KEY;
131 
132  QDBusReply<QVariant> reply = QDBusConnection::systemBus().asyncCall(msg);
133  if (reply.isValid()) {
134  return reply.value().value<quint64>();
135  } else {
136  qWarning() << "Failed to get IdleSinceHint property" << reply.error().message();
137  }
138 
139  return 0;
140  }
141 
142  void setIdleHint(bool idle)
143  {
144  QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
145  logindSessionPath,
146  LOGIN1_SESSION_IFACE,
147  "SetIdleHint");
148  msg << idle;
149  QDBusConnection::systemBus().asyncCall(msg);
150  }
151 
152 private Q_SLOTS:
153  void onPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
154  {
155  Q_UNUSED(iface)
156 
157  if (changedProps.contains(ACTIVE_KEY) || invalidatedProps.contains(ACTIVE_KEY)) {
158  if (changedProps.value(ACTIVE_KEY).isValid()) {
159  isSessionActive = changedProps.value(ACTIVE_KEY).toBool();
160  } else {
161  checkActive();
162  }
163 
164  Q_EMIT screensaverActiveChanged(!isSessionActive);
165 
166  if (isSessionActive) {
167  screensaverActiveTimer.invalidate();
168  setIdleHint(false);
169  } else {
170  screensaverActiveTimer.start();
171  setIdleHint(true);
172  }
173  }
174  }
175 
176 Q_SIGNALS:
177  void screensaverActiveChanged(bool active);
178 };
179 
180 Q_GLOBAL_STATIC(DBusUnitySessionServicePrivate, d)
181 
183  : UnityDBusObject("/com/canonical/Unity/Session", "com.canonical.Unity")
184 {
185  if (!d->logindSessionPath.isEmpty()) {
186  // connect our Lock() slot to the logind's session Lock() signal
187  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, "Lock", this, SLOT(Lock()));
188  // ... and our Unlocked() signal to the logind's session Unlock() signal
189  // (lightdm handles the unlocking by calling logind's Unlock method which in turn emits this signal we connect to)
190  QDBusConnection::systemBus().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, "Unlock", this, SIGNAL(Unlocked()));
191  } else {
192  qWarning() << "Failed to connect to logind's session Lock/Unlock signals";
193  }
194 }
195 
197 {
198  // TODO ask the apps to quit and then emit the signal
199  Q_EMIT LogoutReady();
200  Q_EMIT logoutReady();
201 }
202 
204 {
205  const QDBusMessage msg = QDBusMessage::createMethodCall("com.ubuntu.Upstart",
206  "/com/ubuntu/Upstart",
207  "com.ubuntu.Upstart0_6",
208  "EndSession");
209  QDBusConnection::sessionBus().asyncCall(msg);
210 }
211 
213 {
214  return d->checkLogin1Call("CanHibernate");
215 }
216 
218 {
219  return d->checkLogin1Call("CanSuspend");
220 }
221 
223 {
224  return d->checkLogin1Call("CanHybridSleep");
225 }
226 
228 {
229  return d->checkLogin1Call("CanReboot");
230 }
231 
233 {
234  return d->checkLogin1Call("CanPowerOff");
235 }
236 
238 {
239  return true; // FIXME
240 }
241 
243 {
244  struct passwd *p = getpwuid(geteuid());
245  if (p) {
246  return QString::fromUtf8(p->pw_name);
247  }
248 
249  return QString();
250 }
251 
253 {
254  struct passwd *p = getpwuid(geteuid());
255  if (p) {
256  const QString gecos = QString::fromLocal8Bit(p->pw_gecos);
257  if (!gecos.isEmpty()) {
258  return gecos.split(QLatin1Char(',')).first();
259  }
260  }
261 
262  return QString();
263 }
264 
266 {
267  char hostName[512];
268  if (gethostname(hostName, sizeof(hostName)) == -1) {
269  qWarning() << "Could not determine local hostname";
270  return QString();
271  }
272  hostName[sizeof(hostName) - 1] = '\0';
273  return QString::fromLocal8Bit(hostName);
274 }
275 
277 {
278  Q_EMIT LockRequested();
279  Q_EMIT lockRequested();
280 }
281 
283 {
284  // lock the session using the org.freedesktop.DisplayManager system DBUS service
285  const QString sessionPath = QString::fromLocal8Bit(qgetenv("XDG_SESSION_PATH"));
286  QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.DisplayManager",
287  sessionPath,
288  "org.freedesktop.DisplayManager.Session",
289  "Lock");
290  qDebug() << "Locking session" << msg.path();
291  QDBusReply<void> reply = QDBusConnection::systemBus().asyncCall(msg);
292  if (!reply.isValid()) {
293  qWarning() << "Lock call failed" << reply.error().message();
294  } else {
295  // emit Locked when the call succeeds
296  Q_EMIT Locked();
297  }
298 }
299 
301 {
302  return !d->isSessionActive;
303 }
304 
306 {
307  Q_EMIT LogoutRequested(false);
308  Q_EMIT logoutRequested(false);
309 }
310 
312 {
313  d->makeLogin1Call("Reboot", {false});
314 }
315 
317 {
318  Q_EMIT RebootRequested(false);
319  Q_EMIT rebootRequested(false);
320 }
321 
323 {
324  d->makeLogin1Call("PowerOff", {false});
325 }
326 
328 {
329  d->makeLogin1Call("Suspend", {false});
330 }
331 
333 {
334  d->makeLogin1Call("Hibernate", {false});
335 }
336 
338 {
339  d->makeLogin1Call("HybridSleep", {false});
340 }
341 
343 {
344  Q_EMIT ShutdownRequested(false);
345  Q_EMIT shutdownRequested(false);
346 }
347 
348 enum class Action : unsigned
349 {
350  LOGOUT = 0,
351  SHUTDOWN,
352  REBOOT,
353  NONE
354 };
355 
356 
357 void performAsyncUnityCall(const QString &method)
358 {
359  const QDBusMessage msg = QDBusMessage::createMethodCall("com.canonical.Unity",
360  "/com/canonical/Unity/Session",
361  "com.canonical.Unity.Session",
362  method);
363  QDBusConnection::sessionBus().asyncCall(msg);
364 }
365 
366 
367 DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
368  : UnityDBusObject("/org/gnome/SessionManager/EndSessionDialog", "com.canonical.Unity")
369 {
370 }
371 
372 void DBusGnomeSessionManagerWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
373 {
374  Q_UNUSED(arg_1);
375  Q_UNUSED(max_wait);
376  Q_UNUSED(inhibitors);
377 
378  switch (static_cast<Action>(type))
379  {
380  case Action::LOGOUT:
381  performAsyncUnityCall("RequestLogout");
382  break;
383 
384  case Action::REBOOT:
385  performAsyncUnityCall("RequestReboot");
386  break;
387 
388  case Action::SHUTDOWN:
389  performAsyncUnityCall("RequestShutdown");
390  break;
391 
392  default:
393  break;
394  }
395 }
396 
397 
398 DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
399  : UnityDBusObject("/org/gnome/ScreenSaver", "org.gnome.ScreenSaver")
400 {
401  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
402 }
403 
404 bool DBusGnomeScreensaverWrapper::GetActive() const
405 {
406  return !d->isSessionActive; // return whether the session is not active
407 }
408 
409 void DBusGnomeScreensaverWrapper::SetActive(bool lock)
410 {
411  if (lock) {
412  Lock();
413  }
414 }
415 
416 void DBusGnomeScreensaverWrapper::Lock()
417 {
418  performAsyncUnityCall("Lock");
419 }
420 
421 quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
422 {
423  return d->screensaverActiveTime();
424 }
425 
426 void DBusGnomeScreensaverWrapper::SimulateUserActivity()
427 {
428  d->setIdleHint(false);
429 }
430 
431 
432 DBusScreensaverWrapper::DBusScreensaverWrapper()
433  : UnityDBusObject("/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver")
434 {
435  QDBusConnection::sessionBus().registerObject("/ScreenSaver", this, QDBusConnection::ExportScriptableContents); // compat path, also register here
436  connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
437 }
438 
439 bool DBusScreensaverWrapper::GetActive() const
440 {
441  return !d->isSessionActive; // return whether the session is not active
442 }
443 
444 bool DBusScreensaverWrapper::SetActive(bool lock)
445 {
446  if (lock) {
447  Lock();
448  return true;
449  }
450  return false;
451 }
452 
453 void DBusScreensaverWrapper::Lock()
454 {
455  performAsyncUnityCall("Lock");
456 }
457 
458 quint32 DBusScreensaverWrapper::GetActiveTime() const
459 {
460  return d->screensaverActiveTime();
461 }
462 
463 quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
464 {
465  return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
466 }
467 
468 void DBusScreensaverWrapper::SimulateUserActivity()
469 {
470  d->setIdleHint(false);
471 }
472 
473 #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