File indexing completed on 2024-05-12 17:02:08

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