Add a libeis input backend plugin

This adds a libeis backend via plugin which supports clients sending
emulated input events. No public listening socket is exposed,
clients are expected to go through the RemoteDesktop portal.
In order to restrict the device types available to clients
according to what was approved via the portal a separate eis
context per portal request is created. The communication with
the portal happens through a simple dbus interface where cookies
are  handed out for each eis context so the portal can inform KWin
when the portal session is closed/should end.
This commit is contained in:
David Redondo 2024-03-21 12:11:16 +01:00 committed by Vlad Zahorodnii
parent 2120e18729
commit 499d006e3a
18 changed files with 914 additions and 1 deletions

View file

@ -11,4 +11,4 @@ suse_tumbleweed_qt66_reduced_featureset:
extends: suse_tumbleweed_qt66
script:
- git config --global --add safe.directory $CI_PROJECT_DIR
- python3 -u ci-utilities/run-ci-build.py --project $CI_PROJECT_NAME --branch $CI_COMMIT_REF_NAME --platform Linux --extra-cmake-args="-DKWIN_BUILD_KCMS=OFF -DKWIN_BUILD_SCREENLOCKER=OFF -DKWIN_BUILD_TABBOX=OFF -DKWIN_BUILD_ACTIVITIES=OFF -DKWIN_BUILD_RUNNERS=OFF -DKWIN_BUILD_NOTIFICATIONS=OFF -DKWIN_BUILD_GLOBALSHORTCUTS=OFF -DKWIN_BUILD_X11=OFF" --skip-publishing
- python3 -u ci-utilities/run-ci-build.py --project $CI_PROJECT_NAME --branch $CI_COMMIT_REF_NAME --platform Linux --extra-cmake-args="-DKWIN_BUILD_KCMS=OFF -DKWIN_BUILD_SCREENLOCKER=OFF -DKWIN_BUILD_TABBOX=OFF -DKWIN_BUILD_ACTIVITIES=OFF -DKWIN_BUILD_RUNNERS=OFF -DKWIN_BUILD_NOTIFICATIONS=OFF -DKWIN_BUILD_GLOBALSHORTCUTS=OFF -DKWIN_BUILD_X11=OFF -DKWIN_BUILD_EIS=OFF" --skip-publishing

View file

@ -290,6 +290,9 @@ endif()
find_package(Libinput 1.19)
set_package_properties(Libinput PROPERTIES TYPE REQUIRED PURPOSE "Required for input handling on Wayland.")
find_package(Libeis-1.0)
set_package_properties(Libeis PROPERTIES TYPE OPTIONAL PURPOSE "Required for emulated input handling.")
find_package(UDev)
set_package_properties(UDev PROPERTIES
URL "https://www.freedesktop.org/wiki/Software/systemd/"
@ -395,6 +398,7 @@ ecm_find_qmlmodule(org.kde.plasma.components 2.0)
########### configure tests ###############
cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "PlasmaActivities_FOUND" OFF)
option(KWIN_BUILD_RUNNERS "Enable building of KWin with krunner support" ON)
cmake_dependent_option(KWIN_BUILD_EIS "Enable building KWin with libeis support" ON "Libeis-1.0_FOUND" OFF)
check_symbol_exists(SCHED_RESET_ON_FORK "sched.h" HAVE_SCHED_RESET_ON_FORK)
add_feature_info("SCHED_RESET_ON_FORK"

View file

@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redono.de>
# SPDX-License-Identifier: BSD-3-Clause
find_package(PkgConfig)
pkg_check_modules(PKG_Libeis QUIET libeis-1.0)
find_path(Libeis_INCLUDE_DIR
NAMES libeis.h
HINTS ${PKG_Libeis_INCLUDE_DIRS}
)
find_library(Libeis_LIBRARY
NAMES eis
PATHS ${PKG_Libeis_LIBRARY_DIRS}
)
set(Libeis_VERSION ${PKG_Libeis_VERSION})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libeis-1.0
FOUND_VAR Libeis-1.0_FOUND
REQUIRED_VARS
Libeis_LIBRARY
Libeis_INCLUDE_DIR
VERSION_VAR Libeis_VERSION
)
if(Libeis-1.0_FOUND AND NOT TARGET Libeis::Libeis)
add_library(Libeis::Libeis UNKNOWN IMPORTED)
set_target_properties(Libeis::Libeis PROPERTIES
IMPORTED_LOCATION "${Libeis_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${PKG_Libeis_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${Libeis_INCLUDE_DIR}"
)
endif()
mark_as_advanced(Libeis_INCLUDE_DIR Libeis_LIBRARY)

