diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9f5c70bfd2..2f5fab1c24 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 165d8ba421..9859535812 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/cmake/modules/FindLibeis-1.0.cmake b/cmake/modules/FindLibeis-1.0.cmake new file mode 100644 index 0000000000..e1a841d5fa --- /dev/null +++ b/cmake/modules/FindLibeis-1.0.cmake @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2024 David Redondo +# 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) diff --git a/src/input.cpp b/src/input.cpp index 144d85dde7..edf58e2217 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -2665,6 +2665,7 @@ InputRedirection::InputRedirection(QObject *parent) qRegisterMetaType(); qRegisterMetaType(); setupInputBackends(); + connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); } diff --git a/src/keyboard_input.cpp b/src/keyboard_input.cpp index 8f9325cd9a..a1dc7ac4cd 100644 --- a/src/keyboard_input.cpp +++ b/src/keyboard_input.cpp @@ -64,6 +64,11 @@ Qt::KeyboardModifiers KeyboardInputRedirection::modifiersRelevantForGlobalShortc return m_xkb->modifiersRelevantForGlobalShortcuts(); } +KeyboardLayout *KeyboardInputRedirection::keyboardLayout() const +{ + return m_keyboardLayout; +} + class KeyStateChangedSpy : public InputEventSpy { public: diff --git a/src/keyboard_input.h b/src/keyboard_input.h index 14d5cce933..46d4f8e6b7 100644 --- a/src/keyboard_input.h +++ b/src/keyboard_input.h @@ -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); diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 0a8de26c15..7be67562e1 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -126,3 +126,6 @@ endif() if(TARGET K::KGlobalAccelD) add_subdirectory(kglobalaccel) endif() +if (KWIN_BUILD_EIS) + add_subdirectory(eis) +endif() diff --git a/src/plugins/eis/CMakeLists.txt b/src/plugins/eis/CMakeLists.txt new file mode 100644 index 0000000000..557925f241 --- /dev/null +++ b/src/plugins/eis/CMakeLists.txt @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2024 David Redondo +# 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) diff --git a/src/plugins/eis/eisbackend.cpp b/src/plugins/eis/eisbackend.cpp new file mode 100644 index 0000000000..43eca32d4b --- /dev/null +++ b/src/plugins/eis/eisbackend.cpp @@ -0,0 +1,166 @@ +/* + SPDX-FileCopyrightText: 2024 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 +#include +#include +#include +#include + +#include + +#include + +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 &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 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(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 &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; +} +} diff --git a/src/plugins/eis/eisbackend.h b/src/plugins/eis/eisbackend.h new file mode 100644 index 0000000000..a34d40369b --- /dev/null +++ b/src/plugins/eis/eisbackend.h @@ -0,0 +1,52 @@ +/* + SPDX-FileCopyrightText: 2024 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 +#include + +#include + +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> m_contexts; +}; + +} diff --git a/src/plugins/eis/eiscontext.cpp b/src/plugins/eis/eiscontext.cpp new file mode 100644 index 0000000000..51b33670b7 --- /dev/null +++ b/src/plugins/eis/eiscontext.cpp @@ -0,0 +1,326 @@ +/* + SPDX-FileCopyrightText: 2024 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 absoluteDevice; + std::unique_ptr pointer; + std::unique_ptr keyboard; +}; + +EisContext::EisContext(KWin::EisBackend *backend, QFlags 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::system_clock::now().time_since_epoch()); +} + +void EisContext::handleEvents() +{ + auto eventDevice = [](eis_event *event) { + return static_cast(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(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(eis_client_get_user_data(client))) { + m_clients.erase(std::ranges::find(m_clients, seat, &std::unique_ptr::get)); + } + break; + } + case EIS_EVENT_SEAT_BIND: { + auto seat = eis_event_get_seat(event); + auto clientSeat = static_cast(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 &device, auto &&createFunc, bool shouldHave) { + if (shouldHave) { + if (!device) { + device = std::make_unique(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(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); + } +} +} diff --git a/src/plugins/eis/eiscontext.h b/src/plugins/eis/eiscontext.h new file mode 100644 index 0000000000..fd8f676a4f --- /dev/null +++ b/src/plugins/eis/eiscontext.h @@ -0,0 +1,47 @@ +/* + SPDX-FileCopyrightText: 2024 David Redondo + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#pragma once + +#include +#include +#include + +#include + +#include +#include + +namespace KWin +{ + +class EisBackend; +struct EisClient; + +class EisContext +{ +public: + EisContext(EisBackend *backend, QFlags 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 m_allowedCapabilities; + QSocketNotifier m_socketNotifier; + std::vector> m_clients; +}; + +} diff --git a/src/plugins/eis/eisdevice.cpp b/src/plugins/eis/eisdevice.cpp new file mode 100644 index 0000000000..3303a5da13 --- /dev/null +++ b/src/plugins/eis/eisdevice.cpp @@ -0,0 +1,125 @@ +/* + SPDX-FileCopyrightText: 2024 David Redondo + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#include "eisdevice.h" + +#include + +namespace KWin +{ + +static std::chrono::microseconds currentTime() +{ + return std::chrono::duration_cast(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; +} + +} diff --git a/src/plugins/eis/eisdevice.h b/src/plugins/eis/eisdevice.h new file mode 100644 index 0000000000..ed24f90a88 --- /dev/null +++ b/src/plugins/eis/eisdevice.h @@ -0,0 +1,56 @@ +/* + SPDX-FileCopyrightText: 2024 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 pressedButtons; + std::vector pressedKeys; + std::vector 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; +}; + +} diff --git a/src/plugins/eis/eisplugin.cpp b/src/plugins/eis/eisplugin.cpp new file mode 100644 index 0000000000..561835cd00 --- /dev/null +++ b/src/plugins/eis/eisplugin.cpp @@ -0,0 +1,15 @@ +#include "eisplugin.h" + +#include "eisbackend.h" + +#include "input.h" + +EisPlugin::EisPlugin() + : Plugin() +{ + KWin::input()->addInputBackend(std::make_unique()); +} + +EisPlugin::~EisPlugin() +{ +} diff --git a/src/plugins/eis/eisplugin.h b/src/plugins/eis/eisplugin.h new file mode 100644 index 0000000000..56d58a0234 --- /dev/null +++ b/src/plugins/eis/eisplugin.h @@ -0,0 +1,17 @@ +/* + SPDX-FileCopyrightText: 2024 David Redondo + + 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; +}; diff --git a/src/plugins/eis/main.cpp b/src/plugins/eis/main.cpp new file mode 100644 index 0000000000..3a21a5c5ef --- /dev/null +++ b/src/plugins/eis/main.cpp @@ -0,0 +1,32 @@ +/* + SPDX-FileCopyrightText: 2024 David Redondo + + 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 create() const override + { + switch (KWin::kwinApp()->operationMode()) { + case KWin::Application::OperationModeXwayland: + case KWin::Application::OperationModeWaylandOnly: + return std::make_unique(); + case KWin::Application::OperationModeX11: + default: + return nullptr; + } + } +}; + +#include "main.moc" diff --git a/src/plugins/eis/metadata.json b/src/plugins/eis/metadata.json new file mode 100644 index 0000000000..aa304f4093 --- /dev/null +++ b/src/plugins/eis/metadata.json @@ -0,0 +1,5 @@ +{ + "KPlugin": { + "EnabledByDefault": true + } +}