on master help sign in
unity8/plugins/Unity/Session/dbusunitysessionservice.cpp
647 lines 21 kB history raw
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 */
17// local
18#include "dbusunitysessionservice.h"
20// system
21#include <grp.h>
22#include <sys/types.h>
23#include <unistd.h>
24#include <pwd.h>
26// Qt
27#include <QDebug>
28#include <QDBusPendingCall>
29#include <QDBusReply>
30#include <QElapsedTimer>
31#include <QDateTime>
32#include <QDBusUnixFileDescriptor>
34// Glib
35#include <glib.h>
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")
42#define ACTIVE_KEY QStringLiteral("Active")
43#define IDLE_SINCE_KEY QStringLiteral("IdleSinceHint")
45class DBusUnitySessionServicePrivate: public QObject
47 Q_OBJECT
48public:
49 QString logindSessionPath;
50 bool isSessionActive = true;
51 QElapsedTimer screensaverActiveTimer;
52 QDBusUnixFileDescriptor m_systemdInhibitFd;
54 DBusUnitySessionServicePrivate(): QObject() {
55 init();
56 checkActive();
57 }
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();
68 QDBusReply<QDBusObjectPath> reply = QDBusConnection::SM_BUSNAME().call(msg);
69 if (reply.isValid()) {
70 logindSessionPath = reply.value().path();
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)));
76 setupSystemdInhibition();
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 }
86 void setupSystemdInhibition()
87 {
88 if (m_systemdInhibitFd.isValid())
89 return;
91 // inhibit systemd handling of power/sleep/hibernate buttons
92 // http://www.freedesktop.org/wiki/Software/systemd/inhibit
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
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 }
111 m_systemdInhibitFd = reply.value();
112 });
113 }
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 }
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 }
132 void setActive(bool active)
133 {
134 isSessionActive = active;
136 Q_EMIT screensaverActiveChanged(!isSessionActive);
138 if (isSessionActive) {
139 screensaverActiveTimer.invalidate();
140 setIdleHint(false);
141 } else {
142 screensaverActiveTimer.start();
143 setIdleHint(true);
144 }
145 }
147 void checkActive()
148 {
149 if (logindSessionPath.isEmpty()) {
150 qWarning() << "Invalid session path";
151 return;
152 }
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;
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) {
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 }
173 setActive(reply.value().toBool());
174 });
175 }
177 quint32 screensaverActiveTime() const
178 {
179 if (!isSessionActive && screensaverActiveTimer.isValid()) {
180 return screensaverActiveTimer.elapsed() / 1000;
181 }
183 return 0;
184 }
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;
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 }
202 return 0;
203 }
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 }
215 bool isUserInGroup(const QString &user, const QString &groupName) const
216 {
217 auto group = getgrnam(groupName.toUtf8().data());
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 }
229 return false;
230 }
232private Q_SLOTS:
233 void onPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
234 {
235 Q_UNUSED(iface)
237 if (changedProps.contains(ACTIVE_KEY)) {
238 setActive(changedProps.value(ACTIVE_KEY).toBool());
239 } else if (invalidatedProps.contains(ACTIVE_KEY)) {
240 checkActive();
241 }
242 }
244 void onResuming(bool active)
245 {
246 if (!active) {
247 setupSystemdInhibition();
248 } else {
249 Q_EMIT prepareForSleep();
250 }
251 }
253Q_SIGNALS:
254 void screensaverActiveChanged(bool active);
255 void prepareForSleep();
256};
258Q_GLOBAL_STATIC(DBusUnitySessionServicePrivate, d)
260DBusUnitySessionService::DBusUnitySessionService()
261 : UnityDBusObject(QStringLiteral("/com/canonical/Unity/Session"), QStringLiteral("com.canonical.Unity"))
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 }
275void DBusUnitySessionService::Logout()
277 // TODO ask the apps to quit and then emit the signal
278 Q_EMIT LogoutReady();
279 Q_EMIT logoutReady();
282void DBusUnitySessionService::EndSession()
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);
291bool DBusUnitySessionService::CanHibernate() const
293 return d->checkLogin1Call(QStringLiteral("CanHibernate"));
296bool DBusUnitySessionService::CanSuspend() const
298 return d->checkLogin1Call(QStringLiteral("CanSuspend"));
301bool DBusUnitySessionService::CanHybridSleep() const
303 return d->checkLogin1Call(QStringLiteral("CanHybridSleep"));
306bool DBusUnitySessionService::CanReboot() const
308 return d->checkLogin1Call(QStringLiteral("CanReboot"));
311bool DBusUnitySessionService::CanShutdown() const
313 return d->checkLogin1Call(QStringLiteral("CanPowerOff"));
316bool DBusUnitySessionService::CanLock() const
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 }
327QString DBusUnitySessionService::UserName() const
329 return QString::fromUtf8(g_get_user_name());
332QString DBusUnitySessionService::RealName() const
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 }
343 return QString();
346QString DBusUnitySessionService::HostName() const
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);
357void DBusUnitySessionService::PromptLock()
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 }
368void DBusUnitySessionService::Lock()
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();
394void DBusUnitySessionService::switchToGreeter()
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"));
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) {
408 QDBusPendingReply<void> reply = *watcher;
409 watcher->deleteLater();
410 if (reply.isError()) {
411 qWarning() << "Lock call failed" << reply.error().message();
412 return;
413 }
415 // emit Locked when the call succeeds
416 Q_EMIT Locked();
417 });
420void DBusUnitySessionService::doUnlock()
422 Q_EMIT Unlocked();
423 Q_EMIT unlocked();
426bool DBusUnitySessionService::IsLocked() const
428 return !d->isSessionActive;
431void DBusUnitySessionService::RequestLogout()
433 Q_EMIT LogoutRequested(false);
434 Q_EMIT logoutRequested(false);
437void DBusUnitySessionService::Reboot()
439 d->makeLogin1Call(QStringLiteral("Reboot"), {false});
442void DBusUnitySessionService::RequestReboot()
444 Q_EMIT RebootRequested(false);
445 Q_EMIT rebootRequested(false);
448void DBusUnitySessionService::Shutdown()
450 d->makeLogin1Call(QStringLiteral("PowerOff"), {false});
453void DBusUnitySessionService::Suspend()
455 PromptLock();
456 d->makeLogin1Call(QStringLiteral("Suspend"), {false});
459void DBusUnitySessionService::Hibernate()
461 PromptLock();
462 d->makeLogin1Call(QStringLiteral("Hibernate"), {false});
465void DBusUnitySessionService::HybridSleep()
467 PromptLock();
468 d->makeLogin1Call(QStringLiteral("HybridSleep"), {false});
471void DBusUnitySessionService::RequestShutdown()
473 Q_EMIT ShutdownRequested(false);
474 Q_EMIT shutdownRequested(false);
477enum class Action : unsigned
479 LOGOUT = 0,
480 SHUTDOWN,
481 REBOOT,
482 NONE
483};
486void performAsyncUnityCall(const QString &method)
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);
496DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
497 : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager"), QStringLiteral("org.gnome.SessionManager"))
501void DBusGnomeSessionManagerWrapper::Logout(quint32 mode)
503 auto call = QStringLiteral("RequestLogout");
505 // These modes are documented as bitwise flags, not an enum, even though
506 // they only ever seem to be used as enums.
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");
513 performAsyncUnityCall(call);
516void DBusGnomeSessionManagerWrapper::Reboot()
518 // GNOME's Reboot means with dialog (they use Request differently than us).
519 performAsyncUnityCall(QStringLiteral("RequestReboot"));
522void DBusGnomeSessionManagerWrapper::RequestReboot()
524 // GNOME's RequestReboot means no dialog (they use Request differently than us).
525 performAsyncUnityCall(QStringLiteral("Reboot"));
528void DBusGnomeSessionManagerWrapper::RequestShutdown()
530 // GNOME's RequestShutdown means no dialog (they use Request differently than us).
531 performAsyncUnityCall(QStringLiteral("Shutdown"));
534void DBusGnomeSessionManagerWrapper::Shutdown()
536 // GNOME's Shutdown means with dialog (they use Request differently than us).
537 performAsyncUnityCall(QStringLiteral("RequestShutdown"));
541DBusGnomeSessionManagerDialogWrapper::DBusGnomeSessionManagerDialogWrapper()
542 : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager/EndSessionDialog"), QStringLiteral("com.canonical.Unity"))
546void DBusGnomeSessionManagerDialogWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
548 Q_UNUSED(arg_1);
549 Q_UNUSED(max_wait);
550 Q_UNUSED(inhibitors);
552 switch (static_cast<Action>(type))
553 {
554 case Action::LOGOUT:
555 performAsyncUnityCall(QStringLiteral("RequestLogout"));
556 break;
558 case Action::REBOOT:
559 performAsyncUnityCall(QStringLiteral("RequestReboot"));
560 break;
562 case Action::SHUTDOWN:
563 performAsyncUnityCall(QStringLiteral("RequestShutdown"));
564 break;
566 default:
567 break;
568 }
572DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
573 : UnityDBusObject(QStringLiteral("/org/gnome/ScreenSaver"), QStringLiteral("org.gnome.ScreenSaver"))
575 connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
578bool DBusGnomeScreensaverWrapper::GetActive() const
580 return !d->isSessionActive; // return whether the session is not active
583void DBusGnomeScreensaverWrapper::SetActive(bool lock)
585 if (lock) {
586 Lock();
587 }
590void DBusGnomeScreensaverWrapper::Lock()
592 performAsyncUnityCall(QStringLiteral("PromptLock"));
595quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
597 return d->screensaverActiveTime();
600void DBusGnomeScreensaverWrapper::SimulateUserActivity()
602 d->setIdleHint(false);
606DBusScreensaverWrapper::DBusScreensaverWrapper()
607 : UnityDBusObject(QStringLiteral("/org/freedesktop/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"))
609 QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this, QDBusConnection::ExportScriptableContents); // compat path, also register here
610 connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
613bool DBusScreensaverWrapper::GetActive() const
615 return !d->isSessionActive; // return whether the session is not active
618bool DBusScreensaverWrapper::SetActive(bool lock)
620 if (lock) {
621 Lock();
622 return true;
623 }
624 return false;
627void DBusScreensaverWrapper::Lock()
629 performAsyncUnityCall(QStringLiteral("PromptLock"));
632quint32 DBusScreensaverWrapper::GetActiveTime() const
634 return d->screensaverActiveTime();
637quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
639 return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
642void DBusScreensaverWrapper::SimulateUserActivity()
644 d->setIdleHint(false);
647#include "dbusunitysessionservice.moc"