View file

@ -2665,6 +2665,7 @@ InputRedirection::InputRedirection(QObject *parent)
qRegisterMetaType<InputRedirection::PointerButtonState>();
qRegisterMetaType<InputRedirection::PointerAxis>();
setupInputBackends();
connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace);
}

View file

@ -64,6 +64,11 @@ Qt::KeyboardModifiers KeyboardInputRedirection::modifiersRelevantForGlobalShortc
return m_xkb->modifiersRelevantForGlobalShortcuts();
}
KeyboardLayout *KeyboardInputRedirection::keyboardLayout() const
{
return m_keyboardLayout;
}
class KeyStateChangedSpy : public InputEventSpy
{
public:

View file

@ -57,6 +57,7 @@ public:
Xkb *xkb() const;
Qt::KeyboardModifiers modifiers() const;
Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const;
KeyboardLayout *keyboardLayout() const;
Q_SIGNALS:
void ledsChanged(KWin::LEDs);

View file

@ -126,3 +126,6 @@ endif()
if(TARGET K::KGlobalAccelD)
add_subdirectory(kglobalaccel)
endif()
if (KWIN_BUILD_EIS)
add_subdirectory(eis)
endif()

View file

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redono.de>
# SPDX-License-Identifier: BSD-3-Clause
kcoreaddons_add_plugin(eis INSTALL_NAMESPACE "kwin/plugins")
ecm_qt_declare_logging_category(eis
HEADER libeis_logging.h
IDENTIFIER KWIN_EIS
CATEGORY_NAME kwin_libeis
DEFAULT_SEVERITY Debug
)
target_sources(eis PRIVATE
main.cpp
eisdevice.cpp
eisbackend.cpp
eiscontext.cpp
eisplugin.cpp
)
target_link_libraries(eis PRIVATE kwin Libeis::Libeis XKB::XKB)

View file

@ -0,0 +1,166 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "eisbackend.h"
#include "eiscontext.h"
#include "eisdevice.h"
#include "libeis_logging.h"
#include "core/output.h"
#include "input.h"
#include "keyboard_input.h"
#include "keyboard_layout.h"
#include "main.h"
#include "workspace.h"
#include "xkb.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusServiceWatcher>
#include <QFlags>
#include <QSocketNotifier>
#include <libeis.h>
#include <ranges>
namespace KWin
{
EisBackend::EisBackend(QObject *parent)
: KWin::InputBackend(parent)
, m_serviceWatcher(new QDBusServiceWatcher(this))
{
m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &service) {
std::erase_if(m_contexts, [&service](const std::unique_ptr<EisContext> &context) {
return context->dbusService == service;
});
m_serviceWatcher->removeWatchedService(service);
});
}
EisBackend::~EisBackend()
{
}
void EisBackend::initialize()
{
const QByteArray keyMap = input()->keyboard()->xkb()->keymapContents();
m_keymapFile = RamFile("eis keymap", keyMap.data(), keyMap.size(), RamFile::Flag::SealWrite);
connect(input()->keyboard()->keyboardLayout(), &KeyboardLayout::layoutsReconfigured, this, [this] {
const QByteArray keyMap = input()->keyboard()->xkb()->keymapContents();
m_keymapFile = RamFile("eis keymap", keyMap.data(), keyMap.size(), RamFile::Flag::SealWrite);
for (const auto &context : m_contexts) {
context->updateKeymap();
}
});
QDBusConnection::sessionBus().registerObject("/org/kde/KWin/EIS/RemoteDesktop", "org.kde.KWin.EIS.RemoteDesktop", this, QDBusConnection::ExportAllInvokables);
}
void EisBackend::updateScreens()
{
for (const auto &context : m_contexts) {
context->updateScreens();
}
}
QDBusUnixFileDescriptor EisBackend::connectToEIS(const int &capabilities, int &cookie)
{
constexpr int keyboardPortal = 1;
constexpr int pointerPortal = 2;
constexpr int touchPortal = 4;
QFlags<eis_device_capability> eisCapabilities;
if (capabilities & keyboardPortal) {
eisCapabilities |= EIS_DEVICE_CAP_KEYBOARD;
}
if (capabilities & pointerPortal) {
eisCapabilities |= EIS_DEVICE_CAP_POINTER;
eisCapabilities |= EIS_DEVICE_CAP_POINTER_ABSOLUTE;
eisCapabilities |= EIS_DEVICE_CAP_BUTTON;
eisCapabilities |= EIS_DEVICE_CAP_SCROLL;
}
if (capabilities & touchPortal) {
eisCapabilities |= EIS_DEVICE_CAP_TOUCH;
}
const QString dbusService = message().service();
static int s_cookie = 0;
cookie = ++s_cookie;
m_contexts.push_back(std::make_unique<EisContext>(this, eisCapabilities, cookie, dbusService));
m_serviceWatcher->addWatchedService(dbusService);
return QDBusUnixFileDescriptor(m_contexts.back()->addClient());
}
void EisBackend::disconnect(int cookie)
{
auto it = std::ranges::find(m_contexts, cookie, [](const std::unique_ptr<EisContext> &context) {
return context->cookie;
});
if (it != std::ranges::end(m_contexts)) {
m_contexts.erase(it);
}
}
eis_device *createDevice(eis_seat *seat, const QByteArray &name)
{
auto device = eis_seat_new_device(seat);
auto client = eis_seat_get_client(seat);
const char *clientName = eis_client_get_name(client);
const QByteArray deviceName = clientName + (' ' + name);
eis_device_configure_name(device, deviceName);
return device;
}
eis_device *EisBackend::createPointer(eis_seat *seat)
{
auto device = createDevice(seat, "eis pointer");
eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER);
eis_device_configure_capability(device, EIS_DEVICE_CAP_SCROLL);
eis_device_configure_capability(device, EIS_DEVICE_CAP_BUTTON);
return device;
}
eis_device *EisBackend::createAbsoluteDevice(eis_seat *seat)
{
auto device = createDevice(seat, "eis absolute device");
auto eisDevice = device;
eis_device_configure_capability(eisDevice, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
eis_device_configure_capability(eisDevice, EIS_DEVICE_CAP_SCROLL);
eis_device_configure_capability(eisDevice, EIS_DEVICE_CAP_BUTTON);
eis_device_configure_capability(eisDevice, EIS_DEVICE_CAP_TOUCH);
const auto outputs = workspace()->outputs();
for (const auto output : outputs) {
auto region = eis_device_new_region(eisDevice);
const QRect outputGeometry = output->geometry();
eis_region_set_offset(region, outputGeometry.x(), outputGeometry.y());
eis_region_set_size(region, outputGeometry.width(), outputGeometry.height());
eis_region_set_physical_scale(region, output->scale());
eis_region_add(region);
eis_region_unref(region);
};
return device;
}
eis_device *EisBackend::createKeyboard(eis_seat *seat)
{
auto device = createDevice(seat, "eis keyboard");
eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD);
if (m_keymapFile.isValid()) {
auto keymap = eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, m_keymapFile.fd(), m_keymapFile.size());
eis_keymap_add(keymap);
eis_keymap_unref(keymap);
}
return device;
}
}

