Use Logind to open/close devices needed by libinput

With libinput we have the problem that we need to have privileges to
open the device files. In order to not need wrappers or suid bits, we
use logind. This means that kwin_wayland has to be the session controler.

A LogindIntegration is added to connect to logind and wrap the dbus
calls. This is based on the logind integration done for ksld in
ksmserver. The LogindIntegration is started by Workspace and the
InputRedirection tries to become the session controller and starts the
libinput integration only after this succeeded.
icc-effect-5.14.5
Martin Gräßlin 2014-08-15 09:30:08 +02:00
parent c4bb3d11fc
commit a918591fef
9 changed files with 411 additions and 36 deletions

View File

@ -362,6 +362,7 @@ set(kwin_KDEINIT_SRCS
virtualdesktops.cpp
xcbutils.cpp
x11eventfilter.cpp
logind.cpp
scripting/scripting.cpp
scripting/workspace_wrapper.cpp
scripting/meta.cpp

View File

@ -21,6 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "client.h"
#include "effects.h"
#include "globalshortcuts.h"
#include "logind.h"
#include "main.h"
#ifdef KWIN_BUILD_TABBOX
#include "tabbox/tabbox.h"
#endif
@ -169,6 +171,33 @@ InputRedirection::InputRedirection(QObject *parent)
, m_pointerWindow()
, m_shortcuts(new GlobalShortcutsManager(this))
{
#if HAVE_INPUT
if (kwinApp()->operationMode() != Application::OperationModeX11) {
LogindIntegration *logind = LogindIntegration::self();
auto takeControl = [logind, this]() {
if (logind->hasSessionControl()) {
setupLibInput();
} else {
logind->takeControl();
connect(logind, &LogindIntegration::hasSessionControlChanged, this, &InputRedirection::setupLibInput);
}
};
if (logind->isConnected()) {
takeControl();
} else {
connect(logind, &LogindIntegration::connectedChanged, this, takeControl);
}
}
#endif
}
InputRedirection::~InputRedirection()
{
s_self = NULL;
}
void InputRedirection::setupLibInput()
{
#if HAVE_INPUT
LibInput::Connection *conn = LibInput::Connection::create(this);
if (conn) {
@ -181,11 +210,6 @@ InputRedirection::InputRedirection(QObject *parent)
#endif
}
InputRedirection::~InputRedirection()
{
s_self = NULL;
}
void InputRedirection::updatePointerWindow()
{
// TODO: handle pointer grab aka popups

View File

@ -155,6 +155,7 @@ private:
static QEvent::Type buttonStateToEvent(PointerButtonState state);
static Qt::MouseButton buttonToQtMouseButton(uint32_t button);
Toplevel *findToplevel(const QPoint &pos);
void setupLibInput();
QPointF m_globalPointer;
QHash<uint32_t, PointerButtonState> m_pointerButtons;
#if HAVE_XKB

View File

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "context.h"
#include "events.h"
#include "../logind.h"
#include <libudev.h>
#include <fcntl.h>
@ -93,13 +94,55 @@ void Context::closeRestrictedCallBack(int fd, void *user_data)
int Context::openRestricted(const char *path, int flags)
{
int fd = open(path, flags);
return fd < 0 ? -errno : fd;
LogindIntegration *logind = LogindIntegration::self();
Q_ASSERT(logind);
int fd = logind->takeDevice(path);
if (fd < 0) {
// failed
return fd;
}
// adjust flags - based on Weston (logind-util.c)
int fl = fcntl(fd, F_GETFL);
auto errorHandling = [fd, this]() {
close(fd);
closeRestricted(fd);
};
if (fl < 0) {
errorHandling();
return -1;
}
if (flags & O_NONBLOCK) {
fl |= O_NONBLOCK;
}
if (fcntl(fd, F_SETFL, fl) < 0) {
errorHandling();
return -1;
}
fl = fcntl(fd, F_GETFD);
if (fl < 0) {
errorHandling();
return -1;
}
if (!(flags & O_CLOEXEC)) {
fl &= ~FD_CLOEXEC;
}
if (fcntl(fd, F_SETFD, fl) < 0) {
errorHandling();
return -1;
}
return fd;
}
void Context::closeRestricted(int fd)
{
close(fd);
LogindIntegration *logind = LogindIntegration::self();
Q_ASSERT(logind);
logind->releaseDevice(fd);
}
Event *Context::event()

208
logind.cpp Normal file
View File

@ -0,0 +1,208 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "logind.h"
#include <QCoreApplication>
#include <QDebug>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusPendingCallWatcher>
#include <QDBusServiceWatcher>
#include <QDBusUnixFileDescriptor>
#include <sys/stat.h>
#include <unistd.h>
namespace KWin
{
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_login1SessionInterface = QStringLiteral("org.freedesktop.login1.Session");
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_logindServiceWatcher(new QDBusServiceWatcher(s_login1Service,
m_bus,
QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration,
this))
, m_connected(false)
, m_sessionControl(false)
{
connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LogindIntegration::logindServiceRegistered);
connect(m_logindServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this,
[this]() {
m_connected = false;
emit connectedChanged();
}
);
// 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)) {
logindServiceRegistered();
}
}
);
}
LogindIntegration::LogindIntegration(QObject *parent)
: LogindIntegration(QDBusConnection::systemBus(), parent)
{
}
LogindIntegration::~LogindIntegration()
{
s_self = nullptr;
}
void LogindIntegration::logindServiceRegistered()
{
// get the current session
QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service,
s_login1Path,
s_login1ManagerInterface,
QStringLiteral("GetSessionByPID"));
message.setArguments(QVariantList() << (quint32) QCoreApplication::applicationPid());
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()) {
qDebug() << "The session is not registered with logind" << reply.error().message();
return;
}
m_sessionPath = reply.value().path();
qDebug() << "Session path:" << m_sessionPath;
m_connected = true;
emit connectedChanged();
}
);
}
void LogindIntegration::takeControl()
{
if (!m_connected || m_sessionPath.isEmpty() || m_sessionControl) {
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service,
m_sessionPath,
s_login1SessionInterface,
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) {
QDBusPendingReply<void> reply = *self;
self->deleteLater();
if (!reply.isValid()) {
qDebug() << "Failed to get session control" << reply.error().message();
emit hasSessionControlChanged(false);
return;
}
qDebug() << "Gained session control";
m_sessionControl = true;
emit hasSessionControlChanged(true);
}
);
}
void LogindIntegration::releaseControl()
{
if (!m_connected || m_sessionPath.isEmpty() || !m_sessionControl) {
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service,
m_sessionPath,
s_login1SessionInterface,
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) {
qDebug() << "Could not stat the path";
return -1;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service,
m_sessionPath,
s_login1SessionInterface,
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) {
qDebug() << "Could not take device" << path << ", cause: " << reply.errorMessage();
return -1;
}
return dup(reply.arguments().first().value<QDBusUnixFileDescriptor>().fileDescriptor());
}
void LogindIntegration::releaseDevice(int fd)
{
struct stat st;
if (fstat(fd, &st) < 0) {
qDebug() << "Could not stat the file descriptor";
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service,
m_sessionPath,
s_login1SessionInterface,
QStringLiteral("ReleaseDevice"));
message.setArguments(QVariantList({QVariant(major(st.st_rdev)), QVariant(minor(st.st_rdev))}));
m_bus.asyncCall(message);
}
} // namespace

