You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
888 lines
31 KiB
888 lines
31 KiB
/* |
|
SPDX-FileCopyrightText: 2004 Oswald Buddenhagen <ossi@kde.org> |
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later |
|
*/ |
|
|
|
#include "kdisplaymanager.h" |
|
|
|
#if HAVE_X11 |
|
|
|
#include <kuser.h> |
|
|
|
#include <KLocalizedString> |
|
|
|
#include <QCoreApplication> |
|
#include <QDBusArgument> |
|
#include <QDBusConnectionInterface> |
|
#include <QDBusInterface> |
|
#include <QDBusMetaType> |
|
#include <QDBusObjectPath> |
|
#include <QDBusReply> |
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
|
#include <private/qtx11extras_p.h> |
|
#else |
|
#include <QX11Info> |
|
#endif |
|
|
|
#include <X11/Xauth.h> |
|
#include <X11/Xlib.h> |
|
|
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <sys/socket.h> |
|
#include <sys/types.h> |
|
#include <sys/un.h> |
|
#include <unistd.h> |
|
|
|
#define _DBUS_PROPERTIES_IFACE "org.freedesktop.DBus.Properties" |
|
#define _DBUS_PROPERTIES_GET "Get" |
|
|
|
#define DBUS_PROPERTIES_IFACE QLatin1String(_DBUS_PROPERTIES_IFACE) |
|
#define DBUS_PROPERTIES_GET QLatin1String(_DBUS_PROPERTIES_GET) |
|
|
|
#define _SYSTEMD_SERVICE "org.freedesktop.login1" |
|
#define _SYSTEMD_BASE_PATH "/org/freedesktop/login1" |
|
#define _SYSTEMD_MANAGER_IFACE _SYSTEMD_SERVICE ".Manager" |
|
#define _SYSTEMD_SESSION_BASE_PATH _SYSTEMD_BASE_PATH "/session" |
|
#define _SYSTEMD_SEAT_IFACE _SYSTEMD_SERVICE ".Seat" |
|
#define _SYSTEMD_SEAT_BASE_PATH _SYSTEMD_BASE_PATH "/seat" |
|
#define _SYSTEMD_SESSION_IFACE _SYSTEMD_SERVICE ".Session" |
|
#define _SYSTEMD_USER_PROPERTY "User" |
|
#define _SYSTEMD_SEAT_PROPERTY "Seat" |
|
#define _SYSTEMD_SESSIONS_PROPERTY "Sessions" |
|
#define _SYSTEMD_SWITCH_PROPERTY "Activate" |
|
|
|
#define SYSTEMD_SERVICE QLatin1String(_SYSTEMD_SERVICE) |
|
#define SYSTEMD_BASE_PATH QLatin1String(_SYSTEMD_BASE_PATH) |
|
#define SYSTEMD_MANAGER_IFACE QLatin1String(_SYSTEMD_MANAGER_IFACE) |
|
#define SYSTEMD_SESSION_BASE_PATH QLatin1String(_SYSTEMD_SESSION_BASE_PATH) |
|
#define SYSTEMD_SEAT_IFACE QLatin1String(_SYSTEMD_SEAT_IFACE) |
|
#define SYSTEMD_SEAT_BASE_PATH QLatin1String(_SYSTEMD_SEAT_BASE_PATH) |
|
#define SYSTEMD_SESSION_IFACE QLatin1String(_SYSTEMD_SESSION_IFACE) |
|
#define SYSTEMD_USER_PROPERTY QLatin1String(_SYSTEMD_USER_PROPERTY) |
|
#define SYSTEMD_SEAT_PROPERTY QLatin1String(_SYSTEMD_SEAT_PROPERTY) |
|
#define SYSTEMD_SESSIONS_PROPERTY QLatin1String(_SYSTEMD_SESSIONS_PROPERTY) |
|
#define SYSTEMD_SWITCH_CALL QLatin1String(_SYSTEMD_SWITCH_PROPERTY) |
|
|
|
struct NamedDBusObjectPath { |
|
QString name; |
|
QDBusObjectPath path; |
|
}; |
|
Q_DECLARE_METATYPE(NamedDBusObjectPath) |
|
Q_DECLARE_METATYPE(QList<NamedDBusObjectPath>) |
|
|
|
// Marshall the NamedDBusObjectPath data into a D-Bus argument |
|
QDBusArgument &operator<<(QDBusArgument &argument, const NamedDBusObjectPath &namedPath) |
|
{ |
|
argument.beginStructure(); |
|
argument << namedPath.name << namedPath.path; |
|
argument.endStructure(); |
|
return argument; |
|
} |
|
|
|
// Retrieve the NamedDBusObjectPath data from the D-Bus argument |
|
const QDBusArgument &operator>>(const QDBusArgument &argument, NamedDBusObjectPath &namedPath) |
|
{ |
|
argument.beginStructure(); |
|
argument >> namedPath.name >> namedPath.path; |
|
argument.endStructure(); |
|
return argument; |
|
} |
|
|
|
struct NumberedDBusObjectPath { |
|
uint num; |
|
QDBusObjectPath path; |
|
}; |
|
Q_DECLARE_METATYPE(NumberedDBusObjectPath) |
|
|
|
// Marshall the NumberedDBusObjectPath data into a D-Bus argument |
|
QDBusArgument &operator<<(QDBusArgument &argument, const NumberedDBusObjectPath &numberedPath) |
|
{ |
|
argument.beginStructure(); |
|
argument << numberedPath.num << numberedPath.path; |
|
argument.endStructure(); |
|
return argument; |
|
} |
|
|
|
// Retrieve the NumberedDBusObjectPath data from the D-Bus argument |
|
const QDBusArgument &operator>>(const QDBusArgument &argument, NumberedDBusObjectPath &numberedPath) |
|
{ |
|
argument.beginStructure(); |
|
argument >> numberedPath.num >> numberedPath.path; |
|
argument.endStructure(); |
|
return argument; |
|
} |
|
|
|
class SystemdManager : public QDBusInterface |
|
{ |
|
public: |
|
SystemdManager() |
|
: QDBusInterface(SYSTEMD_SERVICE, SYSTEMD_BASE_PATH, SYSTEMD_MANAGER_IFACE, QDBusConnection::systemBus()) |
|
{ |
|
} |
|
}; |
|
|
|
class SystemdSeat : public QDBusInterface |
|
{ |
|
public: |
|
SystemdSeat(const QDBusObjectPath &path) |
|
: QDBusInterface(SYSTEMD_SERVICE, path.path(), SYSTEMD_SEAT_IFACE, QDBusConnection::systemBus()) |
|
{ |
|
} |
|
/* HACK to be able to extract a(so) type from QDBus, property doesn't do the trick */ |
|
QList<NamedDBusObjectPath> getSessions() |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET); |
|
message << interface() << SYSTEMD_SESSIONS_PROPERTY; |
|
QDBusMessage reply = QDBusConnection::systemBus().call(message); |
|
|
|
QVariantList args = reply.arguments(); |
|
if (!args.isEmpty()) { |
|
QList<NamedDBusObjectPath> namedPathList = |
|
qdbus_cast<QList<NamedDBusObjectPath>>(args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>()); |
|
return namedPathList; |
|
} |
|
return QList<NamedDBusObjectPath>(); |
|
} |
|
}; |
|
|
|
class SystemdSession : public QDBusInterface |
|
{ |
|
public: |
|
SystemdSession(const QDBusObjectPath &path) |
|
: QDBusInterface(SYSTEMD_SERVICE, path.path(), SYSTEMD_SESSION_IFACE, QDBusConnection::systemBus()) |
|
{ |
|
} |
|
/* HACK to be able to extract (so) type from QDBus, property doesn't do the trick */ |
|
NamedDBusObjectPath getSeat() |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET); |
|
message << interface() << SYSTEMD_SEAT_PROPERTY; |
|
QDBusMessage reply = QDBusConnection::systemBus().call(message); |
|
|
|
QVariantList args = reply.arguments(); |
|
if (!args.isEmpty()) { |
|
NamedDBusObjectPath namedPath; |
|
args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> namedPath; |
|
return namedPath; |
|
} |
|
return NamedDBusObjectPath(); |
|
} |
|
NumberedDBusObjectPath getUser() |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET); |
|
message << interface() << SYSTEMD_USER_PROPERTY; |
|
QDBusMessage reply = QDBusConnection::systemBus().call(message); |
|
|
|
QVariantList args = reply.arguments(); |
|
if (!args.isEmpty()) { |
|
NumberedDBusObjectPath numberedPath; |
|
args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> numberedPath; |
|
return numberedPath; |
|
} |
|
return NumberedDBusObjectPath(); |
|
} |
|
void getSessionLocation(SessEnt &se) |
|
{ |
|
se.tty = (property("Type").toString() != QLatin1String("x11")); |
|
se.display = property(se.tty ? "TTY" : "Display").toString(); |
|
se.vt = property("VTNr").toInt(); |
|
} |
|
}; |
|
|
|
class CKManager : public QDBusInterface |
|
{ |
|
public: |
|
CKManager() |
|
: QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"), |
|
QStringLiteral("/org/freedesktop/ConsoleKit/Manager"), |
|
QStringLiteral("org.freedesktop.ConsoleKit.Manager"), |
|
QDBusConnection::systemBus()) |
|
{ |
|
} |
|
}; |
|
|
|
class CKSeat : public QDBusInterface |
|
{ |
|
public: |
|
CKSeat(const QDBusObjectPath &path) |
|
: QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"), |
|
path.path(), |
|
QStringLiteral("org.freedesktop.ConsoleKit.Seat"), |
|
QDBusConnection::systemBus()) |
|
{ |
|
} |
|
}; |
|
|
|
class CKSession : public QDBusInterface |
|
{ |
|
public: |
|
CKSession(const QDBusObjectPath &path) |
|
: QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"), |
|
path.path(), |
|
QStringLiteral("org.freedesktop.ConsoleKit.Session"), |
|
QDBusConnection::systemBus()) |
|
{ |
|
} |
|
void getSessionLocation(SessEnt &se) |
|
{ |
|
QString tty; |
|
QDBusReply<QString> r = call(QStringLiteral("GetX11Display")); |
|
if (r.isValid() && !r.value().isEmpty()) { |
|
QDBusReply<QString> r2 = call(QStringLiteral("GetX11DisplayDevice")); |
|
tty = r2.value(); |
|
se.display = r.value(); |
|
se.tty = false; |
|
} else { |
|
QDBusReply<QString> r2 = call(QStringLiteral("GetDisplayDevice")); |
|
tty = r2.value(); |
|
se.display = tty; |
|
se.tty = true; |
|
} |
|
se.vt = QStringView(tty).mid(strlen("/dev/tty")).toInt(); |
|
} |
|
}; |
|
|
|
class GDMFactory : public QDBusInterface |
|
{ |
|
public: |
|
GDMFactory() |
|
: QDBusInterface(QStringLiteral("org.gnome.DisplayManager"), |
|
QStringLiteral("/org/gnome/DisplayManager/LocalDisplayFactory"), |
|
QStringLiteral("org.gnome.DisplayManager.LocalDisplayFactory"), |
|
QDBusConnection::systemBus()) |
|
{ |
|
} |
|
}; |
|
|
|
class LightDMDBus : public QDBusInterface |
|
{ |
|
public: |
|
LightDMDBus() |
|
: QDBusInterface(QStringLiteral("org.freedesktop.DisplayManager"), |
|
qgetenv("XDG_SEAT_PATH"), |
|
QStringLiteral("org.freedesktop.DisplayManager.Seat"), |
|
QDBusConnection::systemBus()) |
|
{ |
|
} |
|
}; |
|
|
|
static enum { |
|
Dunno, |
|
NoDM, |
|
NewKDM, |
|
OldKDM, |
|
NewGDM, |
|
OldGDM, |
|
LightDM, |
|
} DMType = Dunno; |
|
static const char *ctl, *dpy; |
|
|
|
class KDisplayManager::Private |
|
{ |
|
public: |
|
Private() |
|
: fd(-1) |
|
{ |
|
} |
|
~Private() |
|
{ |
|
if (fd >= 0) |
|
close(fd); |
|
} |
|
|
|
int fd; |
|
}; |
|
|
|
KDisplayManager::KDisplayManager() |
|
: d(new Private) |
|
{ |
|
const char *ptr; |
|
struct sockaddr_un sa; |
|
|
|
qDBusRegisterMetaType<NamedDBusObjectPath>(); |
|
qDBusRegisterMetaType<QList<NamedDBusObjectPath>>(); |
|
qDBusRegisterMetaType<NumberedDBusObjectPath>(); |
|
|
|
if (DMType == Dunno) { |
|
dpy = ::getenv("DISPLAY"); |
|
if (dpy && (ctl = ::getenv("DM_CONTROL"))) |
|
DMType = NewKDM; |
|
else if (dpy && (ctl = ::getenv("XDM_MANAGED")) && ctl[0] == '/') |
|
DMType = OldKDM; |
|
else if (::getenv("XDG_SEAT_PATH") && LightDMDBus().isValid()) |
|
DMType = LightDM; |
|
else if (::getenv("GDMSESSION")) |
|
DMType = GDMFactory().isValid() ? NewGDM : OldGDM; |
|
else |
|
DMType = NoDM; |
|
} |
|
switch (DMType) { |
|
default: |
|
return; |
|
case NewKDM: |
|
case OldGDM: |
|
if ((d->fd = ::socket(PF_UNIX, SOCK_STREAM, 0)) < 0) |
|
return; |
|
sa.sun_family = AF_UNIX; |
|
if (DMType == OldGDM) { |
|
strcpy(sa.sun_path, "/var/run/gdm_socket"); |
|
if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) { |
|
strcpy(sa.sun_path, "/tmp/.gdm_socket"); |
|
if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) { |
|
::close(d->fd); |
|
d->fd = -1; |
|
break; |
|
} |
|
} |
|
GDMAuthenticate(); |
|
} else { |
|
if ((ptr = strchr(dpy, ':'))) |
|
ptr = strchr(ptr, '.'); |
|
snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/dmctl-%.*s/socket", ctl, ptr ? int(ptr - dpy) : 512, dpy); |
|
if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) { |
|
::close(d->fd); |
|
d->fd = -1; |
|
} |
|
} |
|
break; |
|
case OldKDM: { |
|
QString tf(ctl); |
|
tf.truncate(tf.indexOf(',')); |
|
d->fd = ::open(tf.toLatin1(), O_WRONLY); |
|
} break; |
|
} |
|
} |
|
|
|
KDisplayManager::~KDisplayManager() |
|
{ |
|
delete d; |
|
} |
|
|
|
bool KDisplayManager::exec(const char *cmd) |
|
{ |
|
QByteArray buf; |
|
|
|
return exec(cmd, buf); |
|
} |
|
|
|
/** |
|
* Execute a KDM/GDM remote control command. |
|
* @param cmd the command to execute. FIXME: undocumented yet. |
|
* @param buf the result buffer. |
|
* @return result: |
|
* @li If true, the command was successfully executed. |
|
* @p ret might contain additional results. |
|
* @li If false and @p ret is empty, a communication error occurred |
|
* (most probably KDM is not running). |
|
* @li If false and @p ret is non-empty, it contains the error message |
|
* from KDM. |
|
*/ |
|
bool KDisplayManager::exec(const char *cmd, QByteArray &buf) |
|
{ |
|
bool ret = false; |
|
int tl; |
|
int len = 0; |
|
|
|
if (d->fd < 0) |
|
goto busted; |
|
|
|
tl = strlen(cmd); |
|
if (::write(d->fd, cmd, tl) != tl) { |
|
bust: |
|
::close(d->fd); |
|
d->fd = -1; |
|
busted: |
|
buf.resize(0); |
|
return false; |
|
} |
|
if (DMType == OldKDM) { |
|
buf.resize(0); |
|
return true; |
|
} |
|
for (;;) { |
|
if (buf.size() < 128) |
|
buf.resize(128); |
|
else if (buf.size() < len * 2) |
|
buf.resize(len * 2); |
|
if ((tl = ::read(d->fd, buf.data() + len, buf.size() - len)) <= 0) { |
|
if (tl < 0 && errno == EINTR) |
|
continue; |
|
goto bust; |
|
} |
|
len += tl; |
|
if (buf[len - 1] == '\n') { |
|
buf[len - 1] = 0; |
|
if (len > 2 && (buf[0] == 'o' || buf[0] == 'O') && (buf[1] == 'k' || buf[1] == 'K') && buf[2] <= ' ') |
|
ret = true; |
|
break; |
|
} |
|
} |
|
return ret; |
|
} |
|
|
|
static bool getCurrentSeat(QDBusObjectPath *currentSession, QDBusObjectPath *currentSeat) |
|
{ |
|
SystemdManager man; |
|
if (man.isValid()) { |
|
*currentSeat = QDBusObjectPath(_SYSTEMD_SEAT_BASE_PATH "/auto"); |
|
SystemdSeat seat(*currentSeat); |
|
if (seat.property("Id").isValid()) { // query an arbitrary property to confirm the path is valid |
|
return true; |
|
} |
|
|
|
// auto is newer and may not exist on all platforms, fallback to GetSessionByPID if the above failed |
|
|
|
QDBusReply<QDBusObjectPath> r = man.call(QStringLiteral("GetSessionByPID"), (uint)QCoreApplication::applicationPid()); |
|
if (r.isValid()) { |
|
SystemdSession sess(r.value()); |
|
if (sess.isValid()) { |
|
NamedDBusObjectPath namedPath = sess.getSeat(); |
|
*currentSeat = namedPath.path; |
|
return true; |
|
} |
|
} |
|
} else { |
|
CKManager man; |
|
QDBusReply<QDBusObjectPath> r = man.call(QStringLiteral("GetCurrentSession")); |
|
if (r.isValid()) { |
|
CKSession sess(r.value()); |
|
if (sess.isValid()) { |
|
QDBusReply<QDBusObjectPath> r2 = sess.call(QStringLiteral("GetSeatId")); |
|
if (r2.isValid()) { |
|
if (currentSession) |
|
*currentSession = r.value(); |
|
*currentSeat = r2.value(); |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
static QList<QDBusObjectPath> getSessionsForSeat(const QDBusObjectPath &path) |
|
{ |
|
if (path.path().startsWith(SYSTEMD_BASE_PATH)) { // systemd path incoming |
|
SystemdSeat seat(path); |
|
if (seat.isValid()) { |
|
QList<NamedDBusObjectPath> r = seat.getSessions(); |
|
QList<QDBusObjectPath> result; |
|
foreach (const NamedDBusObjectPath &namedPath, r) |
|
result.append(namedPath.path); |
|
// This pretty much can't contain any other than local sessions as the seat is retrieved from the current session |
|
return result; |
|
} |
|
} else if (path.path().startsWith(QLatin1String("/org/freedesktop/ConsoleKit"))) { |
|
CKSeat seat(path); |
|
if (seat.isValid()) { |
|
QDBusReply<QList<QDBusObjectPath>> r = seat.call(QStringLiteral("GetSessions")); |
|
if (r.isValid()) { |
|
// This will contain only local sessions: |
|
// - this is only ever called when isSwitchable() is true => local seat |
|
// - remote logins into the machine are assigned to other seats |
|
return r.value(); |
|
} |
|
} |
|
} |
|
return QList<QDBusObjectPath>(); |
|
} |
|
|
|
#ifndef KDM_NO_SHUTDOWN |
|
bool KDisplayManager::canShutdown() |
|
{ |
|
if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) { |
|
QDBusReply<QString> canPowerOff = SystemdManager().call(QStringLiteral("CanPowerOff")); |
|
if (canPowerOff.isValid()) |
|
return canPowerOff.value() != QLatin1String("no"); |
|
QDBusReply<bool> canStop = CKManager().call(QStringLiteral("CanStop")); |
|
if (canStop.isValid()) |
|
return canStop.value(); |
|
return false; |
|
} |
|
|
|
if (DMType == OldKDM) |
|
return strstr(ctl, ",maysd") != nullptr; |
|
|
|
QByteArray re; |
|
|
|
if (DMType == OldGDM) |
|
return exec("QUERY_LOGOUT_ACTION\n", re) && re.indexOf("HALT") >= 0; |
|
|
|
return exec("caps\n", re) && re.indexOf("\tshutdown") >= 0; |
|
} |
|
|
|
void KDisplayManager::shutdown(KWorkSpace::ShutdownType shutdownType, |
|
KWorkSpace::ShutdownMode shutdownMode, /* NOT Default */ |
|
const QString &bootOption) |
|
{ |
|
if (shutdownType == KWorkSpace::ShutdownTypeNone || shutdownType == KWorkSpace::ShutdownTypeLogout) |
|
return; |
|
|
|
bool cap_ask; |
|
if (DMType == NewKDM) { |
|
QByteArray re; |
|
cap_ask = exec("caps\n", re) && re.indexOf("\tshutdown ask") >= 0; |
|
} else { |
|
if (!bootOption.isEmpty()) |
|
return; |
|
|
|
if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) { |
|
// systemd supports only 2 modes: |
|
// * interactive = true: brings up a PolicyKit prompt if other sessions are active |
|
// * interactive = false: rejects the shutdown if other sessions are active |
|
// There are no schedule or force modes. |
|
// We try to map our 4 shutdown modes in the sanest way. |
|
bool interactive = (shutdownMode == KWorkSpace::ShutdownModeInteractive || shutdownMode == KWorkSpace::ShutdownModeForceNow); |
|
QDBusReply<QString> check = |
|
SystemdManager().call(QLatin1String(shutdownType == KWorkSpace::ShutdownTypeReboot ? "Reboot" : "PowerOff"), interactive); |
|
if (!check.isValid()) { |
|
// FIXME: entirely ignoring shutdownMode |
|
CKManager().call(QLatin1String(shutdownType == KWorkSpace::ShutdownTypeReboot ? "Restart" : "Stop")); |
|
// if even CKManager call fails, there is nothing more to be done |
|
} |
|
return; |
|
} |
|
|
|
cap_ask = false; |
|
} |
|
if (!cap_ask && shutdownMode == KWorkSpace::ShutdownModeInteractive) |
|
shutdownMode = KWorkSpace::ShutdownModeForceNow; |
|
|
|
QByteArray cmd; |
|
if (DMType == OldGDM) { |
|
cmd.append(shutdownMode == KWorkSpace::ShutdownModeForceNow ? "SET_LOGOUT_ACTION " : "SET_SAFE_LOGOUT_ACTION "); |
|
cmd.append(shutdownType == KWorkSpace::ShutdownTypeReboot ? "REBOOT\n" : "HALT\n"); |
|
} else { |
|
cmd.append("shutdown\t"); |
|
cmd.append(shutdownType == KWorkSpace::ShutdownTypeReboot ? "reboot\t" : "halt\t"); |
|
if (!bootOption.isEmpty()) |
|
cmd.append("=").append(bootOption.toLocal8Bit()).append("\t"); |
|
cmd.append(shutdownMode == KWorkSpace::ShutdownModeInteractive ? "ask\n" |
|
: shutdownMode == KWorkSpace::ShutdownModeForceNow ? "forcenow\n" |
|
: shutdownMode == KWorkSpace::ShutdownModeTryNow ? "trynow\n" |
|
: "schedule\n"); |
|
} |
|
exec(cmd.data()); |
|
} |
|
|
|
bool KDisplayManager::bootOptions(QStringList &opts, int &defopt, int ¤t) |
|
{ |
|
if (DMType != NewKDM) |
|
return false; |
|
|
|
QByteArray re; |
|
if (!exec("listbootoptions\n", re)) |
|
return false; |
|
|
|
opts = QString::fromLocal8Bit(re.data()).split('\t', Qt::SkipEmptyParts); |
|
if (opts.size() < 4) |
|
return false; |
|
|
|
bool ok; |
|
defopt = opts[2].toInt(&ok); |
|
if (!ok) |
|
return false; |
|
current = opts[3].toInt(&ok); |
|
if (!ok) |
|
return false; |
|
|
|
opts = opts[1].split(' ', Qt::SkipEmptyParts); |
|
for (QStringList::Iterator it = opts.begin(); it != opts.end(); ++it) |
|
(*it).replace(QLatin1String("\\s"), QLatin1String(" ")); |
|
|
|
return true; |
|
} |
|
#endif // KDM_NO_SHUTDOWN |
|
|
|
bool KDisplayManager::isSwitchable() |
|
{ |
|
if (DMType == NewGDM || DMType == LightDM) { |
|
QDBusObjectPath currentSeat; |
|
if (getCurrentSeat(nullptr, ¤tSeat)) { |
|
SystemdSeat SDseat(currentSeat); |
|
if (SDseat.isValid()) { |
|
QVariant prop = SDseat.property("CanMultiSession"); |
|
if (prop.isValid()) |
|
return prop.toBool(); |
|
else { |
|
// Newer systemd versions (since 246) don't expose "CanMultiSession" anymore. |
|
// It's hidden and always true. |
|
// See https://github.com/systemd/systemd/commit/8f8cc84ba4612e74cd1e26898c6816e6e60fc4e9 |
|
// and https://github.com/systemd/systemd/commit/c2b178d3cacad52eadc30ecc349160bc02d32a9c |
|
// So assume that it's supported if the property is invalid. |
|
return true; |
|
} |
|
} |
|
CKSeat CKseat(currentSeat); |
|
if (CKseat.isValid()) { |
|
QDBusReply<bool> r = CKseat.call(QStringLiteral("CanActivateSessions")); |
|
if (r.isValid()) |
|
return r.value(); |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
if (DMType == OldKDM) |
|
return dpy[0] == ':'; |
|
|
|
if (DMType == OldGDM) |
|
return exec("QUERY_VT\n"); |
|
|
|
QByteArray re; |
|
|
|
return exec("caps\n", re) && re.indexOf("\tlocal") >= 0; |
|
} |
|
|
|
int KDisplayManager::numReserve() |
|
{ |
|
if (DMType == NewGDM || DMType == OldGDM || DMType == LightDM) |
|
return 1; /* Bleh */ |
|
|
|
if (DMType == OldKDM) |
|
return strstr(ctl, ",rsvd") ? 1 : -1; |
|
|
|
QByteArray re; |
|
int p; |
|
|
|
if (!(exec("caps\n", re) && (p = re.indexOf("\treserve ")) >= 0)) |
|
return -1; |
|
return atoi(re.data() + p + 9); |
|
} |
|
|
|
void KDisplayManager::startReserve() |
|
{ |
|
if (DMType == NewGDM) |
|
GDMFactory().call(QStringLiteral("CreateTransientDisplay")); |
|
else if (DMType == OldGDM) |
|
exec("FLEXI_XSERVER\n"); |
|
else if (DMType == LightDM) { |
|
LightDMDBus lightDM; |
|
lightDM.call(QStringLiteral("SwitchToGreeter")); |
|
} else |
|
exec("reserve\n"); |
|
} |
|
|
|
bool KDisplayManager::localSessions(SessList &list) |
|
{ |
|
if (DMType == OldKDM) |
|
return false; |
|
|
|
if (DMType == NewGDM || DMType == LightDM) { |
|
QDBusObjectPath currentSession, currentSeat; |
|
if (getCurrentSeat(¤tSession, ¤tSeat)) { |
|
// we'll divide the code in two branches to reduce the overhead of calls to non-existent services |
|
// systemd part // preferred |
|
if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_SERVICE)) { |
|
foreach (const QDBusObjectPath &sp, getSessionsForSeat(currentSeat)) { |
|
SystemdSession lsess(sp); |
|
if (lsess.isValid()) { |
|
SessEnt se; |
|
lsess.getSessionLocation(se); |
|
if ((lsess.property("Class").toString() != QLatin1String("greeter")) |
|
&& (lsess.property("State").toString() == QLatin1String("online") |
|
|| lsess.property("State").toString() == QLatin1String("active"))) { |
|
NumberedDBusObjectPath numberedPath = lsess.getUser(); |
|
se.display = lsess.property("Display").toString(); |
|
se.vt = lsess.property("VTNr").toInt(); |
|
se.user = KUser(K_UID(numberedPath.num)).loginName(); |
|
/* TODO: |
|
* regarding the session name in this, it IS possible to find it out - logind tracks the session leader PID |
|
* the problem is finding out the name of the process, I could come only with reading /proc/PID/comm which |
|
* doesn't seem exactly... right to me --mbriza |
|
*/ |
|
se.session = QStringLiteral("<unknown>"); |
|
|
|
se.self = lsess.property("Id").toString() == qgetenv("XDG_SESSION_ID"); |
|
se.tty = !lsess.property("TTY").toString().isEmpty(); |
|
} |
|
list.append(se); |
|
} |
|
} |
|
} |
|
// ConsoleKit part |
|
else if (QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.ConsoleKit"))) { |
|
foreach (const QDBusObjectPath &sp, getSessionsForSeat(currentSeat)) { |
|
CKSession lsess(sp); |
|
if (lsess.isValid()) { |
|
SessEnt se; |
|
lsess.getSessionLocation(se); |
|
// "Warning: we haven't yet defined the allowed values for this property. |
|
// It is probably best to avoid this until we do." |
|
QDBusReply<QString> r = lsess.call(QStringLiteral("GetSessionType")); |
|
if (r.value() != QLatin1String("LoginWindow")) { |
|
QDBusReply<unsigned> r2 = lsess.call(QStringLiteral("GetUnixUser")); |
|
se.user = KUser(K_UID(r2.value())).loginName(); |
|
se.session = QStringLiteral("<unknown>"); |
|
} |
|
se.self = (sp == currentSession); |
|
list.append(se); |
|
} |
|
} |
|
} else { |
|
return false; |
|
} |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
QByteArray re; |
|
|
|
if (DMType == OldGDM) { |
|
if (!exec("CONSOLE_SERVERS\n", re)) |
|
return false; |
|
const QStringList sess = QString(re.data() + 3).split(QChar(';'), Qt::SkipEmptyParts); |
|
for (QStringList::ConstIterator it = sess.constBegin(); it != sess.constEnd(); ++it) { |
|
QStringList ts = (*it).split(QChar(',')); |
|
SessEnt se; |
|
se.display = ts[0]; |
|
se.user = ts[1]; |
|
se.vt = ts[2].toInt(); |
|
se.session = QStringLiteral("<unknown>"); |
|
se.self = ts[0] == ::getenv("DISPLAY"); /* Bleh */ |
|
se.tty = false; |
|
list.append(se); |
|
} |
|
} else { |
|
if (!exec("list\talllocal\n", re)) |
|
return false; |
|
const QStringList sess = QString(re.data() + 3).split(QChar('\t'), Qt::SkipEmptyParts); |
|
for (QStringList::ConstIterator it = sess.constBegin(); it != sess.constEnd(); ++it) { |
|
QStringList ts = (*it).split(QChar(',')); |
|
SessEnt se; |
|
se.display = ts[0]; |
|
se.vt = QStringView(ts[1]).mid(2).toInt(); |
|
se.user = ts[2]; |
|
se.session = ts[3]; |
|
se.self = (ts[4].indexOf('*') >= 0); |
|
se.tty = (ts[4].indexOf('t') >= 0); |
|
list.append(se); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
void KDisplayManager::sess2Str2(const SessEnt &se, QString &user, QString &loc) |
|
{ |
|
if (se.tty) { |
|
user = i18nc("user: …", "%1: TTY login", se.user); |
|
loc = se.vt ? QStringLiteral("vt%1").arg(se.vt) : se.display; |
|
} else { |
|
// clang-format off |
|
user = se.user.isEmpty() ? se.session.isEmpty() |
|
? i18nc("… location (TTY or X display)", "Unused") : se.session == QLatin1String("<remote>") |
|
? i18n("X login on remote host") : i18nc("… host", "X login on %1", se.session) |
|
: se.session == QLatin1String("<unknown>") |
|
? se.user : i18nc("user: session type", "%1: %2", se.user, se.session); |
|
// clang-format on |
|
loc = se.vt ? QStringLiteral("%1, vt%2").arg(se.display).arg(se.vt) : se.display; |
|
} |
|
} |
|
|
|
QString KDisplayManager::sess2Str(const SessEnt &se) |
|
{ |
|
QString user, loc; |
|
|
|
sess2Str2(se, user, loc); |
|
return i18nc("session (location)", "%1 (%2)", user, loc); |
|
} |
|
|
|
bool KDisplayManager::switchVT(int vt) |
|
{ |
|
if (DMType == NewGDM || DMType == LightDM) { |
|
QDBusObjectPath currentSeat; |
|
if (getCurrentSeat(nullptr, ¤tSeat)) { |
|
// systemd part // preferred |
|
if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_SERVICE)) { |
|
foreach (const QDBusObjectPath &sp, getSessionsForSeat(currentSeat)) { |
|
SystemdSession lsess(sp); |
|
if (lsess.isValid()) { |
|
SessEnt se; |
|
lsess.getSessionLocation(se); |
|
if (se.vt == vt) { |
|
lsess.call(SYSTEMD_SWITCH_CALL); |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
// ConsoleKit part |
|
else if (QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.ConsoleKit"))) { |
|
foreach (const QDBusObjectPath &sp, getSessionsForSeat(currentSeat)) { |
|
CKSession lsess(sp); |
|
if (lsess.isValid()) { |
|
SessEnt se; |
|
lsess.getSessionLocation(se); |
|
if (se.vt == vt) { |
|
if (se.tty) // ConsoleKit simply ignores these |
|
return false; |
|
lsess.call(QStringLiteral("Activate")); |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
if (DMType == OldGDM) |
|
return exec(QStringLiteral("SET_VT %1\n").arg(vt).toLatin1()); |
|
|
|
return exec(QStringLiteral("activate\tvt%1\n").arg(vt).toLatin1()); |
|
} |
|
|
|
void KDisplayManager::lockSwitchVT(int vt) |
|
{ |
|
// Lock first, otherwise the lock won't be able to kick in until the session is re-activated. |
|
QDBusInterface screensaver(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver")); |
|
screensaver.call(QStringLiteral("Lock")); |
|
|
|
switchVT(vt); |
|
} |
|
|
|
void KDisplayManager::GDMAuthenticate() |
|
{ |
|
FILE *fp; |
|
const char *dpy, *dnum, *dne; |
|
int dnl; |
|
Xauth *xau; |
|
|
|
dpy = DisplayString(QX11Info::display()); |
|
if (!dpy) { |
|
dpy = ::getenv("DISPLAY"); |
|
if (!dpy) |
|
return; |
|
} |
|
dnum = strchr(dpy, ':') + 1; |
|
dne = strchr(dpy, '.'); |
|
dnl = dne ? dne - dnum : strlen(dnum); |
|
|
|
/* XXX should do locking */ |
|
if (!(fp = fopen(XauFileName(), "r"))) |
|
return; |
|
|
|
while ((xau = XauReadAuth(fp))) { |
|
if (xau->family == FamilyLocal && xau->number_length == dnl && !memcmp(xau->number, dnum, dnl) && xau->data_length == 16 && xau->name_length == 18 |
|
&& !memcmp(xau->name, "MIT-MAGIC-COOKIE-1", 18)) { |
|
QString cmd(QStringLiteral("AUTH_LOCAL ")); |
|
for (int i = 0; i < 16; i++) |
|
cmd += QString::number((uchar)xau->data[i], 16).rightJustified(2, '0'); |
|
cmd += '\n'; |
|
if (exec(cmd.toLatin1())) { |
|
XauDisposeAuth(xau); |
|
break; |
|
} |
|
} |
|
XauDisposeAuth(xau); |
|
} |
|
|
|
fclose(fp); |
|
} |
|
|
|
#endif // HAVE_X11
|
|
|