View file

@ -0,0 +1,52 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include "core/inputbackend.h"
#include "utils/ramfile.h"
#include <QDBusContext>
#include <QDBusUnixFileDescriptor>
#include <memory>
extern "C" {
struct eis;
struct eis_device;
struct eis_seat;
}
class QDBusServiceWatcher;
namespace KWin
{
class EisContext;
class EisBackend : public KWin::InputBackend, public QDBusContext
{
Q_OBJECT
public:
explicit EisBackend(QObject *parent = nullptr);
~EisBackend() override;
void initialize() override;
void updateScreens() override;
Q_INVOKABLE QDBusUnixFileDescriptor connectToEIS(const int &capabilities, int &cookie);
Q_INVOKABLE void disconnect(int cookie);
eis_device *createKeyboard(eis_seat *seat);
eis_device *createPointer(eis_seat *seat);
eis_device *createAbsoluteDevice(eis_seat *seat);
private:
QDBusServiceWatcher *m_serviceWatcher;
RamFile m_keymapFile;
std::vector<std::unique_ptr<EisContext>> m_contexts;
};
}

View file

@ -0,0 +1,326 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "eiscontext.h"
#include "eisbackend.h"
#include "eisdevice.h"
#include "libeis_logging.h"
namespace KWin
{
static void eis_log_handler(eis *eis, eis_log_priority priority, const char *message, eis_log_context *context)
{
switch (priority) {
case EIS_LOG_PRIORITY_DEBUG:
qCDebug(KWIN_EIS) << "Libeis:" << message;
break;
case EIS_LOG_PRIORITY_INFO:
qCInfo(KWIN_EIS) << "Libeis:" << message;
break;
case EIS_LOG_PRIORITY_WARNING:
qCWarning(KWIN_EIS) << "Libeis:" << message;
break;
case EIS_LOG_PRIORITY_ERROR:
qCritical(KWIN_EIS) << "Libeis:" << message;
break;
}
}
struct EisClient
{
public:
EisClient(eis_client *client, eis_seat *seat)
: handle(client)
, seat(seat)
{
eis_seat_set_user_data(seat, this);
eis_client_set_user_data(client, this);
}
~EisClient()
{
eis_seat_unref(seat);
eis_client_disconnect(handle);
}
eis_client *handle;
eis_seat *seat;
std::unique_ptr<EisDevice> absoluteDevice;
std::unique_ptr<EisDevice> pointer;
std::unique_ptr<EisDevice> keyboard;
};
EisContext::EisContext(KWin::EisBackend *backend, QFlags<eis_device_capability> allowedCapabilities, int cookie, const QString &dbusService)
: cookie(cookie)
, dbusService(dbusService)
, m_backend(backend)
, m_eisContext(eis_new(this))
, m_allowedCapabilities(allowedCapabilities)
, m_socketNotifier(eis_get_fd(m_eisContext), QSocketNotifier::Read)
{
eis_setup_backend_fd(m_eisContext);
eis_log_set_priority(m_eisContext, EIS_LOG_PRIORITY_DEBUG);
eis_log_set_handler(m_eisContext, eis_log_handler);
QObject::connect(&m_socketNotifier, &QSocketNotifier::activated, [this] {
handleEvents();
});
}
EisContext::~EisContext()
{
for (const auto &client : m_clients) {
if (client->absoluteDevice) {
Q_EMIT m_backend->deviceRemoved(client->absoluteDevice.get());
}
if (client->pointer) {
Q_EMIT m_backend->deviceRemoved(client->pointer.get());
}
if (client->keyboard) {
Q_EMIT m_backend->deviceRemoved(client->keyboard.get());
}
}
}
void EisContext::updateScreens()
{
for (const auto &client : m_clients) {
if (client->absoluteDevice) {
client->absoluteDevice->changeDevice(m_backend->createAbsoluteDevice(client->seat));
}
}
}
void EisContext::updateKeymap()
{
for (const auto &client : m_clients) {
if (client->keyboard) {
client->keyboard->changeDevice(m_backend->createKeyboard(client->seat));
}
}
}
int EisContext::addClient()
{
return eis_backend_fd_add_client(m_eisContext);
}
static std::chrono::microseconds currentTime()
{
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
}
void EisContext::handleEvents()
{
auto eventDevice = [](eis_event *event) {
return static_cast<EisDevice *>(eis_device_get_user_data(eis_event_get_device(event)));
};
eis_dispatch(m_eisContext);
while (eis_event *const event = eis_get_event(m_eisContext)) {
switch (eis_event_get_type(event)) {
case EIS_EVENT_CLIENT_CONNECT: {
auto client = eis_event_get_client(event);
const char *clientName = eis_client_get_name(client);
if (!eis_client_is_sender(client)) {
qCDebug(KWIN_EIS) << "disconnecting receiving client" << clientName;
eis_client_disconnect(client);
break;
}
eis_client_connect(client);
auto seat = eis_client_new_seat(client, QByteArrayLiteral(" seat").prepend(clientName));
constexpr std::array allCapabilities{EIS_DEVICE_CAP_POINTER, EIS_DEVICE_CAP_POINTER_ABSOLUTE, EIS_DEVICE_CAP_KEYBOARD, EIS_DEVICE_CAP_TOUCH, EIS_DEVICE_CAP_SCROLL, EIS_DEVICE_CAP_BUTTON};
for (auto capability : allCapabilities) {
if (m_allowedCapabilities & capability) {
eis_seat_configure_capability(seat, capability);
}
}
eis_seat_add(seat);
m_clients.emplace_back(std::make_unique<EisClient>(client, seat));
qCDebug(KWIN_EIS) << "New eis client" << clientName;
break;
}
case EIS_EVENT_CLIENT_DISCONNECT: {
auto client = eis_event_get_client(event);
qCDebug(KWIN_EIS) << "Client disconnected" << eis_client_get_name(client);
if (auto seat = static_cast<EisClient *>(eis_client_get_user_data(client))) {
m_clients.erase(std::ranges::find(m_clients, seat, &std::unique_ptr<EisClient>::get));
}
break;
}
case EIS_EVENT_SEAT_BIND: {
auto seat = eis_event_get_seat(event);
auto clientSeat = static_cast<EisClient *>(eis_seat_get_user_data(seat));
qCDebug(KWIN_EIS) << "Client" << eis_client_get_name(eis_event_get_client(event)) << "bound to seat" << eis_seat_get_name(seat);
auto updateDevice = [event, this](std::unique_ptr<EisDevice> &device, auto &&createFunc, bool shouldHave) {
if (shouldHave) {
if (!device) {
device = std::make_unique<EisDevice>(std::invoke(createFunc, m_backend, (eis_event_get_seat(event))));
device->setEnabled(true);
Q_EMIT m_backend->deviceAdded(device.get());
}
} else if (device) {
Q_EMIT m_backend->deviceRemoved(device.get());
device.reset();
}
};
updateDevice(clientSeat->absoluteDevice, &EisBackend::createAbsoluteDevice, eis_event_seat_has_capability(event, EIS_DEVICE_CAP_POINTER_ABSOLUTE) || eis_event_seat_has_capability(event, EIS_DEVICE_CAP_TOUCH));
updateDevice(clientSeat->pointer, &EisBackend::createPointer, eis_event_seat_has_capability(event, EIS_DEVICE_CAP_POINTER));
updateDevice(clientSeat->keyboard, &EisBackend::createKeyboard, eis_event_seat_has_capability(event, EIS_DEVICE_CAP_KEYBOARD));
break;
}
case EIS_EVENT_DEVICE_CLOSED: {
auto device = eventDevice(event);
qCDebug(KWIN_EIS) << "Device" << device->name() << "closed by client";
Q_EMIT m_backend->deviceRemoved(device);
auto seat = static_cast<EisClient *>(eis_seat_get_user_data(eis_device_get_seat(device->handle())));
if (device == seat->absoluteDevice.get()) {
seat->absoluteDevice.reset();
} else if (device == seat->keyboard.get()) {
seat->keyboard.reset();
} else if (device == seat->pointer.get()) {
seat->pointer.reset();
}
break;
}
case EIS_EVENT_FRAME: {
auto device = eventDevice(event);
qCDebug(KWIN_EIS) << "Frame for device" << device->name();
if (device->isTouch()) {
Q_EMIT device->touchFrame(device);
}
if (device->isPointer()) {
Q_EMIT device->pointerFrame(device);
}
break;
}
case EIS_EVENT_DEVICE_START_EMULATING: {
auto device = eventDevice(event);
qCDebug(KWIN_EIS) << "Device" << device->name() << "starts emulating";
break;
}
case EIS_EVENT_DEVICE_STOP_EMULATING: {
auto device = eventDevice(event);
qCDebug(KWIN_EIS) << "Device" << device->name() << "stops emulating";
break;
}
case EIS_EVENT_POINTER_MOTION: {
auto device = eventDevice(event);
const double x = eis_event_pointer_get_dx(event);
const double y = eis_event_pointer_get_dy(event);
qCDebug(KWIN_EIS) << device->name() << "pointer motion" << x << y;
const QPointF delta(x, y);
Q_EMIT device->pointerMotion(delta, delta, currentTime(), device);
break;
}
case EIS_EVENT_POINTER_MOTION_ABSOLUTE: {
auto device = eventDevice(event);
const double x = eis_event_pointer_get_absolute_x(event);
const double y = eis_event_pointer_get_absolute_y(event);
qCDebug(KWIN_EIS) << device->name() << "pointer motion absolute" << x << y;
Q_EMIT device->pointerMotionAbsolute({x, y}, currentTime(), device);
break;
}
case EIS_EVENT_BUTTON_BUTTON: {
auto device = eventDevice(event);
const quint32 button = eis_event_button_get_button(event);
const bool press = eis_event_button_get_is_press(event);
qCDebug(KWIN_EIS) << device->name() << "button" << button << press;
if (press) {
device->pressedButtons.push_back(button);
} else {
std::erase(device->pressedButtons, button);
}
Q_EMIT device->pointerButtonChanged(button, press ? InputRedirection::PointerButtonPressed : InputRedirection::PointerButtonReleased, currentTime(), device);
break;
}
case EIS_EVENT_SCROLL_DELTA: {
auto device = eventDevice(event);
const auto x = eis_event_scroll_get_dx(event);
const auto y = eis_event_scroll_get_dy(event);
qCDebug(KWIN_EIS) << device->name() << "scroll" << x << y;
if (x != 0) {
Q_EMIT device->pointerAxisChanged(InputRedirection::PointerAxisHorizontal, x, 0, InputRedirection::PointerAxisSourceUnknown, currentTime(), device);
}
if (y != 0) {
Q_EMIT device->pointerAxisChanged(InputRedirection::PointerAxisVertical, y, 0, InputRedirection::PointerAxisSourceUnknown, currentTime(), device);
}
break;
}
case EIS_EVENT_SCROLL_STOP:
case EIS_EVENT_SCROLL_CANCEL: {
auto device = eventDevice(event);
if (eis_event_scroll_get_stop_x(event)) {
qCDebug(KWIN_EIS) << device->name() << "scroll x" << (eis_event_get_type(event) == EIS_EVENT_SCROLL_STOP ? "stop" : "cancel");
Q_EMIT device->pointerAxisChanged(InputRedirection::PointerAxisHorizontal, 0, 0, InputRedirection::PointerAxisSourceUnknown, currentTime(), device);
}
if (eis_event_scroll_get_stop_y(event)) {
qCDebug(KWIN_EIS) << device->name() << "scroll y" << (eis_event_get_type(event) == EIS_EVENT_SCROLL_STOP ? "stop" : "cancel");
Q_EMIT device->pointerAxisChanged(InputRedirection::PointerAxisVertical, 0, 0, InputRedirection::PointerAxisSourceUnknown, currentTime(), device);
}
break;
}
case EIS_EVENT_SCROLL_DISCRETE: {
auto device = eventDevice(event);
const double x = eis_event_scroll_get_discrete_dx(event);
const double y = eis_event_scroll_get_discrete_dy(event);
qCDebug(KWIN_EIS) << device->name() << "scroll discrete" << x << y;
// otherwise no scroll event
constexpr auto anglePer120Step = 15 / 120.0;
if (x != 0) {
Q_EMIT device->pointerAxisChanged(InputRedirection::PointerAxisHorizontal, x * anglePer120Step, x, InputRedirection::PointerAxisSourceUnknown, currentTime(), device);
}
if (y != 0) {
Q_EMIT device->pointerAxisChanged(InputRedirection::PointerAxisVertical, y * anglePer120Step, y, InputRedirection::PointerAxisSourceUnknown, currentTime(), device);
}
break;
}
case EIS_EVENT_KEYBOARD_KEY: {
auto device = eventDevice(event);
const quint32 key = eis_event_keyboard_get_key(event);
const bool press = eis_event_keyboard_get_key_is_press(event);
qCDebug(KWIN_EIS) << device->name() << "key" << key << press;
if (press) {
device->pressedButtons.push_back(key);
} else {
std::erase(device->pressedButtons, key);
}
Q_EMIT device->keyChanged(key, press ? InputRedirection::KeyboardKeyPressed : InputRedirection::KeyboardKeyReleased, currentTime(), device);
break;
}
case EIS_EVENT_TOUCH_DOWN: {
auto device = eventDevice(event);
const auto x = eis_event_touch_get_x(event);
const auto y = eis_event_touch_get_y(event);
const auto id = eis_event_touch_get_id(event);
qCDebug(KWIN_EIS) << device->name() << "touch down" << id << x << y;
device->activeTouches.push_back(id);
Q_EMIT device->touchDown(id, {x, y}, currentTime(), device);
break;
}
case EIS_EVENT_TOUCH_UP: {
auto device = eventDevice(event);
const auto id = eis_event_touch_get_id(event);
qCDebug(KWIN_EIS) << device->name() << "touch up" << id;
std::erase(device->activeTouches, id);
Q_EMIT device->touchUp(id, currentTime(), device);
break;
}
case EIS_EVENT_TOUCH_MOTION: {
auto device = eventDevice(event);
const auto x = eis_event_touch_get_x(event);
const auto y = eis_event_touch_get_y(event);
const auto id = eis_event_touch_get_id(event);
qCDebug(KWIN_EIS) << device->name() << "touch move" << id << x << y;
Q_EMIT device->touchMotion(id, {x, y}, currentTime(), device);
break;
}
}
eis_event_unref(event);
}
}
}

