File indexing completed on 2024-05-19 05:31:37

0001 /*
0002     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "session_logind.h"
0008 #include "utils/common.h"
0009 
0010 #include <QCoreApplication>
0011 #include <QDBusConnection>
0012 #include <QDBusConnectionInterface>
0013 #include <QDBusInterface>
0014 #include <QDBusMessage>
0015 #include <QDBusMetaType>
0016 #include <QDBusObjectPath>
0017 #include <QDBusPendingCall>
0018 #include <QDBusUnixFileDescriptor>
0019 
0020 #include <fcntl.h>
0021 #include <sys/stat.h>
0022 #include <unistd.h>
0023 
0024 #if __has_include(<sys/sysmacros.h>)
0025 #include <sys/sysmacros.h>
0026 #endif
0027 
0028 struct DBusLogindSeat
0029 {
0030     QString id;
0031     QDBusObjectPath path;
0032 };
0033 
0034 QDBusArgument &operator<<(QDBusArgument &argument, const DBusLogindSeat &seat)
0035 {
0036     argument.beginStructure();
0037     argument << seat.id << seat.path;
0038     argument.endStructure();
0039     return argument;
0040 }
0041 
0042 const QDBusArgument &operator>>(const QDBusArgument &argument, DBusLogindSeat &seat)
0043 {
0044     argument.beginStructure();
0045     argument >> seat.id >> seat.path;
0046     argument.endStructure();
0047     return argument;
0048 }
0049 
0050 Q_DECLARE_METATYPE(DBusLogindSeat)
0051 
0052 namespace KWin
0053 {
0054 
0055 static const QString s_serviceName = QStringLiteral("org.freedesktop.login1");
0056 static const QString s_propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
0057 static const QString s_sessionInterface = QStringLiteral("org.freedesktop.login1.Session");
0058 static const QString s_seatInterface = QStringLiteral("org.freedesktop.login1.Seat");
0059 static const QString s_managerInterface = QStringLiteral("org.freedesktop.login1.Manager");
0060 static const QString s_managerPath = QStringLiteral("/org/freedesktop/login1");
0061 
0062 static QString findProcessSessionPath()
0063 {
0064     const QString sessionId = qEnvironmentVariable("XDG_SESSION_ID", QStringLiteral("auto"));
0065     QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, s_managerPath,
0066                                                           s_managerInterface,
0067                                                           QStringLiteral("GetSession"));
0068     message.setArguments({sessionId});
0069     const QDBusMessage reply = QDBusConnection::systemBus().call(message);
0070     if (reply.type() == QDBusMessage::ErrorMessage) {
0071         return QString();
0072     }
0073 
0074     return reply.arguments().constFirst().value<QDBusObjectPath>().path();
0075 }
0076 
0077 static bool takeControl(const QString &sessionPath)
0078 {
0079     QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath,
0080                                                           s_sessionInterface,
0081                                                           QStringLiteral("TakeControl"));
0082     message.setArguments({false});
0083 
0084     const QDBusMessage reply = QDBusConnection::systemBus().call(message);
0085 
0086     return reply.type() != QDBusMessage::ErrorMessage;
0087 }
0088 
0089 static void releaseControl(const QString &sessionPath)
0090 {
0091     const QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath,
0092                                                                 s_sessionInterface,
0093                                                                 QStringLiteral("ReleaseControl"));
0094 
0095     QDBusConnection::systemBus().asyncCall(message);
0096 }
0097 
0098 static bool activate(const QString &sessionPath)
0099 {
0100     const QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath,
0101                                                                 s_sessionInterface,
0102                                                                 QStringLiteral("Activate"));
0103 
0104     const QDBusMessage reply = QDBusConnection::systemBus().call(message);
0105 
0106     return reply.type() != QDBusMessage::ErrorMessage;
0107 }
0108 
0109 std::unique_ptr<LogindSession> LogindSession::create()
0110 {
0111     if (!QDBusConnection::systemBus().interface()->isServiceRegistered(s_serviceName)) {
0112         return nullptr;
0113     }
0114 
0115     const QString sessionPath = findProcessSessionPath();
0116     if (sessionPath.isEmpty()) {
0117         qCWarning(KWIN_CORE) << "Could not determine the active graphical session";
0118         return nullptr;
0119     }
0120 
0121     if (!activate(sessionPath)) {
0122         qCWarning(KWIN_CORE, "Failed to activate %s session. Maybe another compositor is running?",
0123                   qPrintable(sessionPath));
0124         return nullptr;
0125     }
0126 
0127     if (!takeControl(sessionPath)) {
0128         qCWarning(KWIN_CORE, "Failed to take control of %s session. Maybe another compositor is running?",
0129                   qPrintable(sessionPath));
0130         return nullptr;
0131     }
0132 
0133     std::unique_ptr<LogindSession> session{new LogindSession(sessionPath)};
0134     if (session->initialize()) {
0135         return session;
0136     } else {
0137         return nullptr;
0138     }
0139 }
0140 
0141 bool LogindSession::isActive() const
0142 {
0143     return m_isActive;
0144 }
0145 
0146 LogindSession::Capabilities LogindSession::capabilities() const
0147 {
0148     return Capability::SwitchTerminal;
0149 }
0150 
0151 QString LogindSession::seat() const
0152 {
0153     return m_seatId;
0154 }
0155 
0156 uint LogindSession::terminal() const
0157 {
0158     return m_terminal;
0159 }
0160 
0161 int LogindSession::openRestricted(const QString &fileName)
0162 {
0163     struct stat st;
0164     if (stat(fileName.toUtf8(), &st) < 0) {
0165         return -1;
0166     }
0167 
0168     QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
0169                                                           s_sessionInterface,
0170                                                           QStringLiteral("TakeDevice"));
0171     // major() and minor() macros return ints on FreeBSD instead of uints.
0172     message.setArguments({uint(major(st.st_rdev)), uint(minor(st.st_rdev))});
0173 
0174     const QDBusMessage reply = QDBusConnection::systemBus().call(message);
0175     if (reply.type() == QDBusMessage::ErrorMessage) {
0176         qCWarning(KWIN_CORE, "Failed to open %s device (%s)",
0177                   qPrintable(fileName), qPrintable(reply.errorMessage()));
0178         return -1;
0179     }
0180 
0181     const QDBusUnixFileDescriptor descriptor = reply.arguments().constFirst().value<QDBusUnixFileDescriptor>();
0182     if (!descriptor.isValid()) {
0183         qCWarning(KWIN_CORE, "File descriptor for %s from logind is invalid", qPrintable(fileName));
0184         return -1;
0185     }
0186 
0187     return fcntl(descriptor.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
0188 }
0189 
0190 void LogindSession::closeRestricted(int fileDescriptor)
0191 {
0192     struct stat st;
0193     if (fstat(fileDescriptor, &st) < 0) {
0194         close(fileDescriptor);
0195         return;
0196     }
0197 
0198     QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
0199                                                           s_sessionInterface,
0200                                                           QStringLiteral("ReleaseDevice"));
0201     // major() and minor() macros return ints on FreeBSD instead of uints.
0202     message.setArguments({uint(major(st.st_rdev)), uint(minor(st.st_rdev))});
0203 
0204     QDBusConnection::systemBus().asyncCall(message);
0205 
0206     close(fileDescriptor);
0207 }
0208 
0209 void LogindSession::switchTo(uint terminal)
0210 {
0211     QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_seatPath,
0212                                                           s_seatInterface,
0213                                                           QStringLiteral("SwitchTo"));
0214     message.setArguments({terminal});
0215 
0216     QDBusConnection::systemBus().asyncCall(message);
0217 }
0218 
0219 LogindSession::LogindSession(const QString &sessionPath)
0220     : m_sessionPath(sessionPath)
0221 {
0222     qDBusRegisterMetaType<DBusLogindSeat>();
0223 }
0224 
0225 LogindSession::~LogindSession()
0226 {
0227     releaseControl(m_sessionPath);
0228 }
0229 
0230 bool LogindSession::initialize()
0231 {
0232     QDBusMessage activeMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
0233                                                                 s_propertiesInterface,
0234                                                                 QStringLiteral("Get"));
0235     activeMessage.setArguments({s_sessionInterface, QStringLiteral("Active")});
0236 
0237     QDBusMessage seatMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
0238                                                               s_propertiesInterface,
0239                                                               QStringLiteral("Get"));
0240     seatMessage.setArguments({s_sessionInterface, QStringLiteral("Seat")});
0241 
0242     QDBusMessage terminalMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
0243                                                                   s_propertiesInterface,
0244                                                                   QStringLiteral("Get"));
0245     terminalMessage.setArguments({s_sessionInterface, QStringLiteral("VTNr")});
0246 
0247     QDBusPendingReply<QVariant> activeReply =
0248         QDBusConnection::systemBus().asyncCall(activeMessage);
0249     QDBusPendingReply<QVariant> terminalReply =
0250         QDBusConnection::systemBus().asyncCall(terminalMessage);
0251     QDBusPendingReply<QVariant> seatReply =
0252         QDBusConnection::systemBus().asyncCall(seatMessage);
0253 
0254     // We must wait until all replies have been received because the drm backend needs a
0255     // valid seat name to properly select gpu devices, this also simplifies startup code.
0256     activeReply.waitForFinished();
0257     terminalReply.waitForFinished();
0258     seatReply.waitForFinished();
0259 
0260     if (activeReply.isError()) {
0261         qCWarning(KWIN_CORE) << "Failed to query Active session property:" << activeReply.error();
0262         return false;
0263     }
0264     if (terminalReply.isError()) {
0265         qCWarning(KWIN_CORE) << "Failed to query VTNr session property:" << terminalReply.error();
0266         return false;
0267     }
0268     if (seatReply.isError()) {
0269         qCWarning(KWIN_CORE) << "Failed to query Seat session property:" << seatReply.error();
0270         return false;
0271     }
0272 
0273     m_isActive = activeReply.value().toBool();
0274     m_terminal = terminalReply.value().toUInt();
0275 
0276     const DBusLogindSeat seat = qdbus_cast<DBusLogindSeat>(seatReply.value().value<QDBusArgument>());
0277     m_seatId = seat.id;
0278     m_seatPath = seat.path.path();
0279 
0280     QDBusConnection::systemBus().connect(s_serviceName, s_managerPath, s_managerInterface,
0281                                          QStringLiteral("PrepareForSleep"),
0282                                          this,
0283                                          SLOT(handlePrepareForSleep(bool)));
0284 
0285     QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_sessionInterface,
0286                                          QStringLiteral("PauseDevice"),
0287                                          this,
0288                                          SLOT(handlePauseDevice(uint, uint, QString)));
0289 
0290     QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_sessionInterface,
0291                                          QStringLiteral("ResumeDevice"),
0292                                          this,
0293                                          SLOT(handleResumeDevice(uint, uint, QDBusUnixFileDescriptor)));
0294 
0295     QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_propertiesInterface,
0296                                          QStringLiteral("PropertiesChanged"),
0297                                          this,
0298                                          SLOT(handlePropertiesChanged(QString, QVariantMap)));
0299 
0300     return true;
0301 }
0302 
0303 void LogindSession::updateActive(bool active)
0304 {
0305     if (m_isActive != active) {
0306         m_isActive = active;
0307         Q_EMIT activeChanged(active);
0308     }
0309 }
0310 
0311 void LogindSession::handlePauseDevice(uint major, uint minor, const QString &type)
0312 {
0313     Q_EMIT devicePaused(makedev(major, minor));
0314 
0315     if (type == QLatin1String("pause")) {
0316         QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
0317                                                               s_sessionInterface,
0318                                                               QStringLiteral("PauseDeviceComplete"));
0319         message.setArguments({major, minor});
0320 
0321         QDBusConnection::systemBus().asyncCall(message);
0322     }
0323 }
0324 
0325 void LogindSession::handleResumeDevice(uint major, uint minor, QDBusUnixFileDescriptor fileDescriptor)
0326 {
0327     // We don't care about the file descriptor as the libinput backend will re-open input devices
0328     // and the drm file descriptors remain valid after pausing gpus.
0329 
0330     Q_EMIT deviceResumed(makedev(major, minor));
0331 }
0332 
0333 void LogindSession::handlePropertiesChanged(const QString &interfaceName, const QVariantMap &properties)
0334 {
0335     if (interfaceName == s_sessionInterface) {
0336         const QVariant active = properties.value(QStringLiteral("Active"));
0337         if (active.isValid()) {
0338             updateActive(active.toBool());
0339         }
0340     }
0341 }
0342 
0343 void LogindSession::handlePrepareForSleep(bool sleep)
0344 {
0345     if (!sleep) {
0346         Q_EMIT awoke();
0347     }
0348 }
0349 
0350 } // namespace KWin
0351 
0352 #include "moc_session_logind.cpp"