76
logind.h Normal file
View File

@ -0,0 +1,76 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_LOGIND_H
#define KWIN_LOGIND_H
#include <kwinglobals.h>
#include <QDBusConnection>
#include <QObject>
class QDBusServiceWatcher;
namespace KWin
{
class LogindIntegration : public QObject
{
Q_OBJECT
public:
~LogindIntegration();
bool isConnected() const {
return m_connected;
}
bool hasSessionControl() const {
return m_sessionControl;
}
void takeControl();
void releaseControl();
int takeDevice(const char *path);
void releaseDevice(int fd);
Q_SIGNALS:
void connectedChanged();
void hasSessionControlChanged(bool);
private:
friend class LogindTest;
/**
* The DBusConnection argument is needed for the unit test. Logind uses the system bus
* on which the unit test's fake logind cannot register to. Thus the unit test need to
* be able to do everything over the session bus. This ctor allows the LogindTest to
* create a LogindIntegration which listens on the session bus.
**/
explicit LogindIntegration(const QDBusConnection &connection, QObject *parent = nullptr);
void logindServiceRegistered();
QDBusConnection m_bus;
QDBusServiceWatcher *m_logindServiceWatcher;
bool m_connected;
QString m_sessionPath;
bool m_sessionControl;
KWIN_SINGLETON(LogindIntegration)
};
}
#endif

View File

