on bionic help sign in
unity8/plugins/Unity/Session/dbusunitysessionservice.cpp
641 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_SESSION_ROOT QStringLiteral("/org/freedesktop/login1/session/")
40#define LOGIN1_IFACE QStringLiteral("org.freedesktop.login1.Manager")
41#define LOGIN1_SESSION_IFACE QStringLiteral("org.freedesktop.login1.Session")
43#define ACTIVE_KEY QStringLiteral("Active")
44#define IDLE_SINCE_KEY QStringLiteral("IdleSinceHint")
46class DBusUnitySessionServicePrivate: public QObject
48 Q_OBJECT
49public:
50 QString logindSessionPath;
51 bool isSessionActive = true;
52 QElapsedTimer screensaverActiveTimer;
53 QDBusUnixFileDescriptor m_systemdInhibitFd;
55 DBusUnitySessionServicePrivate(): QObject() {
56 init();
57 checkActive();
58 }
60 void init()
61 {
62 QString sessionID = QString::fromLocal8Bit(qgetenv("XDG_SESSION_ID"));
63 if (!sessionID.isEmpty()) {
64 logindSessionPath = LOGIN1_SESSION_ROOT + sessionID;
66 // start watching the Active property
67 QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, logindSessionPath, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"),
68 this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
70 setupSystemdInhibition();
72 // re-enable the inhibition upon resume from sleep
73 QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("PrepareForSleep"),
74 this, SLOT(onResuming(bool)));
75 } else {
76 qWarning() << "Failed to get logind session: XDG_SESSION_ID is not set";
77 }
78 }
80 void setupSystemdInhibition()
81 {
82 if (m_systemdInhibitFd.isValid())
83 return;
85 // inhibit systemd handling of power/sleep/hibernate buttons
86 // http://www.freedesktop.org/wiki/Software/systemd/inhibit
88 QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, QStringLiteral("Inhibit"));
89 msg << "handle-power-key:handle-suspend-key:handle-hibernate-key"; // what
90 msg << "Unity"; // who
91 msg << "Unity8 handles power events"; // why
92 msg << "block"; // mode
94 QDBusPendingCall pendingCall = QDBusConnection::SM_BUSNAME().asyncCall(msg);
95 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
96 connect(watcher, &QDBusPendingCallWatcher::finished,
97 this, [this](QDBusPendingCallWatcher* watcher) {
98 QDBusPendingReply<QDBusUnixFileDescriptor> reply = *watcher;
99 watcher->deleteLater();
100 if (reply.isError()) {
101 qWarning() << "Failed to inhibit systemd powersave handling" << reply.error().message();
102 return;
103 }
105 m_systemdInhibitFd = reply.value();
106 });
107 }
109 bool checkLogin1Call(const QString &method) const
110 {
111 QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, method);
112 QDBusReply<QString> reply = QDBusConnection::SM_BUSNAME().call(msg);
113 return reply.isValid() && (reply == QStringLiteral("yes") || reply == QStringLiteral("challenge"));
114 }
116 void makeLogin1Call(const QString &method, const QVariantList &args)
117 {
118 QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
119 LOGIN1_PATH,
120 LOGIN1_IFACE,
121 method);
122 msg.setArguments(args);
123 QDBusConnection::SM_BUSNAME().asyncCall(msg);
124 }
126 void setActive(bool active)
127 {
128 isSessionActive = active;
130 Q_EMIT screensaverActiveChanged(!isSessionActive);
132 if (isSessionActive) {
133 screensaverActiveTimer.invalidate();
134 setIdleHint(false);
135 } else {
136 screensaverActiveTimer.start();
137 setIdleHint(true);
138 }
139 }
141 void checkActive()
142 {
143 if (logindSessionPath.isEmpty()) {
144 qWarning() << "Invalid session path";
145 return;
146 }
148 QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
149 logindSessionPath,
150 QStringLiteral("org.freedesktop.DBus.Properties"),
151 QStringLiteral("Get"));
152 msg << LOGIN1_SESSION_IFACE;
153 msg << ACTIVE_KEY;
155 QDBusPendingCall pendingCall = QDBusConnection::SM_BUSNAME().asyncCall(msg);
156 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
157 connect(watcher, &QDBusPendingCallWatcher::finished,
158 this, [this](QDBusPendingCallWatcher* watcher) {
160 QDBusPendingReply<QVariant> reply = *watcher;
161 watcher->deleteLater();
162 if (reply.isError()) {
163 qWarning() << "Failed to get Active property" << reply.error().message();
164 return;
165 }
167 setActive(reply.value().toBool());
168 });
169 }
171 quint32 screensaverActiveTime() const
172 {
173 if (!isSessionActive && screensaverActiveTimer.isValid()) {
174 return screensaverActiveTimer.elapsed() / 1000;
175 }
177 return 0;
178 }
180 quint64 idleSinceUSecTimestamp() const
181 {
182 QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
183 logindSessionPath,
184 QStringLiteral("org.freedesktop.DBus.Properties"),
185 QStringLiteral("Get"));
186 msg << LOGIN1_SESSION_IFACE;
187 msg << IDLE_SINCE_KEY;
189 QDBusReply<QVariant> reply = QDBusConnection::SM_BUSNAME().call(msg);
190 if (reply.isValid()) {
191 return reply.value().value<quint64>();
192 } else {
193 qWarning() << "Failed to get IdleSinceHint property" << reply.error().message();
194 }
196 return 0;
197 }
199 void setIdleHint(bool idle)
200 {
201 QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE,
202 logindSessionPath,
203 LOGIN1_SESSION_IFACE,
204 QStringLiteral("SetIdleHint"));
205 msg << idle;
206 QDBusConnection::SM_BUSNAME().asyncCall(msg);
207 }
209 bool isUserInGroup(const QString &user, const QString &groupName) const
210 {
211 auto group = getgrnam(groupName.toUtf8().data());
213 if (group && group->gr_mem)
214 {
215 for (int i = 0; group->gr_mem[i]; ++i)
216 {
217 if (g_strcmp0(group->gr_mem[i], user.toUtf8().data()) == 0) {
218 return true;
219 }
220 }
221 }
223 return false;
224 }
226private Q_SLOTS:
227 void onPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
228 {
229 Q_UNUSED(iface)
231 if (changedProps.contains(ACTIVE_KEY)) {
232 setActive(changedProps.value(ACTIVE_KEY).toBool());
233 } else if (invalidatedProps.contains(ACTIVE_KEY)) {
234 checkActive();
235 }
236 }
238 void onResuming(bool active)
239 {
240 if (!active) {
241 setupSystemdInhibition();
242 } else {
243 Q_EMIT prepareForSleep();
244 }
245 }
247Q_SIGNALS:
248 void screensaverActiveChanged(bool active);
249 void prepareForSleep();
250};
252Q_GLOBAL_STATIC(DBusUnitySessionServicePrivate, d)
254DBusUnitySessionService::DBusUnitySessionService()
255 : UnityDBusObject(QStringLiteral("/com/canonical/Unity/Session"), QStringLiteral("com.canonical.Unity"))
257 if (!d->logindSessionPath.isEmpty()) {
258 // connect our PromptLock() slot to the logind's session Lock() signal
259 QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Lock"), this, SLOT(PromptLock()));
260 // ... and our Unlocked() signal to the logind's session Unlock() signal
261 // (lightdm handles the unlocking by calling logind's Unlock method which in turn emits this signal we connect to)
262 QDBusConnection::SM_BUSNAME().connect(LOGIN1_SERVICE, d->logindSessionPath, LOGIN1_SESSION_IFACE, QStringLiteral("Unlock"), this, SLOT(doUnlock()));
263 connect(d, &DBusUnitySessionServicePrivate::prepareForSleep, this, &DBusUnitySessionService::PromptLock);
264 } else {
265 qWarning() << "Failed to connect to logind's session Lock/Unlock signals";
266 }
269void DBusUnitySessionService::Logout()
271 // TODO ask the apps to quit and then emit the signal
272 Q_EMIT LogoutReady();
273 Q_EMIT logoutReady();
276void DBusUnitySessionService::EndSession()
278 const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.ubuntu.Upstart"),
279 QStringLiteral("/com/ubuntu/Upstart"),
280 QStringLiteral("com.ubuntu.Upstart0_6"),
281 QStringLiteral("EndSession"));
282 QDBusConnection::sessionBus().asyncCall(msg);
285bool DBusUnitySessionService::CanHibernate() const
287 return d->checkLogin1Call(QStringLiteral("CanHibernate"));
290bool DBusUnitySessionService::CanSuspend() const
292 return d->checkLogin1Call(QStringLiteral("CanSuspend"));
295bool DBusUnitySessionService::CanHybridSleep() const
297 return d->checkLogin1Call(QStringLiteral("CanHybridSleep"));
300bool DBusUnitySessionService::CanReboot() const
302 return d->checkLogin1Call(QStringLiteral("CanReboot"));
305bool DBusUnitySessionService::CanShutdown() const
307 return d->checkLogin1Call(QStringLiteral("CanPowerOff"));
310bool DBusUnitySessionService::CanLock() const
312 auto user = UserName();
313 if (user.startsWith(QStringLiteral("guest-")) ||
314 d->isUserInGroup(user, QStringLiteral("nopasswdlogin"))) {
315 return false;
316 } else {
317 return true;
318 }
321QString DBusUnitySessionService::UserName() const
323 return QString::fromUtf8(g_get_user_name());
326QString DBusUnitySessionService::RealName() const
328 struct passwd *p = getpwuid(geteuid());
329 if (p) {
330 const QString gecos = QString::fromLocal8Bit(p->pw_gecos);
331 if (!gecos.isEmpty()) {
332 const QStringList splitGecos = gecos.split(QLatin1Char(','));
333 return splitGecos.first();
334 }
335 }
337 return QString();
340QString DBusUnitySessionService::HostName() const
342 char hostName[512];
343 if (gethostname(hostName, sizeof(hostName)) == -1) {
344 qWarning() << "Could not determine local hostname";
345 return QString();
346 }
347 hostName[sizeof(hostName) - 1] = '\0';
348 return QString::fromLocal8Bit(hostName);
351void DBusUnitySessionService::PromptLock()
353 // Prompt as in quick. No locking animation needed. Usually used by
354 // indicator-session in combination with a switch to greeter or other
355 // user session.
356 if (CanLock()) {
357 Q_EMIT LockRequested();
358 Q_EMIT lockRequested();
359 }
362void DBusUnitySessionService::Lock()
364 // Normal lock (with animation, as compared to PromptLock above). Usually
365 // used by indicator-session to lock the session in place.
366 //
367 // FIXME: We also -- as a bit of a hack around indicator-session not fully
368 // supporting a phone profile -- switch to greeter here. The unity7 flow is
369 // that the user chooses "Lock/Switch" from the indicator, and then can go
370 // to greeter by selecting "Switch" again from the indicator, which is now
371 // exposed by the desktop_lockscreen profile. But since in unity8, we try
372 // to expose most things all the time, we don't use the separate lockscreen
373 // profile. Instead, we just go directly to the greeter the first time
374 // a user presses "Lock/Switch". This isn't what this DBus call is
375 // supposed to do, but we can live with it for now.
376 //
377 // Here's a bug about indicator-session growing a converged Touch profile:
378 // https://launchpad.net/bugs/1557716
379 //
380 // We only do this here in the animated-lock call because that's the only
381 // time the indicator locks without also asking the display manager to
382 // switch sessions on us. And since we are switching screens, we also
383 // don't bother respecting the animate request, simply doing a PromptLock.
384 PromptLock();
385 switchToGreeter();
388void DBusUnitySessionService::switchToGreeter()
390 // lock the session using the org.freedesktop.DisplayManager system DBUS service
391 const QString sessionPath = QString::fromLocal8Bit(qgetenv("XDG_SESSION_PATH"));
392 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DisplayManager"),
393 sessionPath,
394 QStringLiteral("org.freedesktop.DisplayManager.Session"),
395 QStringLiteral("Lock"));
397 QDBusPendingCall pendingCall = QDBusConnection::SM_BUSNAME().asyncCall(msg);
398 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
399 connect(watcher, &QDBusPendingCallWatcher::finished,
400 this, [this](QDBusPendingCallWatcher* watcher) {
402 QDBusPendingReply<void> reply = *watcher;
403 watcher->deleteLater();
404 if (reply.isError()) {
405 qWarning() << "Lock call failed" << reply.error().message();
406 return;
407 }
409 // emit Locked when the call succeeds
410 Q_EMIT Locked();
411 });
414void DBusUnitySessionService::doUnlock()
416 Q_EMIT Unlocked();
417 Q_EMIT unlocked();
420bool DBusUnitySessionService::IsLocked() const
422 return !d->isSessionActive;
425void DBusUnitySessionService::RequestLogout()
427 Q_EMIT LogoutRequested(false);
428 Q_EMIT logoutRequested(false);
431void DBusUnitySessionService::Reboot()
433 d->makeLogin1Call(QStringLiteral("Reboot"), {false});
436void DBusUnitySessionService::RequestReboot()
438 Q_EMIT RebootRequested(false);
439 Q_EMIT rebootRequested(false);
442void DBusUnitySessionService::Shutdown()
444 d->makeLogin1Call(QStringLiteral("PowerOff"), {false});
447void DBusUnitySessionService::Suspend()
449 PromptLock();
450 d->makeLogin1Call(QStringLiteral("Suspend"), {false});
453void DBusUnitySessionService::Hibernate()
455 PromptLock();
456 d->makeLogin1Call(QStringLiteral("Hibernate"), {false});
459void DBusUnitySessionService::HybridSleep()
461 PromptLock();
462 d->makeLogin1Call(QStringLiteral("HybridSleep"), {false});
465void DBusUnitySessionService::RequestShutdown()
467 Q_EMIT ShutdownRequested(false);
468 Q_EMIT shutdownRequested(false);
471enum class Action : unsigned
473 LOGOUT = 0,
474 SHUTDOWN,
475 REBOOT,
476 NONE
477};
480void performAsyncUnityCall(const QString &method)
482 const QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.canonical.Unity"),
483 QStringLiteral("/com/canonical/Unity/Session"),
484 QStringLiteral("com.canonical.Unity.Session"),
485 method);
486 QDBusConnection::sessionBus().asyncCall(msg);
490DBusGnomeSessionManagerWrapper::DBusGnomeSessionManagerWrapper()
491 : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager"), QStringLiteral("org.gnome.SessionManager"))
495void DBusGnomeSessionManagerWrapper::Logout(quint32 mode)
497 auto call = QStringLiteral("RequestLogout");
499 // These modes are documented as bitwise flags, not an enum, even though
500 // they only ever seem to be used as enums.
502 if (mode & 1) // without dialog
503 call = QStringLiteral("Logout");
504 if (mode & 2) // without dialog, ignoring inhibitors (which we don't have)
505 call = QStringLiteral("Logout");
507 performAsyncUnityCall(call);
510void DBusGnomeSessionManagerWrapper::Reboot()
512 // GNOME's Reboot means with dialog (they use Request differently than us).
513 performAsyncUnityCall(QStringLiteral("RequestReboot"));
516void DBusGnomeSessionManagerWrapper::RequestReboot()
518 // GNOME's RequestReboot means no dialog (they use Request differently than us).
519 performAsyncUnityCall(QStringLiteral("Reboot"));
522void DBusGnomeSessionManagerWrapper::RequestShutdown()
524 // GNOME's RequestShutdown means no dialog (they use Request differently than us).
525 performAsyncUnityCall(QStringLiteral("Shutdown"));
528void DBusGnomeSessionManagerWrapper::Shutdown()
530 // GNOME's Shutdown means with dialog (they use Request differently than us).
531 performAsyncUnityCall(QStringLiteral("RequestShutdown"));
535DBusGnomeSessionManagerDialogWrapper::DBusGnomeSessionManagerDialogWrapper()
536 : UnityDBusObject(QStringLiteral("/org/gnome/SessionManager/EndSessionDialog"), QStringLiteral("com.canonical.Unity"))
540void DBusGnomeSessionManagerDialogWrapper::Open(const unsigned type, const unsigned arg_1, const unsigned max_wait, const QList<QDBusObjectPath> &inhibitors)
542 Q_UNUSED(arg_1);
543 Q_UNUSED(max_wait);
544 Q_UNUSED(inhibitors);
546 switch (static_cast<Action>(type))
547 {
548 case Action::LOGOUT:
549 performAsyncUnityCall(QStringLiteral("RequestLogout"));
550 break;
552 case Action::REBOOT:
553 performAsyncUnityCall(QStringLiteral("RequestReboot"));
554 break;
556 case Action::SHUTDOWN:
557 performAsyncUnityCall(QStringLiteral("RequestShutdown"));
558 break;
560 default:
561 break;
562 }
566DBusGnomeScreensaverWrapper::DBusGnomeScreensaverWrapper()
567 : UnityDBusObject(QStringLiteral("/org/gnome/ScreenSaver"), QStringLiteral("org.gnome.ScreenSaver"))
569 connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusGnomeScreensaverWrapper::ActiveChanged);
572bool DBusGnomeScreensaverWrapper::GetActive() const
574 return !d->isSessionActive; // return whether the session is not active
577void DBusGnomeScreensaverWrapper::SetActive(bool lock)
579 if (lock) {
580 Lock();
581 }
584void DBusGnomeScreensaverWrapper::Lock()
586 performAsyncUnityCall(QStringLiteral("PromptLock"));
589quint32 DBusGnomeScreensaverWrapper::GetActiveTime() const
591 return d->screensaverActiveTime();
594void DBusGnomeScreensaverWrapper::SimulateUserActivity()
596 d->setIdleHint(false);
600DBusScreensaverWrapper::DBusScreensaverWrapper()
601 : UnityDBusObject(QStringLiteral("/org/freedesktop/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"))
603 QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this, QDBusConnection::ExportScriptableContents); // compat path, also register here
604 connect(d, &DBusUnitySessionServicePrivate::screensaverActiveChanged, this, &DBusScreensaverWrapper::ActiveChanged);
607bool DBusScreensaverWrapper::GetActive() const
609 return !d->isSessionActive; // return whether the session is not active
612bool DBusScreensaverWrapper::SetActive(bool lock)
614 if (lock) {
615 Lock();
616 return true;
617 }
618 return false;
621void DBusScreensaverWrapper::Lock()
623 performAsyncUnityCall(QStringLiteral("PromptLock"));
626quint32 DBusScreensaverWrapper::GetActiveTime() const
628 return d->screensaverActiveTime();
631quint32 DBusScreensaverWrapper::GetSessionIdleTime() const
633 return QDateTime::fromMSecsSinceEpoch(d->idleSinceUSecTimestamp()/1000).secsTo(QDateTime::currentDateTime());
636void DBusScreensaverWrapper::SimulateUserActivity()
638 d->setIdleHint(false);
641#include "dbusunitysessionservice.moc"