File indexing completed on 2024-11-10 04:56:40
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"