@ -24,7 +24,8 @@ if (HAVE_INPUT)
${KWIN_SOURCE_DIR}/libinput/context.cpp
${KWIN_SOURCE_DIR}/libinput/connection.cpp
${KWIN_SOURCE_DIR}/libinput/events.cpp
${KWIN_SOURCE_DIR}/logind.cpp
)
add_executable(libinputtest ${libinputtest_SRCS})
target_link_libraries(libinputtest Qt5::Core Libinput::Libinput ${UDEV_LIBS})
target_link_libraries(libinputtest Qt5::Core Qt5::DBus Libinput::Libinput ${UDEV_LIBS})
endif()

View File

@ -19,6 +19,7 @@
*/
#include "../input.h"
#include "../libinput/connection.h"
#include "../logind.h"
#include <QCoreApplication>
@ -31,37 +32,55 @@ int main(int argc, char **argv)
using namespace KWin::LibInput;
QCoreApplication app(argc, argv);
Connection *conn = Connection::create(&app);
if (!conn) {
std::cerr << "Failed to create LibInput integration" << std::endl;
return 1;
}
conn->setScreenSize(QSize(100, 100));
conn->setup();
QObject::connect(conn, &Connection::keyChanged,
[](uint32_t key, KWin::InputRedirection::KeyboardKeyState state) {
std::cout << "Got key event" << std::endl;;
if (key == KEY_Q && state == KWin::InputRedirection::KeyboardKeyReleased) {
QCoreApplication::instance()->quit();
KWin::LogindIntegration *logind = KWin::LogindIntegration::create(&app);
QObject::connect(logind, &KWin::LogindIntegration::connectedChanged,
[logind]() {
if (logind->isConnected()) {
logind->takeControl();
}
}
);
QObject::connect(conn, &Connection::pointerButtonChanged,
[](uint32_t button, KWin::InputRedirection::PointerButtonState state) {
std::cout << "Got pointer button event" << std::endl;
if (button == BTN_MIDDLE && state == KWin::InputRedirection::PointerButtonReleased) {
QCoreApplication::instance()->quit();
QObject::connect(logind, &KWin::LogindIntegration::hasSessionControlChanged,
[&app](bool valid) {
if (!valid) {
return;
}
}
);
QObject::connect(conn, &Connection::pointerMotion,
[](QPointF delta) {
std::cout << "Got pointer motion: " << delta.x() << "/" << delta.y() << std::endl;
}
);
QObject::connect(conn, &Connection::pointerAxisChanged,
[](KWin::InputRedirection::PointerAxis axis, qreal delta) {
std::cout << "Axis: " << axis << " with delta" << delta << std::endl;
Connection *conn = Connection::create(&app);
if (!conn) {
std::cerr << "Failed to create LibInput integration" << std::endl;
::exit(1);
}
conn->setScreenSize(QSize(100, 100));
conn->setup();
QObject::connect(conn, &Connection::keyChanged,
[](uint32_t key, KWin::InputRedirection::KeyboardKeyState state) {
std::cout << "Got key event" << std::endl;;
if (key == KEY_Q && state == KWin::InputRedirection::KeyboardKeyReleased) {
QCoreApplication::instance()->quit();
}
}
);
QObject::connect(conn, &Connection::pointerButtonChanged,
[](uint32_t button, KWin::InputRedirection::PointerButtonState state) {
std::cout << "Got pointer button event" << std::endl;
if (button == BTN_MIDDLE && state == KWin::InputRedirection::PointerButtonReleased) {
QCoreApplication::instance()->quit();
}
}
);
QObject::connect(conn, &Connection::pointerMotion,
[](QPointF delta) {
std::cout << "Got pointer motion: " << delta.x() << "/" << delta.y() << std::endl;
}
);
QObject::connect(conn, &Connection::pointerAxisChanged,
[](KWin::InputRedirection::PointerAxis axis, qreal delta) {
std::cout << "Axis: " << axis << " with delta" << delta << std::endl;
}
);
QObject::connect(&app, &QCoreApplication::aboutToQuit, [conn] { delete conn; });
}
);

View File

@ -42,6 +42,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "focuschain.h"
#include "group.h"
#include "input.h"
#include "logind.h"
#include "killwindow.h"
#include "netinfo.h"
#include "outline.h"
@ -145,6 +146,7 @@ Workspace::Workspace(bool restore)
// first initialize the extensions
Xcb::Extensions::self();
LogindIntegration::create(this);
InputRedirection::create(this);
// start the Wayland Backend - will only be created if WAYLAND_DISPLAY is present