Plasma-workspace

kdisplaymanager.cpp
1/*
2 SPDX-FileCopyrightText: 2004 Oswald Buddenhagen <ossi@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kdisplaymanager.h"
8
9#include <kuser.h>
10
11#include <KLocalizedString>
12
13#include <QDBusArgument>
14#include <QDBusConnectionInterface>
15#include <QDBusInterface>
16#include <QDBusMetaType>
17#include <QDBusObjectPath>
18#include <QDBusReply>
19#include <QGuiApplication>
20
21#include "config-X11.h"
22#if HAVE_X11
23#include <X11/Xauth.h>
24#include <X11/Xlib.h>
25#endif // HAVE_X11
26
27#include <errno.h>
28#include <fcntl.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <sys/socket.h>
32#include <sys/types.h>
33#include <sys/un.h>
34#include <unistd.h>
35
36#define _DBUS_PROPERTIES_IFACE "org.freedesktop.DBus.Properties"
37#define _DBUS_PROPERTIES_GET "Get"
38
39#define DBUS_PROPERTIES_IFACE QLatin1String(_DBUS_PROPERTIES_IFACE)
40#define DBUS_PROPERTIES_GET QLatin1String(_DBUS_PROPERTIES_GET)
41
42#define _SYSTEMD_SERVICE "org.freedesktop.login1"
43#define _SYSTEMD_BASE_PATH "/org/freedesktop/login1"
44#define _SYSTEMD_MANAGER_IFACE _SYSTEMD_SERVICE ".Manager"
45#define _SYSTEMD_SESSION_BASE_PATH _SYSTEMD_BASE_PATH "/session"
46#define _SYSTEMD_SEAT_IFACE _SYSTEMD_SERVICE ".Seat"
47#define _SYSTEMD_SEAT_BASE_PATH _SYSTEMD_BASE_PATH "/seat"
48#define _SYSTEMD_SESSION_IFACE _SYSTEMD_SERVICE ".Session"
49#define _SYSTEMD_USER_PROPERTY "User"
50#define _SYSTEMD_SEAT_PROPERTY "Seat"
51#define _SYSTEMD_SESSIONS_PROPERTY "Sessions"
52#define _SYSTEMD_SWITCH_PROPERTY "Activate"
53
54#define SYSTEMD_SERVICE QLatin1String(_SYSTEMD_SERVICE)
55#define SYSTEMD_BASE_PATH QLatin1String(_SYSTEMD_BASE_PATH)
56#define SYSTEMD_MANAGER_IFACE QLatin1String(_SYSTEMD_MANAGER_IFACE)
57#define SYSTEMD_SESSION_BASE_PATH QLatin1String(_SYSTEMD_SESSION_BASE_PATH)
58#define SYSTEMD_SEAT_IFACE QLatin1String(_SYSTEMD_SEAT_IFACE)
59#define SYSTEMD_SEAT_BASE_PATH QLatin1String(_SYSTEMD_SEAT_BASE_PATH)
60#define SYSTEMD_SESSION_IFACE QLatin1String(_SYSTEMD_SESSION_IFACE)
61#define SYSTEMD_USER_PROPERTY QLatin1String(_SYSTEMD_USER_PROPERTY)
62#define SYSTEMD_SEAT_PROPERTY QLatin1String(_SYSTEMD_SEAT_PROPERTY)
63#define SYSTEMD_SESSIONS_PROPERTY QLatin1String(_SYSTEMD_SESSIONS_PROPERTY)
64#define SYSTEMD_SWITCH_CALL QLatin1String(_SYSTEMD_SWITCH_PROPERTY)
65
66struct NamedDBusObjectPath {
67 QString name;
68 QDBusObjectPath path;
69};
70
71// Marshall the NamedDBusObjectPath data into a D-Bus argument
72QDBusArgument &operator<<(QDBusArgument &argument, const NamedDBusObjectPath &namedPath)
73{
74 argument.beginStructure();
75 argument << namedPath.name << namedPath.path;
76 argument.endStructure();
77 return argument;
78}
79
80// Retrieve the NamedDBusObjectPath data from the D-Bus argument
81const QDBusArgument &operator>>(const QDBusArgument &argument, NamedDBusObjectPath &namedPath)
82{
83 argument.beginStructure();
84 argument >> namedPath.name >> namedPath.path;
85 argument.endStructure();
86 return argument;
87}
88
89struct NumberedDBusObjectPath {
90 uint num;
91 QDBusObjectPath path;
92};
93
94// Marshall the NumberedDBusObjectPath data into a D-Bus argument
95QDBusArgument &operator<<(QDBusArgument &argument, const NumberedDBusObjectPath &numberedPath)
96{
97 argument.beginStructure();
98 argument << numberedPath.num << numberedPath.path;
99 argument.endStructure();
100 return argument;
101}
102
103// Retrieve the NumberedDBusObjectPath data from the D-Bus argument
104const QDBusArgument &operator>>(const QDBusArgument &argument, NumberedDBusObjectPath &numberedPath)
105{
106 argument.beginStructure();
107 argument >> numberedPath.num >> numberedPath.path;
108 argument.endStructure();
109 return argument;
110}
111
112class SystemdManager : public QDBusInterface
113{
114public:
115 SystemdManager()
116 : QDBusInterface(SYSTEMD_SERVICE, SYSTEMD_BASE_PATH, SYSTEMD_MANAGER_IFACE, QDBusConnection::systemBus())
117 {
118 }
119};
120
121class SystemdSeat : public QDBusInterface
122{
123public:
124 SystemdSeat(const QDBusObjectPath &path)
125 : QDBusInterface(SYSTEMD_SERVICE, path.path(), SYSTEMD_SEAT_IFACE, QDBusConnection::systemBus())
126 {
127 }
128 /* HACK to be able to extract a(so) type from QDBus, property doesn't do the trick */
129 QList<NamedDBusObjectPath> getSessions()
130 {
131 QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET);
132 message << interface() << SYSTEMD_SESSIONS_PROPERTY;
134
135 QVariantList args = reply.arguments();
136 if (!args.isEmpty()) {
137 QList<NamedDBusObjectPath> namedPathList =
138 qdbus_cast<QList<NamedDBusObjectPath>>(args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>());
139 return namedPathList;
140 }
142 }
143};
144
145class SystemdSession : public QDBusInterface
146{
147public:
148 SystemdSession(const QDBusObjectPath &path)
149 : QDBusInterface(SYSTEMD_SERVICE, path.path(), SYSTEMD_SESSION_IFACE, QDBusConnection::systemBus())
150 {
151 }
152 /* HACK to be able to extract (so) type from QDBus, property doesn't do the trick */
153 NamedDBusObjectPath getSeat()
154 {
155 QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET);
156 message << interface() << SYSTEMD_SEAT_PROPERTY;
158
159 QVariantList args = reply.arguments();
160 if (!args.isEmpty()) {
161 NamedDBusObjectPath namedPath;
162 args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> namedPath;
163 return namedPath;
164 }
165 return NamedDBusObjectPath();
166 }
167 NumberedDBusObjectPath getUser()
168 {
169 QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET);
170 message << interface() << SYSTEMD_USER_PROPERTY;
172
173 QVariantList args = reply.arguments();
174 if (!args.isEmpty()) {
175 NumberedDBusObjectPath numberedPath;
176 args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> numberedPath;
177 return numberedPath;
178 }
179 return NumberedDBusObjectPath();
180 }
181 void getSessionLocation(SessEnt &se)
182 {
183 se.tty = (property("Type").toString() != QLatin1String("x11"));
184 se.display = property(se.tty ? "TTY" : "Display").toString();
185 se.vt = property("VTNr").toInt();
186 }
187};
188
189class CKManager : public QDBusInterface
190{
191public:
192 CKManager()
193 : QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"),
194 QStringLiteral("/org/freedesktop/ConsoleKit/Manager"),
195 QStringLiteral("org.freedesktop.ConsoleKit.Manager"),
196 QDBusConnection::systemBus())
197 {
198 }
199};
200
201class CKSeat : public QDBusInterface
202{
203public:
204 CKSeat(const QDBusObjectPath &path)
205 : QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"),
206 path.path(),
207 QStringLiteral("org.freedesktop.ConsoleKit.Seat"),
208 QDBusConnection::systemBus())
209 {
210 }
211};
212
213class CKSession : public QDBusInterface
214{
215public:
216 CKSession(const QDBusObjectPath &path)
217 : QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"),
218 path.path(),
219 QStringLiteral("org.freedesktop.ConsoleKit.Session"),
220 QDBusConnection::systemBus())
221 {
222 }
223 void getSessionLocation(SessEnt &se)
224 {
225 QString tty;
226 QDBusReply<QString> r = call(QStringLiteral("GetX11Display"));
227 if (r.isValid() && !r.value().isEmpty()) {
228 QDBusReply<QString> r2 = call(QStringLiteral("GetX11DisplayDevice"));
229 tty = r2.value();
230 se.display = r.value();
231 se.tty = false;
232 } else {
233 QDBusReply<QString> r2 = call(QStringLiteral("GetDisplayDevice"));
234 tty = r2.value();
235 se.display = tty;
236 se.tty = true;
237 }
238 se.vt = QStringView(tty).mid(strlen("/dev/tty")).toInt();
239 }
240};
241
242class GDMFactory : public QDBusInterface
243{
244public:
245 GDMFactory()
246 : QDBusInterface(QStringLiteral("org.gnome.DisplayManager"),
247 QStringLiteral("/org/gnome/DisplayManager/LocalDisplayFactory"),
248 QStringLiteral("org.gnome.DisplayManager.LocalDisplayFactory"),
249 QDBusConnection::systemBus())
250 {
251 }
252};
253
254class LightDMDBus : public QDBusInterface
255{
256public:
257 LightDMDBus()
258 : QDBusInterface(QStringLiteral("org.freedesktop.DisplayManager"),
259 qgetenv("XDG_SEAT_PATH"),
260 QStringLiteral("org.freedesktop.DisplayManager.Seat"),
261 QDBusConnection::systemBus())
262 {
263 }
264};
265
266static enum {
267 Dunno,
268 NoDM,
269 NewKDM,
270 OldKDM,
271 NewGDM,
272 OldGDM,
273 LightDM,
274} DMType = Dunno;
275static const char *ctl, *dpy;
276
277class KDisplayManager::Private
278{
279public:
280 Private()
281 : fd(-1)
282 {
283 }
284 ~Private()
285 {
286 if (fd >= 0)
287 close(fd);
288 }
289
290 int fd;
291};
292
293KDisplayManager::KDisplayManager()
294 : d(new Private)
295{
296 const char *ptr;
297 struct sockaddr_un sa;
298
299 qDBusRegisterMetaType<NamedDBusObjectPath>();
300 qDBusRegisterMetaType<QList<NamedDBusObjectPath>>();
301 qDBusRegisterMetaType<NumberedDBusObjectPath>();
302
303 if (DMType == Dunno) {
304 dpy = ::getenv("DISPLAY");
305 if (dpy && (ctl = ::getenv("DM_CONTROL")))
306 DMType = NewKDM;
307 else if (dpy && (ctl = ::getenv("XDM_MANAGED")) && ctl[0] == '/')
308 DMType = OldKDM;
309 else if (::getenv("XDG_SEAT_PATH") && LightDMDBus().isValid())
310 DMType = LightDM;
311 else if (::getenv("GDMSESSION"))
312 DMType = GDMFactory().isValid() ? NewGDM : OldGDM;
313 else
314 DMType = NoDM;
315 }
316 switch (DMType) {
317 default:
318 return;
319 case NewKDM:
320 case OldGDM:
321 if ((d->fd = ::socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
322 return;
323 sa.sun_family = AF_UNIX;
324 if (DMType == OldGDM) {
325 strcpy(sa.sun_path, "/var/run/gdm_socket");
326 if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) {
327 strcpy(sa.sun_path, "/tmp/.gdm_socket");
328 if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) {
329 ::close(d->fd);
330 d->fd = -1;
331 break;
332 }
333 }
334 GDMAuthenticate();
335 } else {
336 if ((ptr = strchr(dpy, ':')))
337 ptr = strchr(ptr, '.');
338 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/dmctl-%.*s/socket", ctl, ptr ? int(ptr - dpy) : 512, dpy);
339 if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) {
340 ::close(d->fd);
341 d->fd = -1;
342 }
343 }
344 break;
345 case OldKDM: {
346 QString tf(ctl);
347 tf.truncate(tf.indexOf(','));
348 d->fd = ::open(tf.toLatin1(), O_WRONLY);
349 } break;
350 }
351}
352
353KDisplayManager::~KDisplayManager()
354{
355 delete d;
356}
357
358bool KDisplayManager::exec(const char *cmd)
359{
360 QByteArray buf;
361
362 return exec(cmd, buf);
363}
364
365/**
366 * Execute a KDM/GDM remote control command.
367 * @param cmd the command to execute. FIXME: undocumented yet.
368 * @param buf the result buffer.
369 * @return result:
370 * @li If true, the command was successfully executed.
371 * @p ret might contain additional results.
372 * @li If false and @p ret is empty, a communication error occurred
373 * (most probably KDM is not running).
374 * @li If false and @p ret is non-empty, it contains the error message
375 * from KDM.
376 */
377bool KDisplayManager::exec(const char *cmd, QByteArray &buf)
378{
379 bool ret = false;
380 int tl;
381 int len = 0;
382
383 if (d->fd < 0)
384 goto busted;
385
386 tl = strlen(cmd);
387 if (::write(d->fd, cmd, tl) != tl) {
388 bust:
389 ::close(d->fd);
390 d->fd = -1;
391 busted:
392 buf.resize(0);
393 return false;
394 }
395 if (DMType == OldKDM) {
396 buf.resize(0);
397 return true;
398 }
399 for (;;) {
400 if (buf.size() < 128)
401 buf.resize(128);
402 else if (buf.size() < len * 2)
403 buf.resize(len * 2);
404 if ((tl = ::read(d->fd, buf.data() + len, buf.size() - len)) <= 0) {
405 if (tl < 0 && errno == EINTR)
406 continue;
407 goto bust;
408 }
409 len += tl;
410 if (buf[len - 1] == '\n') {
411 buf[len - 1] = 0;
412 if (len > 2 && (buf[0] == 'o' || buf[0] == 'O') && (buf[1] == 'k' || buf[1] == 'K') && buf[2] <= ' ')
413 ret = true;
414 break;
415 }
416 }
417 return ret;
418}
419
420static bool getCurrentSeat(QDBusObjectPath *currentSession, QDBusObjectPath *currentSeat)
421{
422 SystemdManager man;
423 if (man.isValid()) {
424 *currentSeat = QDBusObjectPath(_SYSTEMD_SEAT_BASE_PATH "/auto");
425 SystemdSeat seat(*currentSeat);
426 if (seat.property("Id").isValid()) { // query an arbitrary property to confirm the path is valid
427 return true;
428 }
429
430 // auto is newer and may not exist on all platforms, fallback to GetSessionByPID if the above failed
431
432 QDBusReply<QDBusObjectPath> r = man.call(QStringLiteral("GetSessionByPID"), (uint)QCoreApplication::applicationPid());
433 if (r.isValid()) {
434 SystemdSession sess(r.value());
435 if (sess.isValid()) {
436 NamedDBusObjectPath namedPath = sess.getSeat();
437 *currentSeat = namedPath.path;
438 return true;
439 }
440 }
441 } else {
442 CKManager man;
443 QDBusReply<QDBusObjectPath> r = man.call(QStringLiteral("GetCurrentSession"));
444 if (r.isValid()) {
445 CKSession sess(r.value());
446 if (sess.isValid()) {
447 QDBusReply<QDBusObjectPath> r2 = sess.call(QStringLiteral("GetSeatId"));
448 if (r2.isValid()) {
449 if (currentSession)
450 *currentSession = r.value();
451 *currentSeat = r2.value();
452 return true;
453 }
454 }
455 }
456 }
457 return false;
458}
459
460static QList<QDBusObjectPath> getSessionsForSeat(const QDBusObjectPath &path)
461{
462 if (path.path().startsWith(SYSTEMD_BASE_PATH)) { // systemd path incoming
463 SystemdSeat seat(path);
464 if (seat.isValid()) {
465 const QList<NamedDBusObjectPath> r = seat.getSessions();
467 for (const NamedDBusObjectPath &namedPath : r)
468 result.append(namedPath.path);
469 // This pretty much can't contain any other than local sessions as the seat is retrieved from the current session
470 return result;
471 }
472 } else if (path.path().startsWith(QLatin1String("/org/freedesktop/ConsoleKit"))) {
473 CKSeat seat(path);
474 if (seat.isValid()) {
475 QDBusReply<QList<QDBusObjectPath>> r = seat.call(QStringLiteral("GetSessions"));
476 if (r.isValid()) {
477 // This will contain only local sessions:
478 // - this is only ever called when isSwitchable() is true => local seat
479 // - remote logins into the machine are assigned to other seats
480 return r.value();
481 }
482 }
483 }
484 return QList<QDBusObjectPath>();
485}
486
487#ifndef KDM_NO_SHUTDOWN
488bool KDisplayManager::canShutdown()
489{
490 if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) {
491 QDBusReply<QString> canPowerOff = SystemdManager().call(QStringLiteral("CanPowerOff"));
492 if (canPowerOff.isValid())
493 return canPowerOff.value() != QLatin1String("no");
494 QDBusReply<bool> canStop = CKManager().call(QStringLiteral("CanStop"));
495 if (canStop.isValid())
496 return canStop.value();
497 return false;
498 }
499
500 if (DMType == OldKDM)
501 return strstr(ctl, ",maysd") != nullptr;
502
503 QByteArray re;
504
505 if (DMType == OldGDM)
506 return exec("QUERY_LOGOUT_ACTION\n", re) && re.indexOf("HALT") >= 0;
507
508 return exec("caps\n", re) && re.indexOf("\tshutdown") >= 0;
509}
510
511void KDisplayManager::shutdown(KWorkSpace::ShutdownType shutdownType,
512 KWorkSpace::ShutdownMode shutdownMode, /* NOT Default */
513 const QString &bootOption)
514{
515 if (shutdownType == KWorkSpace::ShutdownTypeNone || shutdownType == KWorkSpace::ShutdownTypeLogout)
516 return;
517
518 bool cap_ask;
519 if (DMType == NewKDM) {
520 QByteArray re;
521 cap_ask = exec("caps\n", re) && re.indexOf("\tshutdown ask") >= 0;
522 } else {
523 if (!bootOption.isEmpty())
524 return;
525
526 if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) {
527 // systemd supports only 2 modes:
528 // * interactive = true: brings up a PolicyKit prompt if other sessions are active
529 // * interactive = false: rejects the shutdown if other sessions are active
530 // There are no schedule or force modes.
531 // We try to map our 4 shutdown modes in the sanest way.
532 bool interactive = (shutdownMode == KWorkSpace::ShutdownModeInteractive || shutdownMode == KWorkSpace::ShutdownModeForceNow);
533 QDBusReply<QString> check =
534 SystemdManager().call(QLatin1String(shutdownType == KWorkSpace::ShutdownTypeReboot ? "Reboot" : "PowerOff"), interactive);
535 if (!check.isValid()) {
536 // FIXME: entirely ignoring shutdownMode
537 CKManager().call(QLatin1String(shutdownType == KWorkSpace::ShutdownTypeReboot ? "Restart" : "Stop"));
538 // if even CKManager call fails, there is nothing more to be done
539 }
540 return;
541 }
542
543 cap_ask = false;
544 }
545 if (!cap_ask && shutdownMode == KWorkSpace::ShutdownModeInteractive)
546 shutdownMode = KWorkSpace::ShutdownModeForceNow;
547
548 QByteArray cmd;
549 if (DMType == OldGDM) {
550 cmd.append(shutdownMode == KWorkSpace::ShutdownModeForceNow ? "SET_LOGOUT_ACTION " : "SET_SAFE_LOGOUT_ACTION ");
551 cmd.append(shutdownType == KWorkSpace::ShutdownTypeReboot ? "REBOOT\n" : "HALT\n");
552 } else {
553 cmd.append("shutdown\t");
554 cmd.append(shutdownType == KWorkSpace::ShutdownTypeReboot ? "reboot\t" : "halt\t");
555 if (!bootOption.isEmpty())
556 cmd.append("=").append(bootOption.toLocal8Bit()).append("\t");
557 cmd.append(shutdownMode == KWorkSpace::ShutdownModeInteractive ? "ask\n"
558 : shutdownMode == KWorkSpace::ShutdownModeForceNow ? "forcenow\n"
559 : shutdownMode == KWorkSpace::ShutdownModeTryNow ? "trynow\n"
560 : "schedule\n");
561 }
562 exec(cmd.data());
563}
564
565bool KDisplayManager::bootOptions(QStringList &opts, int &defopt, int &current)
566{
567 if (DMType != NewKDM)
568 return false;
569
570 QByteArray re;
571 if (!exec("listbootoptions\n", re))
572 return false;
573
575 if (opts.size() < 4)
576 return false;
577
578 bool ok;
579 defopt = opts[2].toInt(&ok);
580 if (!ok)
581 return false;
582 current = opts[3].toInt(&ok);
583 if (!ok)
584 return false;
585
586 opts = opts[1].split(' ', Qt::SkipEmptyParts);
587 for (QStringList::Iterator it = opts.begin(); it != opts.end(); ++it)
588 (*it).replace(QLatin1String("\\s"), QLatin1String(" "));
589
590 return true;
591}
592#endif // KDM_NO_SHUTDOWN
593
594bool KDisplayManager::isSwitchable()
595{
596 if (DMType == NewGDM || DMType == LightDM) {
597 QDBusObjectPath currentSeat;
598 if (getCurrentSeat(nullptr, &currentSeat)) {
599 SystemdSeat SDseat(currentSeat);
600 if (SDseat.isValid()) {
601 QVariant prop = SDseat.property("CanMultiSession");
602 if (prop.isValid())
603 return prop.toBool();
604 else {
605 // Newer systemd versions (since 246) don't expose "CanMultiSession" anymore.
606 // It's hidden and always true.
607 // See https://github.com/systemd/systemd/commit/8f8cc84ba4612e74cd1e26898c6816e6e60fc4e9
608 // and https://github.com/systemd/systemd/commit/c2b178d3cacad52eadc30ecc349160bc02d32a9c
609 // So assume that it's supported if the property is invalid.
610 return true;
611 }
612 }
613 CKSeat CKseat(currentSeat);
614 if (CKseat.isValid()) {
615 QDBusReply<bool> r = CKseat.call(QStringLiteral("CanActivateSessions"));
616 if (r.isValid())
617 return r.value();
618 }
619 }
620 return false;
621 }
622
623 if (DMType == OldKDM)
624 return dpy[0] == ':';
625
626 if (DMType == OldGDM)
627 return exec("QUERY_VT\n");
628
629 QByteArray re;
630
631 return exec("caps\n", re) && re.indexOf("\tlocal") >= 0;
632}
633
634int KDisplayManager::numReserve()
635{
636 if (DMType == NewGDM || DMType == OldGDM || DMType == LightDM)
637 return 1; /* Bleh */
638
639 if (DMType == OldKDM)
640 return strstr(ctl, ",rsvd") ? 1 : -1;
641
642 QByteArray re;
643 int p;
644
645 if (!(exec("caps\n", re) && (p = re.indexOf("\treserve ")) >= 0))
646 return -1;
647 return atoi(re.data() + p + 9);
648}
649
650void KDisplayManager::startReserve()
651{
652 if (DMType == NewGDM)
653 GDMFactory().call(QStringLiteral("CreateTransientDisplay"));
654 else if (DMType == OldGDM)
655 exec("FLEXI_XSERVER\n");
656 else if (DMType == LightDM) {
657 LightDMDBus lightDM;
658 lightDM.call(QStringLiteral("SwitchToGreeter"));
659 } else
660 exec("reserve\n");
661}
662
663bool KDisplayManager::localSessions(SessList &list)
664{
665 if (DMType == OldKDM)
666 return false;
667
668 if (DMType == NewGDM || DMType == LightDM) {
669 QDBusObjectPath currentSession, currentSeat;
670 if (getCurrentSeat(&currentSession, &currentSeat)) {
671 // we'll divide the code in two branches to reduce the overhead of calls to non-existent services
672 // systemd part // preferred
673 if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_SERVICE)) {
674 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
675 for (const QDBusObjectPath &sp : sessionsForSeat) {
676 SystemdSession lsess(sp);
677 if (lsess.isValid()) {
678 SessEnt se;
679 lsess.getSessionLocation(se);
680 if ((lsess.property("Class").toString() != QLatin1String("greeter"))
681 && (lsess.property("State").toString() == QLatin1String("online")
682 || lsess.property("State").toString() == QLatin1String("active"))) {
683 NumberedDBusObjectPath numberedPath = lsess.getUser();
684 se.display = lsess.property("Display").toString();
685 se.vt = lsess.property("VTNr").toInt();
686 se.user = KUser(K_UID(numberedPath.num)).loginName();
687 /* TODO:
688 * regarding the session name in this, it IS possible to find it out - logind tracks the session leader PID
689 * the problem is finding out the name of the process, I could come only with reading /proc/PID/comm which
690 * doesn't seem exactly... right to me --mbriza
691 */
692 se.session = QStringLiteral("<unknown>");
693
694 se.self = lsess.property("Id").toString() == qgetenv("XDG_SESSION_ID");
695 se.tty = !lsess.property("TTY").toString().isEmpty();
696 }
697 list.append(se);
698 }
699 }
700 }
701 // ConsoleKit part
702 else if (QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.ConsoleKit"))) {
703 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
704 for (const QDBusObjectPath &sp : sessionsForSeat) {
705 CKSession lsess(sp);
706 if (lsess.isValid()) {
707 SessEnt se;
708 lsess.getSessionLocation(se);
709 // "Warning: we haven't yet defined the allowed values for this property.
710 // It is probably best to avoid this until we do."
711 QDBusReply<QString> r = lsess.call(QStringLiteral("GetSessionType"));
712 if (r.value() != QLatin1String("LoginWindow")) {
713 QDBusReply<unsigned> r2 = lsess.call(QStringLiteral("GetUnixUser"));
714 se.user = KUser(K_UID(r2.value())).loginName();
715 se.session = QStringLiteral("<unknown>");
716 }
717 se.self = (sp == currentSession);
718 list.append(se);
719 }
720 }
721 } else {
722 return false;
723 }
724 return true;
725 }
726 return false;
727 }
728
729 QByteArray re;
730
731 if (DMType == OldGDM) {
732 if (!exec("CONSOLE_SERVERS\n", re))
733 return false;
734 const QStringList sess = QString(re.data() + 3).split(QChar(';'), Qt::SkipEmptyParts);
735 for (QStringList::ConstIterator it = sess.constBegin(); it != sess.constEnd(); ++it) {
736 QStringList ts = (*it).split(QChar(','));
737 SessEnt se;
738 se.display = ts[0];
739 se.user = ts[1];
740 se.vt = ts[2].toInt();
741 se.session = QStringLiteral("<unknown>");
742 se.self = ts[0] == ::getenv("DISPLAY"); /* Bleh */
743 se.tty = false;
744 list.append(se);
745 }
746 } else {
747 if (!exec("list\talllocal\n", re))
748 return false;
749 const QStringList sess = QString(re.data() + 3).split(QChar('\t'), Qt::SkipEmptyParts);
750 for (QStringList::ConstIterator it = sess.constBegin(); it != sess.constEnd(); ++it) {
751 QStringList ts = (*it).split(QChar(','));
752 SessEnt se;
753 se.display = ts[0];
754 se.vt = QStringView(ts[1]).mid(2).toInt();
755 se.user = ts[2];
756 se.session = ts[3];
757 se.self = (ts[4].indexOf('*') >= 0);
758 se.tty = (ts[4].indexOf('t') >= 0);
759 list.append(se);
760 }
761 }
762 return true;
763}
764
765void KDisplayManager::sess2Str2(const SessEnt &se, QString &user, QString &loc)
766{
767 if (se.tty) {
768 user = i18nc("user: …", "%1: TTY login", se.user);
769 loc = se.vt ? QStringLiteral("vt%1").arg(se.vt) : se.display;
770 } else {
771 // clang-format off
772 user = se.user.isEmpty() ? se.session.isEmpty()
773 ? i18nc("… location (TTY or X display)", "Unused") : se.session == QLatin1String("<remote>")
774 ? i18n("X login on remote host") : i18nc("… host", "X login on %1", se.session)
775 : se.session == QLatin1String("<unknown>")
776 ? se.user : i18nc("user: session type", "%1: %2", se.user, se.session);
777 // clang-format on
778 loc = se.vt ? QStringLiteral("%1, vt%2").arg(se.display).arg(se.vt) : se.display;
779 }
780}
781
782QString KDisplayManager::sess2Str(const SessEnt &se)
783{
784 QString user, loc;
785
786 sess2Str2(se, user, loc);
787 return i18nc("session (location)", "%1 (%2)", user, loc);
788}
789
790bool KDisplayManager::switchVT(int vt)
791{
792 if (DMType == NewGDM || DMType == LightDM) {
793 QDBusObjectPath currentSeat;
794 if (getCurrentSeat(nullptr, &currentSeat)) {
795 // systemd part // preferred
796 if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_SERVICE)) {
797 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
798 for (const QDBusObjectPath &sp : sessionsForSeat) {
799 SystemdSession lsess(sp);
800 if (lsess.isValid()) {
801 SessEnt se;
802 lsess.getSessionLocation(se);
803 if (se.vt == vt) {
804 lsess.call(SYSTEMD_SWITCH_CALL);
805 return true;
806 }
807 }
808 }
809 }
810 // ConsoleKit part
811 else if (QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.ConsoleKit"))) {
812 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
813 for (const QDBusObjectPath &sp : sessionsForSeat) {
814 CKSession lsess(sp);
815 if (lsess.isValid()) {
816 SessEnt se;
817 lsess.getSessionLocation(se);
818 if (se.vt == vt) {
819 if (se.tty) // ConsoleKit simply ignores these
820 return false;
821 lsess.call(QStringLiteral("Activate"));
822 return true;
823 }
824 }
825 }
826 }
827 }
828 return false;
829 }
830
831 if (DMType == OldGDM)
832 return exec(QStringLiteral("SET_VT %1\n").arg(vt).toLatin1());
833
834 return exec(QStringLiteral("activate\tvt%1\n").arg(vt).toLatin1());
835}
836
837void KDisplayManager::lockSwitchVT(int vt)
838{
839 // Lock first, otherwise the lock won't be able to kick in until the session is re-activated.
840 QDBusInterface screensaver(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"));
841 screensaver.call(QStringLiteral("Lock"));
842
843 switchVT(vt);
844}
845
846void KDisplayManager::GDMAuthenticate()
847{
848#if HAVE_X11
849 FILE *fp;
850 const char *dpy = nullptr, *dnum, *dne;
851 int dnl;
852 Xauth *xau;
853
854 if (auto instance = qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
855 if (auto nativeInterface = instance->nativeInterface<QNativeInterface::QX11Application>()) {
856 dpy = DisplayString(nativeInterface->display());
857 }
858 }
859
860 if (!dpy) {
861 dpy = ::getenv("DISPLAY");
862 if (!dpy)
863 return;
864 }
865 dnum = strchr(dpy, ':') + 1;
866 dne = strchr(dpy, '.');
867 dnl = dne ? dne - dnum : strlen(dnum);
868
869 /* XXX should do locking */
870 if (!(fp = fopen(XauFileName(), "r")))
871 return;
872
873 while ((xau = XauReadAuth(fp))) {
874 if (xau->family == FamilyLocal && xau->number_length == dnl && !memcmp(xau->number, dnum, dnl) && xau->data_length == 16 && xau->name_length == 18
875 && !memcmp(xau->name, "MIT-MAGIC-COOKIE-1", 18)) {
876 QString cmd(QStringLiteral("AUTH_LOCAL "));
877 for (int i = 0; i < 16; i++)
878 cmd += QString::number((uchar)xau->data[i], 16).rightJustified(2, '0');
879 cmd += '\n';
880 if (exec(cmd.toLatin1())) {
881 XauDisposeAuth(xau);
882 break;
883 }
884 }
885 XauDisposeAuth(xau);
886 }
887
888 fclose(fp);
889#endif // HAVE_X11
890}
QString loginName() const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QString path(const QString &relativePath)
bool isValid(QStringView ifopt)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & close()
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
QByteArray & append(QByteArrayView data)
char * data()
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
void resize(qsizetype newSize, char c)
qsizetype size() const const
qint64 applicationPid()
QCoreApplication * instance()
QDBusMessage call(QDBus::CallMode mode, const QString &method, Args &&... args)
QString interface() const const
bool isValid() const const
QString path() const const
QString service() const const
void beginStructure()
void endStructure()
QDBusMessage call(const QDBusMessage &message, QDBus::CallMode mode, int timeout) const const
QDBusConnection systemBus()
QList< QVariant > arguments() const const
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
bool isValid() const const
typedef ConstIterator
typedef Iterator
void append(QList< T > &&value)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator end()
void replace(qsizetype i, parameter_type value)
qsizetype size() const const
QVariant property(const char *name) const const
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString rightJustified(qsizetype width, QChar fill, bool truncate) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLocal8Bit() const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
QStringView mid(qsizetype start, qsizetype length) const const
int toInt(bool *ok, int base) const const
SkipEmptyParts
bool isValid() const const
bool toBool() const const
int toInt(bool *ok) const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:42 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.