452 lines
19 KiB
C++
452 lines
19 KiB
C++
/*
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#include "logind.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDebug>
|
|
#include <QDBusConnectionInterface>
|
|
#include <QDBusPendingCallWatcher>
|
|
#include <QDBusServiceWatcher>
|
|
#include <QDBusUnixFileDescriptor>
|
|
#include <QDBusMetaType>
|
|
|
|
#include <sys/stat.h>
|
|
#include <config-kwin.h>
|
|
#if HAVE_SYS_SYSMACROS_H
|
|
#include <sys/sysmacros.h>
|
|
#endif
|
|
#ifndef major
|
|
#include <sys/types.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include "utils.h"
|
|
|
|
struct DBusLogindSeat {
|
|
QString name;
|
|
QDBusObjectPath path;
|
|
};
|
|
|
|
QDBusArgument &operator<<(QDBusArgument &argument, const DBusLogindSeat &seat)
|
|
{
|
|
argument.beginStructure();
|
|
argument << seat.name << seat.path ;
|
|
argument.endStructure();
|
|
return argument;
|
|
}
|
|
|
|
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusLogindSeat &seat)
|
|
{
|
|
argument.beginStructure();
|
|
argument >> seat.name >> seat.path;
|
|
argument.endStructure();
|
|
return argument;
|
|
}
|
|
|
|
Q_DECLARE_METATYPE(DBusLogindSeat)
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
const static QString s_login1Name = QStringLiteral("logind");
|
|
const static QString s_login1Service = QStringLiteral("org.freedesktop.login1");
|
|
const static QString s_login1Path = QStringLiteral("/org/freedesktop/login1");
|
|
const static QString s_login1ManagerInterface = QStringLiteral("org.freedesktop.login1.Manager");
|
|
const static QString s_login1SeatInterface = QStringLiteral("org.freedesktop.login1.Seat");
|
|
const static QString s_login1SessionInterface = QStringLiteral("org.freedesktop.login1.Session");
|
|
const static QString s_login1ActiveProperty = QStringLiteral("Active");
|
|
|
|
const static QString s_ck2Name = QStringLiteral("ConsoleKit");
|
|
const static QString s_ck2Service = QStringLiteral("org.freedesktop.ConsoleKit");
|
|
const static QString s_ck2Path = QStringLiteral("/org/freedesktop/ConsoleKit/Manager");
|
|
const static QString s_ck2ManagerInterface = QStringLiteral("org.freedesktop.ConsoleKit.Manager");
|
|
const static QString s_ck2SeatInterface = QStringLiteral("org.freedesktop.ConsoleKit.Seat");
|
|
const static QString s_ck2SessionInterface = QStringLiteral("org.freedesktop.ConsoleKit.Session");
|
|
const static QString s_ck2ActiveProperty = QStringLiteral("active");
|
|
|
|
const static QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
|
|
|
|
|
|
|
|
LogindIntegration *LogindIntegration::s_self = nullptr;
|
|
|
|
LogindIntegration *LogindIntegration::create(QObject *parent)
|
|
{
|
|
Q_ASSERT(!s_self);
|
|
s_self = new LogindIntegration(parent);
|
|
return s_self;
|
|
}
|
|
|
|
LogindIntegration::LogindIntegration(const QDBusConnection &connection, QObject *parent)
|
|
: QObject(parent)
|
|
, m_bus(connection)
|
|
, m_connected(false)
|
|
, m_sessionControl(false)
|
|
, m_sessionActive(false)
|
|
{
|
|
// check whether the logind service is registered
|
|
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
|
|
QStringLiteral("/"),
|
|
QStringLiteral("org.freedesktop.DBus"),
|
|
QStringLiteral("ListNames"));
|
|
QDBusPendingReply<QStringList> async = m_bus.asyncCall(message);
|
|
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
|
|
connect(callWatcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this](QDBusPendingCallWatcher *self) {
|
|
QDBusPendingReply<QStringList> reply = *self;
|
|
self->deleteLater();
|
|
if (!reply.isValid()) {
|
|
return;
|
|
}
|
|
if (reply.value().contains(s_login1Service)) {
|
|
setupSessionController(SessionControllerLogind);
|
|
} else if (reply.value().contains(s_ck2Service)) {
|
|
setupSessionController(SessionControllerConsoleKit);
|
|
}
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
LogindIntegration::LogindIntegration(QObject *parent)
|
|
: LogindIntegration(QDBusConnection::systemBus(), parent)
|
|
{
|
|
}
|
|
|
|
LogindIntegration::~LogindIntegration()
|
|
{
|
|
s_self = nullptr;
|
|
}
|
|
|
|
void LogindIntegration::setupSessionController(SessionController controller)
|
|
{
|
|
if (controller == SessionControllerLogind) {
|
|
// We have the logind serivce, set it up and use it
|
|
m_sessionControllerName = s_login1Name;
|
|
m_sessionControllerService = s_login1Service;
|
|
m_sessionControllerPath = s_login1Path;
|
|
m_sessionControllerManagerInterface = s_login1ManagerInterface;
|
|
m_sessionControllerSeatInterface = s_login1SeatInterface;
|
|
m_sessionControllerSessionInterface = s_login1SessionInterface;
|
|
m_sessionControllerActiveProperty = s_login1ActiveProperty;
|
|
m_logindServiceWatcher = new QDBusServiceWatcher(m_sessionControllerService,
|
|
m_bus,
|
|
QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration,
|
|
this);
|
|
connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LogindIntegration::logindServiceRegistered);
|
|
connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this,
|
|
[this]() {
|
|
m_connected = false;
|
|
emit connectedChanged();
|
|
}
|
|
);
|
|
logindServiceRegistered();
|
|
} else if (controller == SessionControllerConsoleKit) {
|
|
// We have the ConsoleKit serivce, set it up and use it
|
|
m_sessionControllerName = s_ck2Name;
|
|
m_sessionControllerService = s_ck2Service;
|
|
m_sessionControllerPath = s_ck2Path;
|
|
m_sessionControllerManagerInterface = s_ck2ManagerInterface;
|
|
m_sessionControllerSeatInterface = s_ck2SeatInterface;
|
|
m_sessionControllerSessionInterface = s_ck2SessionInterface;
|
|
m_sessionControllerActiveProperty = s_ck2ActiveProperty;
|
|
m_logindServiceWatcher = new QDBusServiceWatcher(m_sessionControllerService,
|
|
m_bus,
|
|
QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration,
|
|
this);
|
|
connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LogindIntegration::logindServiceRegistered);
|
|
connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this,
|
|
[this]() {
|
|
m_connected = false;
|
|
emit connectedChanged();
|
|
}
|
|
);
|
|
logindServiceRegistered();
|
|
}
|
|
}
|
|
|
|
void LogindIntegration::logindServiceRegistered()
|
|
{
|
|
const QByteArray sessionId = qgetenv("XDG_SESSION_ID");
|
|
QString methodName;
|
|
QVariantList args;
|
|
if (sessionId.isEmpty()) {
|
|
methodName = QStringLiteral("GetSessionByPID");
|
|
args << (quint32) QCoreApplication::applicationPid();
|
|
} else {
|
|
methodName = QStringLiteral("GetSession");
|
|
args << QString::fromLocal8Bit(sessionId);
|
|
}
|
|
// get the current session
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionControllerPath,
|
|
m_sessionControllerManagerInterface,
|
|
methodName);
|
|
message.setArguments(args);
|
|
QDBusPendingReply<QDBusObjectPath> session = m_bus.asyncCall(message);
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(session, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this](QDBusPendingCallWatcher *self) {
|
|
QDBusPendingReply<QDBusObjectPath> reply = *self;
|
|
self->deleteLater();
|
|
if (m_connected) {
|
|
return;
|
|
}
|
|
if (!reply.isValid()) {
|
|
qCDebug(KWIN_CORE) << "The session is not registered with " << m_sessionControllerName << " " << reply.error().message();
|
|
return;
|
|
}
|
|
m_sessionPath = reply.value().path();
|
|
qCDebug(KWIN_CORE) << "Session path:" << m_sessionPath;
|
|
m_connected = true;
|
|
connectSessionPropertiesChanged();
|
|
// activate the session, in case we are not on it
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
m_sessionControllerSessionInterface,
|
|
QStringLiteral("Activate"));
|
|
// blocking on purpose
|
|
m_bus.call(message);
|
|
getSeat();
|
|
getSessionActive();
|
|
getVirtualTerminal();
|
|
|
|
emit connectedChanged();
|
|
}
|
|
);
|
|
|
|
m_bus.connect(m_sessionControllerService,
|
|
m_sessionPath,
|
|
m_sessionControllerManagerInterface,
|
|
QStringLiteral("PrepareForSleep"),
|
|
this,
|
|
SIGNAL(prepareForSleep(bool)));
|
|
}
|
|
|
|
void LogindIntegration::connectSessionPropertiesChanged()
|
|
{
|
|
m_bus.connect(m_sessionControllerService,
|
|
m_sessionPath,
|
|
s_dbusPropertiesInterface,
|
|
QStringLiteral("PropertiesChanged"),
|
|
this,
|
|
SLOT(getSessionActive()));
|
|
m_bus.connect(m_sessionControllerService,
|
|
m_sessionPath,
|
|
s_dbusPropertiesInterface,
|
|
QStringLiteral("PropertiesChanged"),
|
|
this,
|
|
SLOT(getVirtualTerminal()));
|
|
}
|
|
|
|
void LogindIntegration::getSessionActive()
|
|
{
|
|
if (!m_connected || m_sessionPath.isEmpty()) {
|
|
return;
|
|
}
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
s_dbusPropertiesInterface,
|
|
QStringLiteral("Get"));
|
|
message.setArguments(QVariantList({m_sessionControllerSessionInterface, m_sessionControllerActiveProperty}));
|
|
QDBusPendingReply<QVariant> reply = m_bus.asyncCall(message);
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this](QDBusPendingCallWatcher *self) {
|
|
QDBusPendingReply<QVariant> reply = *self;
|
|
self->deleteLater();
|
|
if (!reply.isValid()) {
|
|
qCDebug(KWIN_CORE) << "Failed to get Active Property of " << m_sessionControllerName << " session:" << reply.error().message();
|
|
return;
|
|
}
|
|
const bool active = reply.value().toBool();
|
|
if (m_sessionActive != active) {
|
|
m_sessionActive = active;
|
|
emit sessionActiveChanged(m_sessionActive);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
void LogindIntegration::getVirtualTerminal()
|
|
{
|
|
if (!m_connected || m_sessionPath.isEmpty()) {
|
|
return;
|
|
}
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
s_dbusPropertiesInterface,
|
|
QStringLiteral("Get"));
|
|
message.setArguments(QVariantList({m_sessionControllerSessionInterface, QStringLiteral("VTNr")}));
|
|
QDBusPendingReply<QVariant> reply = m_bus.asyncCall(message);
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this](QDBusPendingCallWatcher *self) {
|
|
QDBusPendingReply<QVariant> reply = *self;
|
|
self->deleteLater();
|
|
if (!reply.isValid()) {
|
|
qCDebug(KWIN_CORE) << "Failed to get VTNr Property of " << m_sessionControllerName << " session:" << reply.error().message();
|
|
return;
|
|
}
|
|
const int vt = reply.value().toUInt();
|
|
if (m_vt != (int)vt) {
|
|
m_vt = vt;
|
|
emit virtualTerminalChanged(m_vt);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
void LogindIntegration::takeControl()
|
|
{
|
|
if (!m_connected || m_sessionPath.isEmpty() || m_sessionControl) {
|
|
return;
|
|
}
|
|
static bool s_recursionCheck = false;
|
|
if (s_recursionCheck) {
|
|
return;
|
|
}
|
|
s_recursionCheck = true;
|
|
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
m_sessionControllerSessionInterface,
|
|
QStringLiteral("TakeControl"));
|
|
message.setArguments(QVariantList({QVariant(false)}));
|
|
QDBusPendingReply<void> session = m_bus.asyncCall(message);
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(session, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this](QDBusPendingCallWatcher *self) {
|
|
s_recursionCheck = false;
|
|
QDBusPendingReply<void> reply = *self;
|
|
self->deleteLater();
|
|
if (!reply.isValid()) {
|
|
qCDebug(KWIN_CORE) << "Failed to get session control" << reply.error().message();
|
|
emit hasSessionControlChanged(false);
|
|
return;
|
|
}
|
|
qCDebug(KWIN_CORE) << "Gained session control";
|
|
m_sessionControl = true;
|
|
emit hasSessionControlChanged(true);
|
|
m_bus.connect(m_sessionControllerService, m_sessionPath,
|
|
m_sessionControllerSessionInterface, QStringLiteral("PauseDevice"),
|
|
this, SLOT(pauseDevice(uint,uint,QString)));
|
|
}
|
|
);
|
|
}
|
|
|
|
void LogindIntegration::releaseControl()
|
|
{
|
|
if (!m_connected || m_sessionPath.isEmpty() || !m_sessionControl) {
|
|
return;
|
|
}
|
|
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
m_sessionControllerSessionInterface,
|
|
QStringLiteral("ReleaseControl"));
|
|
m_bus.asyncCall(message);
|
|
m_sessionControl = false;
|
|
emit hasSessionControlChanged(false);
|
|
}
|
|
|
|
int LogindIntegration::takeDevice(const char *path)
|
|
{
|
|
struct stat st;
|
|
if (stat(path, &st) < 0) {
|
|
qCDebug(KWIN_CORE) << "Could not stat the path";
|
|
return -1;
|
|
}
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
m_sessionControllerSessionInterface,
|
|
QStringLiteral("TakeDevice"));
|
|
message.setArguments(QVariantList({QVariant(major(st.st_rdev)), QVariant(minor(st.st_rdev))}));
|
|
// intended to be a blocking call
|
|
QDBusMessage reply = m_bus.call(message);
|
|
if (reply.type() == QDBusMessage::ErrorMessage) {
|
|
qCDebug(KWIN_CORE) << "Could not take device" << path << ", cause: " << reply.errorMessage();
|
|
return -1;
|
|
}
|
|
|
|
// The dup syscall removes the CLOEXEC flag as a side-effect. So use fcntl's F_DUPFD_CLOEXEC cmd.
|
|
return fcntl(reply.arguments().first().value<QDBusUnixFileDescriptor>().fileDescriptor(), F_DUPFD_CLOEXEC, 0);
|
|
}
|
|
|
|
void LogindIntegration::releaseDevice(int fd)
|
|
{
|
|
struct stat st;
|
|
if (fstat(fd, &st) < 0) {
|
|
qCDebug(KWIN_CORE) << "Could not stat the file descriptor";
|
|
} else {
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
m_sessionControllerSessionInterface,
|
|
QStringLiteral("ReleaseDevice"));
|
|
message.setArguments(QVariantList({QVariant(major(st.st_rdev)), QVariant(minor(st.st_rdev))}));
|
|
m_bus.asyncCall(message);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
void LogindIntegration::pauseDevice(uint devMajor, uint devMinor, const QString &type)
|
|
{
|
|
if (QString::compare(type, QStringLiteral("pause"), Qt::CaseInsensitive) == 0) {
|
|
// unconditionally call complete
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService, m_sessionPath, m_sessionControllerSessionInterface, QStringLiteral("PauseDeviceComplete"));
|
|
message.setArguments(QVariantList({QVariant(devMajor), QVariant(devMinor)}));
|
|
m_bus.asyncCall(message);
|
|
}
|
|
}
|
|
|
|
void LogindIntegration::getSeat()
|
|
{
|
|
if (m_sessionPath.isEmpty()) {
|
|
return;
|
|
}
|
|
qDBusRegisterMetaType<DBusLogindSeat>();
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_sessionPath,
|
|
s_dbusPropertiesInterface,
|
|
QStringLiteral("Get"));
|
|
message.setArguments(QVariantList({m_sessionControllerSessionInterface, QStringLiteral("Seat")}));
|
|
QDBusPendingReply<QVariant> reply = m_bus.asyncCall(message);
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this](QDBusPendingCallWatcher *self) {
|
|
QDBusPendingReply<QVariant> reply = *self;
|
|
self->deleteLater();
|
|
if (!reply.isValid()) {
|
|
qCDebug(KWIN_CORE) << "Failed to get Seat Property of " << m_sessionControllerName << " session:" << reply.error().message();
|
|
return;
|
|
}
|
|
DBusLogindSeat seat = qdbus_cast<DBusLogindSeat>(reply.value().value<QDBusArgument>());
|
|
const QString seatPath = seat.path.path();
|
|
qCDebug(KWIN_CORE) << m_sessionControllerName << " seat:" << seat.name << "/" << seatPath;
|
|
m_seatPath = seatPath;
|
|
m_seatName = seat.name;
|
|
}
|
|
);
|
|
}
|
|
|
|
void LogindIntegration::switchVirtualTerminal(quint32 vtNr)
|
|
{
|
|
if (!m_connected || m_seatPath.isEmpty()) {
|
|
return;
|
|
}
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_sessionControllerService,
|
|
m_seatPath,
|
|
m_sessionControllerSeatInterface,
|
|
QStringLiteral("SwitchTo"));
|
|
message.setArguments(QVariantList{vtNr});
|
|
m_bus.asyncCall(message);
|
|
}
|
|
|
|
} // namespace
|