You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
12 KiB
338 lines
12 KiB
/* |
|
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
#include "session_consolekit.h" |
|
#include "utils.h" |
|
|
|
#include <QCoreApplication> |
|
#include <QDBusConnection> |
|
#include <QDBusConnectionInterface> |
|
#include <QDBusInterface> |
|
#include <QDBusMessage> |
|
#include <QDBusMetaType> |
|
#include <QDBusObjectPath> |
|
#include <QDBusPendingCall> |
|
#include <QDBusUnixFileDescriptor> |
|
|
|
#include <fcntl.h> |
|
#include <sys/stat.h> |
|
#include <unistd.h> |
|
|
|
#if HAVE_SYS_SYSMACROS_H |
|
#include <sys/sysmacros.h> |
|
#endif |
|
|
|
// Note that ConsoleKit's session api is not fully compatible with logind's session api. |
|
|
|
struct DBusConsoleKitSeat |
|
{ |
|
QString id; |
|
QDBusObjectPath path; |
|
}; |
|
|
|
QDBusArgument &operator<<(QDBusArgument &argument, const DBusConsoleKitSeat &seat) |
|
{ |
|
argument.beginStructure(); |
|
argument << seat.id << seat.path; |
|
argument.endStructure(); |
|
return argument; |
|
} |
|
|
|
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusConsoleKitSeat &seat) |
|
{ |
|
argument.beginStructure(); |
|
argument >> seat.id >> seat.path; |
|
argument.endStructure(); |
|
return argument; |
|
} |
|
|
|
Q_DECLARE_METATYPE(DBusConsoleKitSeat) |
|
|
|
namespace KWin |
|
{ |
|
|
|
static const QString s_serviceName = QStringLiteral("org.freedesktop.ConsoleKit"); |
|
static const QString s_propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); |
|
static const QString s_sessionInterface = QStringLiteral("org.freedesktop.ConsoleKit.Session"); |
|
static const QString s_seatInterface = QStringLiteral("org.freedesktop.ConsoleKit.Seat"); |
|
static const QString s_managerInterface = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); |
|
static const QString s_managerPath = QStringLiteral("/org/freedesktop/ConsoleKit/Manager"); |
|
|
|
static QString findProcessSessionPath() |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, s_managerPath, |
|
s_managerInterface, |
|
QStringLiteral("GetSessionByPID")); |
|
message.setArguments({ uint32_t(QCoreApplication::applicationPid()) }); |
|
|
|
const QDBusMessage reply = QDBusConnection::systemBus().call(message); |
|
if (reply.type() == QDBusMessage::ErrorMessage) { |
|
return QString(); |
|
} |
|
|
|
return reply.arguments().constFirst().value<QDBusObjectPath>().path(); |
|
} |
|
|
|
static bool takeControl(const QString &sessionPath) |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath, |
|
s_sessionInterface, |
|
QStringLiteral("TakeControl")); |
|
message.setArguments({ false }); |
|
|
|
const QDBusMessage reply = QDBusConnection::systemBus().call(message); |
|
|
|
return reply.type() != QDBusMessage::ErrorMessage; |
|
} |
|
|
|
static void releaseControl(const QString &sessionPath) |
|
{ |
|
const QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath, |
|
s_sessionInterface, |
|
QStringLiteral("ReleaseControl")); |
|
|
|
QDBusConnection::systemBus().asyncCall(message); |
|
} |
|
|
|
static bool activate(const QString &sessionPath) |
|
{ |
|
const QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, sessionPath, |
|
s_sessionInterface, |
|
QStringLiteral("Activate")); |
|
|
|
const QDBusMessage reply = QDBusConnection::systemBus().call(message); |
|
|
|
return reply.type() != QDBusMessage::ErrorMessage; |
|
} |
|
|
|
ConsoleKitSession *ConsoleKitSession::create(QObject *parent) |
|
{ |
|
if (!QDBusConnection::systemBus().interface()->isServiceRegistered(s_serviceName)) { |
|
return nullptr; |
|
} |
|
|
|
const QString sessionPath = findProcessSessionPath(); |
|
if (sessionPath.isEmpty()) { |
|
qCWarning(KWIN_CORE) << "Could not determine the active graphical session"; |
|
return nullptr; |
|
} |
|
|
|
if (!activate(sessionPath)) { |
|
qCWarning(KWIN_CORE, "Failed to activate %s session. Maybe another compositor is running?", |
|
qPrintable(sessionPath)); |
|
return nullptr; |
|
} |
|
|
|
if (!takeControl(sessionPath)) { |
|
qCWarning(KWIN_CORE, "Failed to take control of %s session. Maybe another compositor is running?", |
|
qPrintable(sessionPath)); |
|
return nullptr; |
|
} |
|
|
|
ConsoleKitSession *session = new ConsoleKitSession(sessionPath, parent); |
|
if (session->initialize()) { |
|
return session; |
|
} |
|
|
|
delete session; |
|
return nullptr; |
|
} |
|
|
|
bool ConsoleKitSession::isActive() const |
|
{ |
|
return m_isActive; |
|
} |
|
|
|
ConsoleKitSession::Capabilities ConsoleKitSession::capabilities() const |
|
{ |
|
return Capability::SwitchTerminal; |
|
} |
|
|
|
QString ConsoleKitSession::seat() const |
|
{ |
|
return m_seatId; |
|
} |
|
|
|
uint ConsoleKitSession::terminal() const |
|
{ |
|
return m_terminal; |
|
} |
|
|
|
int ConsoleKitSession::openRestricted(const QString &fileName) |
|
{ |
|
struct stat st; |
|
if (stat(fileName.toUtf8(), &st) < 0) { |
|
return -1; |
|
} |
|
|
|
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath, |
|
s_sessionInterface, |
|
QStringLiteral("TakeDevice")); |
|
// major() and minor() macros return ints on FreeBSD instead of uints. |
|
message.setArguments({uint(major(st.st_rdev)), uint(minor(st.st_rdev))}); |
|
|
|
const QDBusMessage reply = QDBusConnection::systemBus().call(message); |
|
if (reply.type() == QDBusMessage::ErrorMessage) { |
|
qCDebug(KWIN_CORE, "Failed to open %s device (%s)", |
|
qPrintable(fileName), qPrintable(reply.errorMessage())); |
|
return -1; |
|
} |
|
|
|
const QDBusUnixFileDescriptor descriptor = reply.arguments().constFirst().value<QDBusUnixFileDescriptor>(); |
|
if (!descriptor.isValid()) { |
|
return -1; |
|
} |
|
|
|
return fcntl(descriptor.fileDescriptor(), F_DUPFD_CLOEXEC, 0); |
|
} |
|
|
|
void ConsoleKitSession::closeRestricted(int fileDescriptor) |
|
{ |
|
struct stat st; |
|
if (fstat(fileDescriptor, &st) < 0) { |
|
close(fileDescriptor); |
|
return; |
|
} |
|
|
|
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath, |
|
s_sessionInterface, |
|
QStringLiteral("ReleaseDevice")); |
|
// major() and minor() macros return ints on FreeBSD instead of uints. |
|
message.setArguments({uint(major(st.st_rdev)), uint(minor(st.st_rdev))}); |
|
|
|
QDBusConnection::systemBus().asyncCall(message); |
|
|
|
close(fileDescriptor); |
|
} |
|
|
|
void ConsoleKitSession::switchTo(uint terminal) |
|
{ |
|
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_seatPath, |
|
s_seatInterface, |
|
QStringLiteral("SwitchTo")); |
|
message.setArguments({ terminal }); |
|
|
|
QDBusConnection::systemBus().asyncCall(message); |
|
} |
|
|
|
ConsoleKitSession::ConsoleKitSession(const QString &sessionPath, QObject *parent) |
|
: Session(parent) |
|
, m_sessionPath(sessionPath) |
|
{ |
|
qDBusRegisterMetaType<DBusConsoleKitSeat>(); |
|
} |
|
|
|
ConsoleKitSession::~ConsoleKitSession() |
|
{ |
|
releaseControl(m_sessionPath); |
|
} |
|
|
|
bool ConsoleKitSession::initialize() |
|
{ |
|
QDBusMessage activeMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath, |
|
s_propertiesInterface, |
|
QStringLiteral("Get")); |
|
activeMessage.setArguments({ s_sessionInterface, QStringLiteral("active") }); |
|
|
|
QDBusMessage seatMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath, |
|
s_propertiesInterface, |
|
QStringLiteral("Get")); |
|
seatMessage.setArguments({ s_sessionInterface, QStringLiteral("Seat") }); |
|
|
|
QDBusMessage terminalMessage = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath, |
|
s_propertiesInterface, |
|
QStringLiteral("Get")); |
|
terminalMessage.setArguments({ s_sessionInterface, QStringLiteral("VTNr") }); |
|
|
|
QDBusPendingReply<QVariant> activeReply = |
|
QDBusConnection::systemBus().asyncCall(activeMessage); |
|
QDBusPendingReply<QVariant> terminalReply = |
|
QDBusConnection::systemBus().asyncCall(terminalMessage); |
|
QDBusPendingReply<QVariant> seatReply = |
|
QDBusConnection::systemBus().asyncCall(seatMessage); |
|
|
|
// We must wait until all replies have been received because the drm backend needs a |
|
// valid seat name to properly select gpu devices, this also simplifies startup code. |
|
activeReply.waitForFinished(); |
|
terminalReply.waitForFinished(); |
|
seatReply.waitForFinished(); |
|
|
|
if (activeReply.isError()) { |
|
qCWarning(KWIN_CORE) << "Failed to query active session property:" << activeReply.error(); |
|
return false; |
|
} |
|
if (terminalReply.isError()) { |
|
qCWarning(KWIN_CORE) << "Failed to query VTNr session property:" << terminalReply.error(); |
|
return false; |
|
} |
|
if (seatReply.isError()) { |
|
qCWarning(KWIN_CORE) << "Failed to query Seat session property:" << seatReply.error(); |
|
return false; |
|
} |
|
|
|
m_isActive = activeReply.value().toBool(); |
|
m_terminal = terminalReply.value().toUInt(); |
|
|
|
const DBusConsoleKitSeat seat = qdbus_cast<DBusConsoleKitSeat>(seatReply.value().value<QDBusArgument>()); |
|
m_seatId = seat.id; |
|
m_seatPath = seat.path.path(); |
|
|
|
QDBusConnection::systemBus().connect(s_serviceName, s_managerPath, s_managerInterface, |
|
QStringLiteral("PrepareForSleep"), |
|
this, |
|
SLOT(handlePrepareForSleep(bool))); |
|
|
|
QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_sessionInterface, |
|
QStringLiteral("PauseDevice"), |
|
this, |
|
SLOT(handlePauseDevice(uint, uint, QString))); |
|
|
|
QDBusConnection::systemBus().connect(s_serviceName, m_sessionPath, s_propertiesInterface, |
|
QStringLiteral("PropertiesChanged"), |
|
this, |
|
SLOT(handlePropertiesChanged(QString, QVariantMap))); |
|
|
|
return true; |
|
} |
|
|
|
void ConsoleKitSession::updateActive(bool active) |
|
{ |
|
if (m_isActive != active) { |
|
m_isActive = active; |
|
emit activeChanged(active); |
|
} |
|
} |
|
|
|
void ConsoleKitSession::handlePauseDevice(uint major, uint minor, const QString &type) |
|
{ |
|
if (type == QLatin1String("pause")) { |
|
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, m_sessionPath, |
|
s_sessionInterface, |
|
QStringLiteral("PauseDeviceComplete")); |
|
message.setArguments({ major, minor }); |
|
|
|
QDBusConnection::systemBus().asyncCall(message); |
|
} |
|
} |
|
|
|
void ConsoleKitSession::handlePropertiesChanged(const QString &interfaceName, const QVariantMap &properties) |
|
{ |
|
if (interfaceName == s_sessionInterface) { |
|
const QVariant active = properties.value(QStringLiteral("active")); |
|
if (active.isValid()) { |
|
updateActive(active.toBool()); |
|
} |
|
} |
|
} |
|
|
|
void ConsoleKitSession::handlePrepareForSleep(bool sleep) |
|
{ |
|
if (!sleep) { |
|
emit awoke(); |
|
} |
|
} |
|
|
|
} // namespace KWin
|
|
|