View file

@ -0,0 +1,47 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <QFlag>
#include <QSocketNotifier>
#include <QString>
#include <libeis.h>
#include <memory>
#include <vector>
namespace KWin
{
class EisBackend;
struct EisClient;
class EisContext
{
public:
EisContext(EisBackend *backend, QFlags<eis_device_capability> allowedCapabilities, int cookie, const QString &dbusService);
~EisContext();
int addClient();
void updateScreens();
void updateKeymap();
const int cookie;
const QString dbusService;
private:
void handleEvents();
EisBackend *m_backend;
eis *m_eisContext;
QFlags<eis_device_capability> m_allowedCapabilities;
QSocketNotifier m_socketNotifier;
std::vector<std::unique_ptr<EisClient>> m_clients;
};
}

View file

@ -0,0 +1,125 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "eisdevice.h"
#include <libeis.h>
namespace KWin
{
static std::chrono::microseconds currentTime()
{
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
}
EisDevice::EisDevice(eis_device *device, QObject *parent)
: InputDevice(parent)
, m_device(device)
{
eis_device_set_user_data(device, this);
eis_device_add(device);
}
EisDevice::~EisDevice()
{
for (const auto button : pressedButtons) {
Q_EMIT pointerButtonChanged(button, InputRedirection::PointerButtonReleased, currentTime(), this);
}
for (const auto key : pressedKeys) {
Q_EMIT keyChanged(key, InputRedirection::KeyboardKeyReleased, currentTime(), this);
}
if (!activeTouches.empty()) {
Q_EMIT touchCanceled(this);
}
eis_device_remove(m_device);
eis_device_unref(m_device);
}
void EisDevice::changeDevice(eis_device *device)
{
eis_device_set_user_data(m_device, nullptr);
eis_device_remove(m_device);
eis_device_unref(m_device);
m_device = device;
eis_device_set_user_data(device, this);
eis_device_add(device);
if (m_enabled) {
eis_device_resume(device);
}
}
QString EisDevice::sysName() const
{
return QString();
}
QString EisDevice::name() const
{
return QString::fromUtf8(eis_device_get_name(m_device));
}
bool EisDevice::isEnabled() const
{
return m_enabled;
}
void EisDevice::setEnabled(bool enabled)
{
m_enabled = enabled;
enabled ? eis_device_resume(m_device) : eis_device_pause(m_device);
}
LEDs EisDevice::leds() const
{
return LEDs();
}
void EisDevice::setLeds(LEDs leds)
{
}
bool EisDevice::isKeyboard() const
{
return eis_device_has_capability(m_device, EIS_DEVICE_CAP_KEYBOARD);
}
bool EisDevice::isPointer() const
{
return eis_device_has_capability(m_device, EIS_DEVICE_CAP_POINTER) || eis_device_has_capability(m_device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
}
bool EisDevice::isTouchpad() const
{
return false;
}
bool EisDevice::isTouch() const
{
return eis_device_has_capability(m_device, EIS_DEVICE_CAP_TOUCH);
}
bool EisDevice::isTabletTool() const
{
return false;
}
bool EisDevice::isTabletPad() const
{
return false;
}
bool EisDevice::isTabletModeSwitch() const
{
return false;
}
bool EisDevice::isLidSwitch() const
{
return false;
}
}

View file

@ -0,0 +1,56 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include "core/inputdevice.h"
struct eis_device;
namespace KWin
{
class EisDevice : public InputDevice
{
Q_OBJECT
public:
explicit EisDevice(eis_device *device, QObject *parent = nullptr);
~EisDevice() override;
eis_device *handle() const
{
return m_device;
}
void changeDevice(eis_device *device);
std::vector<quint32> pressedButtons;
std::vector<quint32> pressedKeys;
std::vector<int> activeTouches;
QString sysName() const override;
QString name() const override;
bool isEnabled() const override;
void setEnabled(bool enabled) override;
LEDs leds() const override;
void setLeds(LEDs leds) override;
bool isKeyboard() const override;
bool isPointer() const override;
bool isTouchpad() const override;
bool isTouch() const override;
bool isTabletTool() const override;
bool isTabletPad() const override;
bool isTabletModeSwitch() const override;
bool isLidSwitch() const override;
private:
eis_device *m_device;
bool m_enabled;
};
}

View file

@ -0,0 +1,15 @@
#include "eisplugin.h"
#include "eisbackend.h"
#include "input.h"
EisPlugin::EisPlugin()
: Plugin()
{
KWin::input()->addInputBackend(std::make_unique<KWin::EisBackend>());
}
EisPlugin::~EisPlugin()
{
}

View file

@ -0,0 +1,17 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redono.de>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include "plugin.h"
class EisPlugin : public KWin::Plugin
{
Q_OBJECT
public:
EisPlugin();
~EisPlugin() override;
};

32
src/plugins/eis/main.cpp Normal file
View file

@ -0,0 +1,32 @@
/*
SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redono.de>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "main.h"
#include "plugin.h"
#include "eisplugin.h"
class KWIN_EXPORT EisPluginFactory : public KWin::PluginFactory
{
Q_OBJECT
Q_PLUGIN_METADATA(IID PluginFactory_iid FILE "metadata.json")
Q_INTERFACES(KWin::PluginFactory)
public:
std::unique_ptr<KWin::Plugin> create() const override
{
switch (KWin::kwinApp()->operationMode()) {
case KWin::Application::OperationModeXwayland:
case KWin::Application::OperationModeWaylandOnly:
return std::make_unique<EisPlugin>();
case KWin::Application::OperationModeX11:
default:
return nullptr;
}
}
};
#include "main.moc"

View file

@ -0,0 +1,5 @@
{
"KPlugin": {
"EnabledByDefault": true
}
}