KWin
Loading...
Searching...
No Matches
session_logind.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "session_logind.h"
8#include "utils/common.h"
9
10#include <QCoreApplication>
11#include <QDBusConnection>
12#include <QDBusConnectionInterface>
13#include <QDBusInterface>
14#include <QDBusMessage>
15#include <QDBusMetaType>
16#include <QDBusObjectPath>
17#include <QDBusPendingCall>
18#include <QDBusUnixFileDescriptor>
19
20#include <fcntl.h>
21#include <sys/stat.h>
22#include <unistd.h>
23
24#if __has_include(<sys/sysmacros.h>)
25#include <sys/sysmacros.h>
26#endif
27
29{
30 QString id;
31 QDBusObjectPath path;
32};
33
34QDBusArgument &operator<<(QDBusArgument &argument, const DBusLogindSeat &seat)
35{
36 argument.beginStructure();
37 argument << seat.id << seat.path;
38 argument.endStructure();
39 return argument;
40}
41
42const QDBusArgument &operator>>(const QDBusArgument &argument, DBusLogindSeat &seat)
43{
44 argument.beginStructure();
45 argument >> seat.id >> seat.path;
46 argument.endStructure();
47 return argument;
48}
49
51
52namespace KWin
53{
54
55static const QString s_serviceName = QStringLiteral("org.freedesktop.login1");
56static const QString s_propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
57static const QString s_sessionInterface = QStringLiteral("org.freedesktop.login1.Session");
58static const QString s_seatInterface = QStringLiteral("org.freedesktop.login1.Seat");
59static const QString s_managerInterface = QStringLiteral("org.freedesktop.login1.Manager");
60static const QString s_managerPath = QStringLiteral("/org/freedesktop/login1");
61
62static QString findProcessSessionPath()
63{
64 const QString sessionId = qEnvironmentVariable("XDG_SESSION_ID", QStringLiteral("auto"));
65 QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, s_managerPath,
66 s_managerInterface,
67 QStringLiteral("GetSession"));
68 message.setArguments({sessionId});
69 const QDBusMessage reply = QDBusConnection::systemBus().call(message);
70 if (reply.type() == QDBusMessage::ErrorMessage) {
71 return QString();
72 }
73
74 return reply.arguments().constFirst().value<QDBusObjectPath>().path();
75}
76
77static bool takeControl(const QString &sessionPath)
78{
79 QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath,
80 s_sessionInterface,
81 QStringLiteral("TakeControl"));
82 message.setArguments({false});
83
84 const QDBusMessage reply = QDBusConnection::systemBus().call(message);
85
86 return reply.type() != QDBusMessage::ErrorMessage;
87}
88
89static void releaseControl(const QString &sessionPath)
90{
91 const QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath,
92 s_sessionInterface,
93 QStringLiteral("ReleaseControl"));
94
95 QDBusConnection::systemBus().asyncCall(message);
96}
97
98static bool activate(const QString &sessionPath)
99{
100 const QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath,
101 s_sessionInterface,
102 QStringLiteral("Activate"));
103
104 const QDBusMessage reply = QDBusConnection::systemBus().call(message);
105
106 return reply.type() != QDBusMessage::ErrorMessage;
107}
108
109std::unique_ptr<LogindSession> LogindSession::create()
110{
111 if (!QDBusConnection::systemBus().interface()->isServiceRegistered(s_serviceName)) {
112 return nullptr;
113 }
114
115 const QString sessionPath = findProcessSessionPath();
116 if (sessionPath.isEmpty()) {
117 qCWarning(KWIN_CORE) << "Could not determine the active graphical session";
118 return nullptr;
119 }
120
121 if (!activate(sessionPath)) {
122 qCWarning(KWIN_CORE, "Failed to activate %s session. Maybe another compositor is running?",
123 qPrintable(sessionPath));
124 return nullptr;
125 }
126
127 if (!takeControl(sessionPath)) {
128 qCWarning(KWIN_CORE, "Failed to take control of %s session. Maybe another compositor is running?",
129 qPrintable(sessionPath));
130 return nullptr;
131 }
132
133 std::unique_ptr<LogindSession> session{new LogindSession(sessionPath)};
134 if (session->initialize()) {
135 return session;
136 } else {
137 return nullptr;
138 }
139}
140
142{
143 return m_isActive;
144}
145
146LogindSession::Capabilities LogindSession::capabilities() const
147{
149}
150
151QString LogindSession::seat() const
152{
153 return m_seatId;
154}
155
157{
158 return m_terminal;
159}
160
161int LogindSession::openRestricted(const QString &fileName)
162{
163 struct stat st;
164 if (stat(fileName.toUtf8(), &st) < 0) {
165 return -1;
166 }
167
168 QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
169 s_sessionInterface,
170 QStringLiteral("TakeDevice"));
171 // major() and minor() macros return ints on FreeBSD instead of uints.
172 message.setArguments({uint(major(st.st_rdev)), uint(minor(st.st_rdev))});
173
174 const QDBusMessage reply = QDBusConnection::systemBus().call(message);
175 if (reply.type() == QDBusMessage::ErrorMessage) {
176 qCWarning(KWIN_CORE, "Failed to open %s device (%s)",
177 qPrintable(fileName), qPrintable(reply.errorMessage()));
178 return -1;
179 }
180
181 const QDBusUnixFileDescriptor descriptor = reply.arguments().constFirst().value<QDBusUnixFileDescriptor>();
182 if (!descriptor.isValid()) {
183 qCWarning(KWIN_CORE, "File descriptor for %s from logind is invalid", qPrintable(fileName));
184 return -1;
185 }
186
187 return fcntl(descriptor.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
188}
189
190void LogindSession::closeRestricted(int fileDescriptor)
191{
192 struct stat st;
193 if (fstat(fileDescriptor, &st) < 0) {
194 close(fileDescriptor);
195 return;
196 }
197
198 QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
199 s_sessionInterface,
200 QStringLiteral("ReleaseDevice"));
201 // major() and minor() macros return ints on FreeBSD instead of uints.
202 message.setArguments({uint(major(st.st_rdev)), uint(minor(st.st_rdev))});
203
204 QDBusConnection::systemBus().asyncCall(message);
205
206 close(fileDescriptor);
207}
208
209void LogindSession::switchTo(uint terminal)
210{
211 QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_seatPath,
212 s_seatInterface,
213 QStringLiteral("SwitchTo"));
214 message.setArguments({terminal});
215
216 QDBusConnection::systemBus().asyncCall(message);
217}
218
219LogindSession::LogindSession(const QString &sessionPath)
220 : m_sessionPath(sessionPath)
221{
222 qDBusRegisterMetaType<DBusLogindSeat>();
223}
224
226{
227 releaseControl(m_sessionPath);
228}
229
230bool LogindSession::initialize()
231{
232 QDBusMessage activeMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
233 s_propertiesInterface,
234 QStringLiteral("Get"));
235 activeMessage.setArguments({s_sessionInterface, QStringLiteral("Active")});
236
237 QDBusMessage seatMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
238 s_propertiesInterface,
239 QStringLiteral("Get"));
240 seatMessage.setArguments({s_sessionInterface, QStringLiteral("Seat")});
241
242 QDBusMessage terminalMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
243 s_propertiesInterface,
244 QStringLiteral("Get"));
245 terminalMessage.setArguments({s_sessionInterface, QStringLiteral("VTNr")});
246
247 QDBusPendingReply<QVariant> activeReply =
248 QDBusConnection::systemBus().asyncCall(activeMessage);
249 QDBusPendingReply<QVariant> terminalReply =
250 QDBusConnection::systemBus().asyncCall(terminalMessage);
251 QDBusPendingReply<QVariant> seatReply =
252 QDBusConnection::systemBus().asyncCall(seatMessage);
253
254 // We must wait until all replies have been received because the drm backend needs a
255 // valid seat name to properly select gpu devices, this also simplifies startup code.
256 activeReply.waitForFinished();
257 terminalReply.waitForFinished();
258 seatReply.waitForFinished();
259
260 if (activeReply.isError()) {
261 qCWarning(KWIN_CORE) << "Failed to query Active session property:" << activeReply.error();
262 return false;
263 }
264 if (terminalReply.isError()) {
265 qCWarning(KWIN_CORE) << "Failed to query VTNr session property:" << terminalReply.error();
266 return false;
267 }
268 if (seatReply.isError()) {
269 qCWarning(KWIN_CORE) << "Failed to query Seat session property:" << seatReply.error();
270 return false;
271 }
272
273 m_isActive = activeReply.value().toBool();
274 m_terminal = terminalReply.value().toUInt();
275
276 const DBusLogindSeat seat = qdbus_cast<DBusLogindSeat>(seatReply.value().value<QDBusArgument>());
277 m_seatId = seat.id;
278 m_seatPath = seat.path.path();
279
280 QDBusConnection::systemBus().connect(s_serviceName, s_managerPath, s_managerInterface,
281 QStringLiteral("PrepareForSleep"),
282 this,
283 SLOT(handlePrepareForSleep(bool)));
284
285 QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_sessionInterface,
286 QStringLiteral("PauseDevice"),
287 this,
288 SLOT(handlePauseDevice(uint, uint, QString)));
289
290 QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_sessionInterface,
291 QStringLiteral("ResumeDevice"),
292 this,
293 SLOT(handleResumeDevice(uint, uint, QDBusUnixFileDescriptor)));
294
295 QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_propertiesInterface,
296 QStringLiteral("PropertiesChanged"),
297 this,
298 SLOT(handlePropertiesChanged(QString, QVariantMap)));
299
300 return true;
301}
302
303void LogindSession::updateActive(bool active)
304{
305 if (m_isActive != active) {
306 m_isActive = active;
307 Q_EMIT activeChanged(active);
308 }
309}
310
311void LogindSession::handlePauseDevice(uint major, uint minor, const QString &type)
312{
313 Q_EMIT devicePaused(makedev(major, minor));
314
315 if (type == QLatin1String("pause")) {
316 QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath,
317 s_sessionInterface,
318 QStringLiteral("PauseDeviceComplete"));
319 message.setArguments({major, minor});
320
321 QDBusConnection::systemBus().asyncCall(message);
322 }
323}
324
325void LogindSession::handleResumeDevice(uint major, uint minor, QDBusUnixFileDescriptor fileDescriptor)
326{
327 // We don't care about the file descriptor as the libinput backend will re-open input devices
328 // and the drm file descriptors remain valid after pausing gpus.
329
330 Q_EMIT deviceResumed(makedev(major, minor));
331}
332
333void LogindSession::handlePropertiesChanged(const QString &interfaceName, const QVariantMap &properties)
334{
335 if (interfaceName == s_sessionInterface) {
336 const QVariant active = properties.value(QStringLiteral("Active"));
337 if (active.isValid()) {
338 updateActive(active.toBool());
339 }
340 }
341}
342
343void LogindSession::handlePrepareForSleep(bool sleep)
344{
345 if (!sleep) {
346 Q_EMIT awoke();
347 }
348}
349
350} // namespace KWin
351
352#include "moc_session_logind.cpp"
int openRestricted(const QString &fileName) override
uint terminal() const override
bool isActive() const override
void closeRestricted(int fileDescriptor) override
Capabilities capabilities() const override
QString seat() const override
static std::unique_ptr< LogindSession > create()
void switchTo(uint terminal) override
void activeChanged(bool active)
void devicePaused(dev_t deviceId)
void deviceResumed(dev_t deviceId)
Q_DECLARE_METATYPE(KWin::SwitchEvent::State)
Session::Type type
Definition session.cpp:17
const QDBusArgument & operator>>(const QDBusArgument &argument, DBusLogindSeat &seat)
QDBusArgument & operator<<(QDBusArgument &argument, const DBusLogindSeat &seat)
QDBusObjectPath path