From: Martin Briza Date: Thu, 24 Jan 2013 10:56:15 +0000 Subject: systemd session management support in the workspace X-Git-Url: http://quickgit.kde.org/?p=kde-workspace.git&a=commitdiff&h=2b041abd0f35bfbca4f2aac0cd946bdadd6011d4 --- systemd session management support in the workspace CK code is preserved but systemd is checked first Makes switching users possible REVIEW: 107611 --- --- a/libs/kworkspace/kdisplaymanager.cpp +++ b/libs/kworkspace/kdisplaymanager.cpp @@ -40,6 +40,166 @@ #include #include +#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) + +// 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 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 namedPathList = qdbus_cast< QList >(args.at(0).value().variant().value()); + return namedPathList; + } + return QList(); + } +}; + +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().variant().value() >> 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().variant().value() >> 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: @@ -68,9 +228,26 @@ CKSession(const QDBusObjectPath &path) : QDBusInterface( QLatin1String("org.freedesktop.ConsoleKit"), - path.path(), + path.path(), QLatin1String("org.freedesktop.ConsoleKit.Session"), QDBusConnection::systemBus()) {} + void getSessionLocation(SessEnt &se) + { + QString tty; + QDBusReply r = call(QLatin1String("GetX11Display")); + if (r.isValid() && !r.value().isEmpty()) { + QDBusReply r2 = call(QLatin1String("GetX11DisplayDevice")); + tty = r2.value(); + se.display = r.value(); + se.tty = false; + } else { + QDBusReply r2 = call(QLatin1String("GetDisplayDevice")); + tty = r2.value(); + se.display = tty; + se.tty = true; + } + se.vt = tty.mid(strlen("/dev/tty")).toInt(); + } }; class GDMFactory : public QDBusInterface @@ -114,6 +291,10 @@ { const char *ptr; struct sockaddr_un sa; + + qDBusRegisterMetaType(); + qDBusRegisterMetaType >(); + qDBusRegisterMetaType(); if (DMType == Dunno) { if (!(dpy = ::getenv("DISPLAY"))) @@ -242,17 +423,31 @@ static bool getCurrentSeat(QDBusObjectPath *currentSession, QDBusObjectPath *currentSeat) { - CKManager man; - QDBusReply r = man.call(QLatin1String("GetCurrentSession")); + SystemdManager man; + QDBusReply r = man.call(QLatin1String("GetSessionByPID"), (uint) QCoreApplication::applicationPid()); if (r.isValid()) { - CKSession sess(r.value()); + SystemdSession sess(r.value()); if (sess.isValid()) { - QDBusReply r2 = sess.call(QLatin1String("GetSeatId")); - if (r2.isValid()) { - if (currentSession) - *currentSession = r.value(); - *currentSeat = r2.value(); - return true; + NamedDBusObjectPath namedPath = sess.getSeat(); + if (currentSession) + *currentSession = r.value(); + *currentSeat = namedPath.path; + return true; + } + } + else { + CKManager man; + QDBusReply r = man.call(QLatin1String("GetCurrentSession")); + if (r.isValid()) { + CKSession sess(r.value()); + if (sess.isValid()) { + QDBusReply r2 = sess.call(QLatin1String("GetSeatId")); + if (r2.isValid()) { + if (currentSession) + *currentSession = r.value(); + *currentSeat = r2.value(); + return true; + } } } } @@ -261,35 +456,30 @@ static QList getSessionsForSeat(const QDBusObjectPath &path) { - CKSeat seat(path); - if (seat.isValid()) { - QDBusReply > r = seat.call(QLatin1String("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(); + if (path.path().startsWith(SYSTEMD_BASE_PATH)) { // systemd path incoming + SystemdSeat seat(path); + if (seat.isValid()) { + QList r = seat.getSessions(); + QList 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("/org/freedesktop/ConsoleKit")) { + CKSeat seat(path); + if (seat.isValid()) { + QDBusReply > r = seat.call(QLatin1String("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(); -} - -static void getSessionLocation(CKSession &lsess, SessEnt &se) -{ - QString tty; - QDBusReply r = lsess.call(QLatin1String("GetX11Display")); - if (r.isValid() && !r.value().isEmpty()) { - QDBusReply r2 = lsess.call(QLatin1String("GetX11DisplayDevice")); - tty = r2.value(); - se.display = r.value(); - se.tty = false; - } else { - QDBusReply r2 = lsess.call(QLatin1String("GetDisplayDevice")); - tty = r2.value(); - se.display = tty; - se.tty = true; - } - se.vt = tty.mid(strlen("/dev/tty")).toInt(); } #ifndef KDM_NO_SHUTDOWN @@ -297,8 +487,13 @@ KDisplayManager::canShutdown() { if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) { + QDBusReply canPowerOff = SystemdManager().call(QLatin1String("CanPowerOff")); + if (canPowerOff.isValid()) + return canPowerOff.value() != QLatin1String("no"); QDBusReply canStop = CKManager().call(QLatin1String("CanStop")); - return (canStop.isValid() && canStop.value()); + if (canStop.isValid()) + return canStop.value(); + return false; } if (DMType == OldKDM) @@ -329,9 +524,21 @@ return; if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) { - // FIXME: entirely ignoring shutdownMode - CKManager().call(QLatin1String( - shutdownType == KWorkSpace::ShutdownTypeReboot ? "Restart" : "Stop")); + // 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 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; } @@ -406,9 +613,15 @@ if (DMType == NewGDM || DMType == LightDM) { QDBusObjectPath currentSeat; if (getCurrentSeat(0, ¤tSeat)) { - CKSeat seat(currentSeat); - if (seat.isValid()) { - QDBusReply r = seat.call(QLatin1String("CanActivateSessions")); + SystemdSeat SDseat(currentSeat); + if (SDseat.isValid()) { + QVariant prop = SDseat.property("CanMultiSession"); + if (prop.isValid()) + return prop.toBool(); + } + CKSeat CKseat(currentSeat); + if (CKseat.isValid()) { + QDBusReply r = CKseat.call(QLatin1String("CanActivateSessions")); if (r.isValid()) return r.value(); } @@ -468,22 +681,56 @@ if (DMType == NewGDM || DMType == LightDM) { QDBusObjectPath currentSession, currentSeat; if (getCurrentSeat(¤tSession, ¤tSeat)) { - foreach (const QDBusObjectPath &sp, getSessionsForSeat(currentSeat)) { - CKSession lsess(sp); - if (lsess.isValid()) { - SessEnt se; - getSessionLocation(lsess, se); - // "Warning: we haven't yet defined the allowed values for this property. - // It is probably best to avoid this until we do." - QDBusReply r = lsess.call(QLatin1String("GetSessionType")); - if (r.value() != QLatin1String("LoginWindow")) { - QDBusReply r2 = lsess.call(QLatin1String("GetUnixUser")); - se.user = KUser(K_UID(r2.value())).loginName(); - se.session = ""; + // 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 = ""; + se.self = lsess.property("Display").toString() == ::getenv("DISPLAY"); /* Bleh once again */ + se.tty = !lsess.property("TTY").toString().isEmpty(); + } + list.append(se); } - se.self = (sp == currentSession); - list.append(se); } + } + // ConsoleKit part + else if (QDBusConnection::systemBus().interface()->isServiceRegistered("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 r = lsess.call(QLatin1String("GetSessionType")); + if (r.value() != QLatin1String("LoginWindow")) { + QDBusReply r2 = lsess.call(QLatin1String("GetUnixUser")); + se.user = KUser(K_UID(r2.value())).loginName(); + se.session = ""; + } + se.self = (sp == currentSession); + list.append(se); + } + } + } + else { + return false; } return true; } @@ -566,16 +813,33 @@ if (DMType == NewGDM || DMType == LightDM) { QDBusObjectPath currentSeat; if (getCurrentSeat(0, ¤tSeat)) { - foreach (const QDBusObjectPath &sp, getSessionsForSeat(currentSeat)) { - CKSession lsess(sp); - if (lsess.isValid()) { - SessEnt se; - getSessionLocation(lsess, se); - if (se.vt == vt) { - if (se.tty) // ConsoleKit simply ignores these - return false; - lsess.call(QLatin1String("Activate")); - return true; + // 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("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(QLatin1String("Activate")); + return true; + } } } }