From 0a1e0829e0d525fb7b05c92972904ff258a4c471 Mon Sep 17 00:00:00 2001 From: Pier Luigi Fiorini Date: Tue, 8 Aug 2023 07:29:37 +0200 Subject: [PATCH] Device integration for Wayland Closes: #42 --- CMakeLists.txt | 3 + features.cmake | 23 ++ .../deviceintegration/wayland/CMakeLists.txt | 42 +++ .../wayland/eglintegration.cpp | 88 +++++ .../wayland/eglintegration.h | 44 +++ .../deviceintegration/wayland/wayland.json | 3 + .../wayland/waylandbackend.cpp | 317 ++++++++++++++++++ .../wayland/waylandbackend.h | 89 +++++ .../wayland/waylandcursor.cpp | 88 +++++ .../deviceintegration/wayland/waylandcursor.h | 49 +++ .../wayland/waylandinputmanager.cpp | 120 +++++++ .../wayland/waylandinputmanager.h | 45 +++ .../wayland/waylandintegration.cpp | 146 ++++++++ .../wayland/waylandintegration.h | 52 +++ .../wayland/waylandintegrationplugin.cpp | 23 ++ .../wayland/waylandintegrationplugin.h | 24 ++ .../wayland/waylandkeyboard.cpp | 46 +++ .../wayland/waylandkeyboard.h | 32 ++ .../wayland/waylandoutput.cpp | 159 +++++++++ .../deviceintegration/wayland/waylandoutput.h | 43 +++ .../wayland/waylandpointer.cpp | 102 ++++++ .../wayland/waylandpointer.h | 31 ++ .../wayland/waylandtouch.cpp | 24 ++ .../deviceintegration/wayland/waylandtouch.h | 30 ++ .../wayland/waylandwindow.cpp | 174 ++++++++++ .../deviceintegration/wayland/waylandwindow.h | 57 ++++ 26 files changed, 1854 insertions(+) create mode 100644 src/plugins/deviceintegration/wayland/CMakeLists.txt create mode 100644 src/plugins/deviceintegration/wayland/eglintegration.cpp create mode 100644 src/plugins/deviceintegration/wayland/eglintegration.h create mode 100644 src/plugins/deviceintegration/wayland/wayland.json create mode 100644 src/plugins/deviceintegration/wayland/waylandbackend.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandbackend.h create mode 100644 src/plugins/deviceintegration/wayland/waylandcursor.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandcursor.h create mode 100644 src/plugins/deviceintegration/wayland/waylandinputmanager.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandinputmanager.h create mode 100644 src/plugins/deviceintegration/wayland/waylandintegration.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandintegration.h create mode 100644 src/plugins/deviceintegration/wayland/waylandintegrationplugin.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandintegrationplugin.h create mode 100644 src/plugins/deviceintegration/wayland/waylandkeyboard.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandkeyboard.h create mode 100644 src/plugins/deviceintegration/wayland/waylandoutput.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandoutput.h create mode 100644 src/plugins/deviceintegration/wayland/waylandpointer.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandpointer.h create mode 100644 src/plugins/deviceintegration/wayland/waylandtouch.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandtouch.h create mode 100644 src/plugins/deviceintegration/wayland/waylandwindow.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandwindow.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e7931b29..78d28b3c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,9 @@ if(FEATURE_aurora_qpa) add_subdirectory(src/plugins/platforms/eglfs/deviceintegration/eglfs_x11) endif() endif() +if(FEATURE_aurora_deviceintegration_wayland) + add_subdirectory(src/plugins/deviceintegration/wayland) +endif() if(BUILD_TESTING) if(TARGET AuroraCompositor) add_subdirectory(tests/auto/compositor/compositor) diff --git a/features.cmake b/features.cmake index 158a9a5a6..2adba8e9f 100644 --- a/features.cmake +++ b/features.cmake @@ -471,6 +471,29 @@ if(FEATURE_aurora_vulkan_server_buffer) endif() set(LIRI_FEATURE_aurora_vulkan_server_buffer "$") +# Device Integration: wayland +option(FEATURE_aurora_deviceintegration_wayland "Device Integration: wayland" ON) +if(FEATURE_aurora_deviceintegration_wayland) + find_package(EGL QUIET) + find_package(Wayland "${WAYLAND_MIN_VERSION}" COMPONENTS Egl QUIET) + find_package(KF5Wayland QUIET) + + if(NOT TARGET EGL::EGL) + message(WARNING "You need EGL for Aurora::DeviceIntegration::Wayland") + set(FEATURE_aurora_deviceintegration_wayland OFF) + endif() + if(NOT TARGET Wayland::Egl) + message(WARNING "You need Wayland::Egl for Aurora::DeviceIntegration::Wayland") + set(FEATURE_aurora_deviceintegration_wayland OFF) + endif() + if(NOT KF5Wayland_FOUND) + message(WARNING "You need KWayland::Client for Aurora::DeviceIntegration::Wayland") + set(FEATURE_aurora_deviceintegration_wayland OFF) + endif() +endif() +add_feature_info("Aurora::DeviceIntegration::Wayland" FEATURE_aurora_deviceintegration_wayland "Build Wayland device integration") +set(LIRI_FEATURE_aurora_deviceintegration_wayland "$") + # xwayland option(FEATURE_aurora_xwayland "XWayland support" ON) if(FEATURE_aurora_xwayland) diff --git a/src/plugins/deviceintegration/wayland/CMakeLists.txt b/src/plugins/deviceintegration/wayland/CMakeLists.txt new file mode 100644 index 000000000..a362c43e5 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/CMakeLists.txt @@ -0,0 +1,42 @@ +include(ECMQtDeclareLoggingCategory) +ecm_qt_declare_logging_category( + AuroraDeviceIntegrationWayland_SOURCES + HEADER "waylandloggingcategories.h" + IDENTIFIER "Aurora::Platform::gLcWayland" + CATEGORY_NAME "aurora.platform.wayland" + DEFAULT_SEVERITY "Info" + DESCRIPTION "Aurora device integration for Wayland" +) + +liri_add_plugin(AuroraDeviceIntegrationWayland + TYPE + "aurora/deviceintegration" + OUTPUT_NAME + wayland + SOURCES + eglintegration.cpp eglintegration.h + waylandbackend.cpp waylandbackend.h + waylandcursor.cpp waylandcursor.h + waylandinputmanager.cpp waylandinputmanager.h + waylandintegrationplugin.cpp waylandintegrationplugin.h + waylandkeyboard.cpp waylandkeyboard.h + waylandintegration.cpp waylandintegration.h + waylandoutput.cpp waylandoutput.h + waylandpointer.cpp waylandpointer.h + waylandtouch.cpp waylandtouch.h + waylandwindow.cpp waylandwindow.h + wayland.json + ${AuroraDeviceIntegrationWayland_SOURCES} + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Liri::AuroraCore + Liri::AuroraPlatform + Liri::AuroraPlatformPrivate + EGL::EGL + Wayland::Egl + LIBRARIES + KF5::WaylandClient +) + +liri_finalize_plugin(AuroraDeviceIntegrationWayland) diff --git a/src/plugins/deviceintegration/wayland/eglintegration.cpp b/src/plugins/deviceintegration/wayland/eglintegration.cpp new file mode 100644 index 000000000..aefea10c7 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/eglintegration.cpp @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "eglintegration.h" +#include "waylandbackend.h" +#include "waylandloggingcategories.h" + +namespace Aurora { + +namespace Platform { + +EglIntegration::EglIntegration() +{ +} + +EglIntegration::~EglIntegration() +{ + destroy(); +} + +bool EglIntegration::isInitialized() const +{ + return m_initialized; +} + +EGLDisplay EglIntegration::display() const +{ + return m_eglDisplay; +} + +typedef const char *(*EGLGETERRORSTRINGPROC)(EGLint error); + +bool EglIntegration::initialize() +{ + if (m_initialized) + return true; + + m_initialized = true; + + if (hasEglExtension("EGL_EXT_platform_base")) { + if (hasEglExtension("EGL_KHR_platform_wayland") + || hasEglExtension("EGL_EXT_platform_wayland") + || hasEglExtension("EGL_MESA_platform_wayland")) { + static PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplay = nullptr; + if (!eglGetPlatformDisplay) + eglGetPlatformDisplay = reinterpret_cast( + eglGetProcAddress("eglGetPlatformDisplayEXT")); + + m_eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, + WaylandBackend::instance()->display(), nullptr); + } else { + qCWarning(gLcWayland) << "The EGL implementation does not support the Wayland platform"; + return false; + } + } else { + QByteArray eglPlatform = qgetenv("EGL_PLATFORM"); + if (eglPlatform.isEmpty()) + setenv("EGL_PLATFORM", "wayland", true); + + m_eglDisplay = eglGetDisplay( + reinterpret_cast(WaylandBackend::instance()->display())); + } + + return true; +} + +void EglIntegration::destroy() +{ + if (m_eglDisplay != EGL_NO_DISPLAY) { + eglTerminate(m_eglDisplay); + m_eglDisplay = EGL_NO_DISPLAY; + } +} + +bool EglIntegration::hasEglExtension(const char *name, EGLDisplay display) +{ + QList extensions = + QByteArray(reinterpret_cast(eglQueryString(display, EGL_EXTENSIONS))) + .split(' '); + return extensions.contains(name); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/eglintegration.h b/src/plugins/deviceintegration/wayland/eglintegration.h new file mode 100644 index 000000000..63bd0accd --- /dev/null +++ b/src/plugins/deviceintegration/wayland/eglintegration.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#ifndef EGL_NO_X11 +# define EGL_NO_X11 +#endif +#ifndef MESA_EGL_NO_X11_HEADERS +# define MESA_EGL_NO_X11_HEADERS +#endif + +#include +#include + +namespace Aurora { + +namespace Platform { + +class EglIntegration +{ +public: + EglIntegration(); + ~EglIntegration(); + + bool isInitialized() const; + + EGLDisplay display() const; + + bool initialize(); + void destroy(); + + static bool hasEglExtension(const char *name, EGLDisplay display = EGL_NO_DISPLAY); + +private: + bool m_initialized = false; + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/wayland.json b/src/plugins/deviceintegration/wayland/wayland.json new file mode 100644 index 000000000..8e56c4fd8 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/wayland.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "wayland" ] +} diff --git a/src/plugins/deviceintegration/wayland/waylandbackend.cpp b/src/plugins/deviceintegration/wayland/waylandbackend.cpp new file mode 100644 index 000000000..fffd1bf58 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandbackend.cpp @@ -0,0 +1,317 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "waylandbackend.h" +#include "waylandloggingcategories.h" +#include "waylandoutput.h" +#include "waylandwindow.h" + +using namespace KWayland::Client; + +namespace Aurora { + +namespace Platform { + +Q_GLOBAL_STATIC(WaylandBackend, gWaylandBackend) + +WaylandBackend::WaylandBackend(QObject *parent) + : QObject(parent) + , m_connectionThread(new QThread()) + , m_connectionThreadObject(new KWayland::Client::ConnectionThread()) + , m_eventQueue(new EventQueue(this)) + , m_registry(new Registry(this)) +{ + m_connectionThreadObject->moveToThread(m_connectionThread); +} + +WaylandBackend::~WaylandBackend() +{ + if (m_connectionThreadObject) { + m_connectionThreadObject->flush(); + m_connectionThreadObject->deleteLater(); + m_connectionThreadObject = nullptr; + } + + if (m_connectionThread) { + m_connectionThread->quit(); + m_connectionThread->deleteLater(); + m_connectionThread = nullptr; + } +} + +wl_display *WaylandBackend::display() const +{ + if (m_connectionThreadObject) + return m_connectionThreadObject->display(); + return nullptr; +} + +void WaylandBackend::initialize() +{ + // Compositor + connect(m_registry, &Registry::compositorAnnounced, this, + [this](quint32 name, quint32 version) { + if (version < 4) { + qCCritical(gLcWayland, "wl_compositor version 4 or later is required"); + qFatal("Aborting..."); + } + m_compositor = m_registry->createCompositor(name, version); + }); + + // Compositor for sub-surfaces + connect(m_registry, &Registry::subCompositorAnnounced, this, + [this](quint32 name, quint32 version) { + m_subCompositor = m_registry->createSubCompositor(name, version); + }); + + // Shared memory pool + connect(m_registry, &Registry::shmAnnounced, this, [this](quint32 name, quint32 version) { + m_shmPool = m_registry->createShmPool(name, version); + }); + + // Seat + connect(m_registry, &Registry::seatAnnounced, this, [this](quint32 name, quint32 version) { + m_seat = m_registry->createSeat(name, version); + }); + + // Output + connect(m_registry, &Registry::outputAnnounced, this, [this](quint32 name, quint32 version) { + m_hostOutputsCount++; + + auto *hostOutput = m_registry->createOutput(name, version); + + connect(hostOutput, &KWayland::Client::Output::changed, this, + &WaylandBackend::handleOutputChanged); + }); + + // XDG shell + connect(m_registry, &Registry::xdgShellStableAnnounced, this, + [this](quint32 name, quint32 version) { + m_xdgShell = m_registry->createXdgShell(name, version); + }); + + // XDG decoration + connect(m_registry, &Registry::xdgDecorationAnnounced, this, + [this](quint32 name, quint32 version) { + m_xdgDecorationManager = m_registry->createXdgDecorationManager(name, version); + }); + + // Verify we have what we need + connect(m_registry, &Registry::interfacesAnnounced, this, [this]() { + if (!m_xdgShell || !m_xdgShell->isValid()) + qCritical("The xdg_wm_base interface was not found, cannot continue!"); + }); + + // Connection + connect( + m_connectionThreadObject, &ConnectionThread::connected, this, + [this]() { + qCDebug(gLcWayland) << "Connected to:" << m_connectionThreadObject->socketName(); + + // Create the event queue + m_eventQueue->setup(m_connectionThreadObject); + m_registry->setEventQueue(m_eventQueue); + + // Registry + m_registry->create(m_connectionThreadObject); + m_registry->setup(); + m_connectionThreadObject->flush(); + }, + Qt::QueuedConnection); + connect( + m_connectionThreadObject, &ConnectionThread::connectionDied, this, + [this]() { + emit statusChanged(DeviceIntegration::Status::Failed); + + destroyOutputs(); + + if (m_seat) + m_seat->destroy(); + + if (m_shmPool) + m_shmPool->destroy(); + + if (m_xdgDecorationManager) + m_xdgDecorationManager->destroy(); + if (m_xdgShell) + m_xdgShell->destroy(); + + if (m_subCompositor) + m_subCompositor->destroy(); + if (m_compositor) + m_compositor->destroy(); + if (m_registry) + m_registry->destroy(); + if (m_eventQueue) + m_eventQueue->destroy(); + }, + Qt::QueuedConnection); + connect(m_connectionThreadObject, &ConnectionThread::failed, this, [this]() { + qCDebug(gLcWayland) << "Failed to connect to:" << m_connectionThreadObject->socketName(); + + emit statusChanged(DeviceIntegration::Status::Failed); + + destroyOutputs(); + }); + + // Connect + m_connectionThread->start(); + m_connectionThreadObject->initConnection(); +} + +void WaylandBackend::destroy() +{ + destroyOutputs(); + + if (m_xdgDecorationManager) { + m_xdgDecorationManager->release(); + m_xdgDecorationManager->destroy(); + } + if (m_xdgShell) { + m_xdgShell->release(); + m_xdgShell->destroy(); + } + + if (m_subCompositor) { + m_subCompositor->release(); + m_subCompositor->destroy(); + } + if (m_seat) { + m_seat->release(); + m_seat->destroy(); + } + if (m_shmPool) { + m_shmPool->release(); + m_shmPool->destroy(); + } + if (m_compositor) { + m_compositor->release(); + m_compositor->destroy(); + } + if (m_registry) { + m_registry->release(); + m_registry->destroy(); + } + + if (m_connectionThread) { + m_connectionThread->quit(); + m_connectionThread->wait(); + } + + if (m_eventQueue) + m_eventQueue->release(); +} + +void WaylandBackend::flush() +{ + if (m_connectionThreadObject) + m_connectionThreadObject->flush(); +} + +WaylandBackend *WaylandBackend::instance() +{ + return gWaylandBackend(); +} + +KWayland::Client::Compositor *WaylandBackend::compositor() const +{ + return m_compositor; +} + +KWayland::Client::SubCompositor *WaylandBackend::subCompositor() const +{ + return m_subCompositor; +} + +KWayland::Client::ShmPool *WaylandBackend::shmPool() const +{ + return m_shmPool; +} + +KWayland::Client::Seat *WaylandBackend::seat() const +{ + return m_seat; +} + +KWayland::Client::XdgShell *WaylandBackend::xdgShell() const +{ + return m_xdgShell; +} + +KWayland::Client::XdgDecorationManager *WaylandBackend::xdgDecorationManager() const +{ + return m_xdgDecorationManager; +} + +Outputs WaylandBackend::outputs() const +{ + return m_outputs; +} + +WaylandWindow *WaylandBackend::findWindow(KWayland::Client::Surface *surface) const +{ + for (auto *wlWindow : qAsConst(m_windows)) { + if (wlWindow->surface() == surface) + return wlWindow; + } + + return nullptr; +} + +WaylandOutput *WaylandBackend::findOutput(KWayland::Client::Surface *surface) const +{ + if (auto *wlWindow = findWindow(surface)) + return static_cast(wlWindow->output()); + + return nullptr; +} + +QList WaylandBackend::windows() const +{ + return m_windows; +} + +void WaylandBackend::registerWindow(WaylandWindow *window) +{ + m_windows.append(window); +} + +void WaylandBackend::unregisterWindow(WaylandWindow *window) +{ + m_windows.removeOne(window); +} + +void WaylandBackend::handleOutputChanged() +{ + auto *hostOutput = static_cast(sender()); + if (!hostOutput) + return; + + const auto index = m_outputs.size() + 1; + auto *output = new WaylandOutput(hostOutput, QStringLiteral("WL-%1").arg(index)); + m_outputs.append(output); + emit outputAdded(output); + + if (m_outputs.size() == m_hostOutputsCount) + emit statusChanged(DeviceIntegration::Status::Ready); +} + +void WaylandBackend::destroyOutputs() +{ + auto it = m_outputs.begin(); + while (it != m_outputs.end()) { + auto *output = static_cast(*it); + emit outputRemoved(output); + output->destroy(); + output->deleteLater(); + it = m_outputs.erase(it); + } +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandbackend.h b/src/plugins/deviceintegration/wayland/waylandbackend.h new file mode 100644 index 000000000..63a5a7950 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandbackend.h @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +class WaylandOutput; +class WaylandWindow; + +class WaylandBackend : public QObject +{ + Q_OBJECT +public: + explicit WaylandBackend(QObject *parent = nullptr); + ~WaylandBackend(); + + wl_display *display() const; + + void initialize(); + void destroy(); + + void flush(); + + KWayland::Client::Compositor *compositor() const; + KWayland::Client::SubCompositor *subCompositor() const; + KWayland::Client::ShmPool *shmPool() const; + KWayland::Client::Seat *seat() const; + KWayland::Client::XdgShell *xdgShell() const; + KWayland::Client::XdgDecorationManager *xdgDecorationManager() const; + + Outputs outputs() const; + + WaylandWindow *findWindow(KWayland::Client::Surface *surface) const; + WaylandOutput *findOutput(KWayland::Client::Surface *surface) const; + + QList windows() const; + + void registerWindow(WaylandWindow *window); + void unregisterWindow(WaylandWindow *window); + + static WaylandBackend *instance(); + +signals: + void statusChanged(DeviceIntegration::Status status); + void outputAdded(WaylandOutput *output); + void outputRemoved(WaylandOutput *output); + +private: + QThread *m_connectionThread = nullptr; + KWayland::Client::ConnectionThread *m_connectionThreadObject = nullptr; + KWayland::Client::EventQueue *m_eventQueue = nullptr; + KWayland::Client::Registry *m_registry = nullptr; + KWayland::Client::Compositor *m_compositor = nullptr; + KWayland::Client::SubCompositor *m_subCompositor = nullptr; + KWayland::Client::ShmPool *m_shmPool = nullptr; + KWayland::Client::Seat *m_seat = nullptr; + KWayland::Client::XdgShell *m_xdgShell = nullptr; + KWayland::Client::XdgDecorationManager *m_xdgDecorationManager = nullptr; + + int m_hostOutputsCount = 0; + Outputs m_outputs; + + QList m_windows; + +private slots: + void handleOutputChanged(); + void destroyOutputs(); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandcursor.cpp b/src/plugins/deviceintegration/wayland/waylandcursor.cpp new file mode 100644 index 000000000..e9c2d0682 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandcursor.cpp @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include + +#include "waylandbackend.h" +#include "waylandcursor.h" +#include "waylandwindow.h" + +namespace Aurora { + +namespace Platform { + +WaylandCursor::WaylandCursor(WaylandWindow *window, QObject *parent) + : QObject(parent) + , m_window(window) + , m_surface(WaylandBackend::instance()->compositor()->createSurface(this)) +{ +} + +WaylandCursor::~WaylandCursor() +{ + if (m_surface) { + m_surface->release(); + m_surface->destroy(); + m_surface = nullptr; + } +} + +bool WaylandCursor::isEnabled() const +{ + return m_enabled; +} + +void WaylandCursor::setEnabled(bool enabled) +{ + if (m_enabled == enabled) + return; + + m_enabled = enabled; + render(); +} + +KWayland::Client::Pointer *WaylandCursor::pointer() const +{ + return m_pointer; +} + +void WaylandCursor::setPointer(KWayland::Client::Pointer *pointer) +{ + if (m_pointer == pointer) + return; + + m_pointer = pointer; + if (m_pointer) + m_pointer->setCursor(m_surface, m_hotSpot); +} + +void WaylandCursor::update(const QImage &image, const QPoint &hotSpot, qreal scale) +{ + if (m_image != image || m_scale != scale || m_hotSpot != hotSpot) { + m_image = image; + m_buffer = WaylandBackend::instance()->shmPool()->createBuffer(m_image); + m_scale = scale; + m_hotSpot = hotSpot; + render(); + } +} + +void WaylandCursor::render() +{ + if (m_enabled) { + m_surface->attachBuffer(m_buffer); + m_surface->setScale(qCeil(m_scale)); + m_surface->damageBuffer(QRect(0, 0, INT32_MAX, INT32_MAX)); + } else { + m_surface->attachBuffer(KWayland::Client::Buffer::Ptr()); + } + m_surface->commit(KWayland::Client::Surface::CommitFlag::None); + + m_window->present(); +} + +} // namespace Platform + +} // namespace Aurora \ No newline at end of file diff --git a/src/plugins/deviceintegration/wayland/waylandcursor.h b/src/plugins/deviceintegration/wayland/waylandcursor.h new file mode 100644 index 000000000..310d19da7 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandcursor.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace Aurora { + +namespace Platform { + +class WaylandWindow; + +class WaylandCursor : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) +public: + explicit WaylandCursor(WaylandWindow *window, QObject *parent = nullptr); + ~WaylandCursor(); + + bool isEnabled() const; + void setEnabled(bool enabled); + + KWayland::Client::Pointer *pointer() const; + void setPointer(KWayland::Client::Pointer *pointer); + + void update(const QImage &image, const QPoint &hotSpot, qreal scale); + +signals: + void enabledChanged(bool enabled); + +private: + bool m_enabled = false; + QPointer m_window; + KWayland::Client::Surface *m_surface = nullptr; + KWayland::Client::Pointer *m_pointer = nullptr; + QImage m_image; + KWayland::Client::Buffer::Ptr m_buffer; + QPoint m_hotSpot; + qreal m_scale = 1; + + void render(); +}; + +} // namespace Platform + +} // namespace Aurora \ No newline at end of file diff --git a/src/plugins/deviceintegration/wayland/waylandinputmanager.cpp b/src/plugins/deviceintegration/wayland/waylandinputmanager.cpp new file mode 100644 index 000000000..c95b293e0 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandinputmanager.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "waylandbackend.h" +#include "waylandinputmanager.h" +#include "waylandkeyboard.h" +#include "waylandpointer.h" +#include "waylandtouch.h" + +namespace Aurora { + +namespace Platform { + +WaylandInputManager::WaylandInputManager(QObject *parent) + : InputManager(parent) +{ + auto *seat = WaylandBackend::instance()->seat(); + + handleHasKeyboardChanged(seat->hasKeyboard()); + handleHasPointerChanged(seat->hasPointer()); + handleHasTouchChanged(seat->hasTouch()); + + connect(seat, &KWayland::Client::Seat::hasKeyboardChanged, this, + &WaylandInputManager::handleHasKeyboardChanged); + connect(seat, &KWayland::Client::Seat::hasPointerChanged, this, + &WaylandInputManager::handleHasPointerChanged); + connect(seat, &KWayland::Client::Seat::hasTouchChanged, this, + &WaylandInputManager::handleHasTouchChanged); +} + +WaylandInputManager::~WaylandInputManager() +{ +} + +QList WaylandInputManager::keyboardDevices() const +{ + return QList() << m_keyboard; +} + +QList WaylandInputManager::pointerDevices() const +{ + return QList() << m_pointer; +} + +QList WaylandInputManager::touchDevices() const +{ + return QList() << m_touch; +} + +int WaylandInputManager::deviceCount(InputDevice::DeviceType deviceType) const +{ + switch (deviceType) { + case InputDevice::DeviceType::Keyboard: + return m_keyboard ? 1 : 0; + case InputDevice::DeviceType::Pointer: + return m_pointer ? 1 : 0; + case InputDevice::DeviceType::Touch: + return m_touch ? 1 : 0; + default: + return 0; + } +} + +void WaylandInputManager::handleHasKeyboardChanged(bool hasKeyboard) +{ + auto *seat = WaylandBackend::instance()->seat(); + + if (hasKeyboard) { + m_keyboard = new WaylandKeyboard(seat, this); + emit deviceAdded(m_keyboard); + emit keyboardAdded(m_keyboard); + } else { + if (m_keyboard) { + emit deviceRemoved(m_keyboard); + emit keyboardRemoved(m_keyboard); + m_keyboard->deleteLater(); + m_keyboard = nullptr; + } + } +} + +void WaylandInputManager::handleHasPointerChanged(bool hasPointer) +{ + auto *seat = WaylandBackend::instance()->seat(); + + if (hasPointer) { + m_pointer = new WaylandPointer(seat, this); + emit deviceAdded(m_pointer); + emit pointerAdded(m_pointer); + } else { + if (m_pointer) { + emit deviceRemoved(m_pointer); + emit pointerRemoved(m_pointer); + m_pointer->deleteLater(); + m_pointer = nullptr; + } + } +} + +void WaylandInputManager::handleHasTouchChanged(bool hasTouch) +{ + auto *seat = WaylandBackend::instance()->seat(); + + if (hasTouch) { + m_touch = new WaylandTouch(seat, this); + emit deviceAdded(m_touch); + emit touchAdded(m_touch); + } else { + if (m_pointer) { + emit deviceRemoved(m_touch); + emit touchRemoved(m_touch); + m_touch->deleteLater(); + m_touch = nullptr; + } + } +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandinputmanager.h b/src/plugins/deviceintegration/wayland/waylandinputmanager.h new file mode 100644 index 000000000..502209a88 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandinputmanager.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +class WaylandKeyboard; +class WaylandPointer; +class WaylandTouch; + +typedef QMap InputDevicesMap; + +class WaylandInputManager : public InputManager +{ + Q_OBJECT +public: + WaylandInputManager(QObject *parent = nullptr); + ~WaylandInputManager(); + + QList keyboardDevices() const override; + QList pointerDevices() const override; + QList touchDevices() const override; + + int deviceCount(InputDevice::DeviceType deviceType) const override; + +private: + WaylandKeyboard *m_keyboard = nullptr; + WaylandPointer *m_pointer = nullptr; + WaylandTouch *m_touch = nullptr; + +private slots: + void handleHasKeyboardChanged(bool hasKeyboard); + void handleHasPointerChanged(bool hasPointer); + void handleHasTouchChanged(bool hasTouch); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegration.cpp b/src/plugins/deviceintegration/wayland/waylandintegration.cpp new file mode 100644 index 000000000..a9db73fbd --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegration.cpp @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "eglintegration.h" +#include "waylandbackend.h" +#include "waylandinputmanager.h" +#include "waylandintegration.h" +#include "waylandloggingcategories.h" +#include "waylandoutput.h" +#include "waylandwindow.h" + +#include + +namespace Aurora { + +namespace Platform { + +WaylandIntegration::WaylandIntegration(QObject *parent) + : DeviceIntegration(parent) + , m_eglIntegration(new EglIntegration()) +{ +} + +WaylandIntegration::~WaylandIntegration() +{ +} + +void WaylandIntegration::initialize() +{ + connect(WaylandBackend::instance(), &WaylandBackend::statusChanged, this, + [this](DeviceIntegration::Status status) { DeviceIntegration::setStatus(status); }); + connect(WaylandBackend::instance(), &WaylandBackend::outputAdded, this, + &WaylandIntegration::outputAdded); + connect(WaylandBackend::instance(), &WaylandBackend::outputRemoved, this, + &WaylandIntegration::outputRemoved); + + WaylandBackend::instance()->initialize(); + + if (m_eglIntegration) + m_eglIntegration->initialize(); +} + +void WaylandIntegration::destroy() +{ + qCDebug(gLcWayland, "Wayland device integration is about to be destroyed..."); + + WaylandBackend::instance()->destroy(); + + if (m_eglIntegration) + m_eglIntegration->destroy(); + + qCInfo(gLcWayland, "Wayland device integration destroyed successfully"); +} + +EGLNativeDisplayType WaylandIntegration::platformDisplay() const +{ + return reinterpret_cast(display()); +} + +EGLDisplay WaylandIntegration::eglDisplay() const +{ + if (m_eglIntegration && m_eglIntegration->isInitialized()) + return m_eglIntegration->display(); + return EGL_NO_DISPLAY; +} + +wl_display *WaylandIntegration::display() const +{ + return WaylandBackend::instance()->display(); +} + +EGLNativeWindowType WaylandIntegration::createNativeWindow(Window *window, const QSize &size, + const QSurfaceFormat &format) +{ + Q_UNUSED(format) + + auto *wlWindow = static_cast(window); + if (!wlWindow) { + qCWarning(gLcWayland) << "Window" << window << "cannot be cast to WaylandWindow"; + return 0; + } + + auto *surface = wlWindow->surface()->operator wl_surface *(); + if (surface) { + qCDebug(gLcWayland, "Creating native window with size %dx%d...", size.width(), + size.height()); + return reinterpret_cast( + wl_egl_window_create(surface, size.width(), size.height())); + } else { + qCWarning(gLcWayland) << "Unable to get Wayland surface from window" << window; + return 0; + } + + return 0; +} + +void WaylandIntegration::destroyNativeWindow(EGLNativeWindowType nativeWindow) +{ + if (nativeWindow) { + auto *eglWindow = reinterpret_cast(nativeWindow); + wl_egl_window_destroy(eglWindow); + } +} + +void WaylandIntegration::waitForVSync(Window *window) const +{ +} + +void WaylandIntegration::presentBuffer(Window *window) +{ + auto *waylandWindow = static_cast(window); + if (waylandWindow) + waylandWindow->present(); +} + +Window *WaylandIntegration::createWindow(Output *output, QWindow *qtWindow) +{ + return new WaylandWindow(output, qtWindow); +} + +Window *WaylandIntegration::getWindow(QWindow *qtWindow) const +{ + const auto windows = WaylandBackend::instance()->windows(); + for (auto *window : windows) { + if (window->qtWindow() == qtWindow) + return static_cast(window); + } + + return nullptr; +} + +InputManager *WaylandIntegration::createInputManager() +{ + return new WaylandInputManager(this); +} + +Outputs WaylandIntegration::outputs() const +{ + return WaylandBackend::instance()->outputs(); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegration.h b/src/plugins/deviceintegration/wayland/waylandintegration.h new file mode 100644 index 000000000..682671d46 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegration.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +struct wl_display; + +namespace Aurora { + +namespace Platform { + +class EglIntegration; +class WaylandWindow; + +class WaylandIntegration : public DeviceIntegration +{ + Q_OBJECT +public: + explicit WaylandIntegration(QObject *parent = nullptr); + ~WaylandIntegration(); + + void initialize() override; + void destroy() override; + + EGLNativeDisplayType platformDisplay() const override; + EGLDisplay eglDisplay() const override; + + wl_display *display() const; + + EGLNativeWindowType createNativeWindow(Window *window, const QSize &size, + const QSurfaceFormat &format) override; + void destroyNativeWindow(EGLNativeWindowType nativeWindow) override; + + void waitForVSync(Window *window) const override; + void presentBuffer(Window *window) override; + + Window *createWindow(Output *output, QWindow *qtWindow) override; + Window *getWindow(QWindow *qtWindow) const override; + + InputManager *createInputManager() override; + + Outputs outputs() const override; + +private: + QScopedPointer m_eglIntegration; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegrationplugin.cpp b/src/plugins/deviceintegration/wayland/waylandintegrationplugin.cpp new file mode 100644 index 000000000..1b4aae281 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegrationplugin.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "waylandintegration.h" +#include "waylandintegrationplugin.h" + +namespace Aurora { + +namespace Platform { + +WaylandIntegrationPlugin::WaylandIntegrationPlugin(QObject *parent) + : DeviceIntegrationPlugin(parent) +{ +} + +DeviceIntegration *WaylandIntegrationPlugin::create() +{ + return new WaylandIntegration(this); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegrationplugin.h b/src/plugins/deviceintegration/wayland/waylandintegrationplugin.h new file mode 100644 index 000000000..330cafde5 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegrationplugin.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class WaylandIntegrationPlugin : public DeviceIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.liri.Aurora.DeviceIntegrationPlugin/1" FILE "wayland.json") +public: + explicit WaylandIntegrationPlugin(QObject *parent = nullptr); + + DeviceIntegration *create() override; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandkeyboard.cpp b/src/plugins/deviceintegration/wayland/waylandkeyboard.cpp new file mode 100644 index 000000000..71ec13bbb --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandkeyboard.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "waylandkeyboard.h" + +namespace Aurora { + +namespace Platform { + +WaylandKeyboard::WaylandKeyboard(KWayland::Client::Seat *hostSeat, QObject *parent) + : KeyboardDevice(parent) + , m_hostSeat(hostSeat) + , m_hostKeyboard(hostSeat->createKeyboard(this)) +{ + connect(m_hostKeyboard, &KWayland::Client::Keyboard::keyChanged, this, + [this](quint32 key, KWayland::Client::Keyboard::KeyState state, quint32) { + if (state == KWayland::Client::Keyboard::KeyState::Pressed) + emit keyPressed(key); + else if (state == KWayland::Client::Keyboard::KeyState::Released) + emit keyReleased(key); + }); +} + +QString WaylandKeyboard::seatName() const +{ + return m_hostSeat->name(); +} + +bool WaylandKeyboard::isKeyRepeatEnabled() const +{ + return m_hostKeyboard->isKeyRepeatEnabled(); +} + +qint32 WaylandKeyboard::keyRepeatRate() const +{ + return m_hostKeyboard->keyRepeatRate(); +} + +qint32 WaylandKeyboard::keyRepeatDelay() const +{ + return m_hostKeyboard->keyRepeatDelay(); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandkeyboard.h b/src/plugins/deviceintegration/wayland/waylandkeyboard.h new file mode 100644 index 000000000..c7f7a0c5a --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandkeyboard.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include +#include + +namespace Aurora { + +namespace Platform { + +class WaylandKeyboard : public KeyboardDevice +{ + Q_OBJECT +public: + explicit WaylandKeyboard(KWayland::Client::Seat *hostSeat, QObject *parent = nullptr); + + QString seatName() const override; + + bool isKeyRepeatEnabled() const override; + qint32 keyRepeatRate() const override; + qint32 keyRepeatDelay() const override; + +private: + KWayland::Client::Seat *const m_hostSeat = nullptr; + KWayland::Client::Keyboard *m_hostKeyboard = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandoutput.cpp b/src/plugins/deviceintegration/wayland/waylandoutput.cpp new file mode 100644 index 000000000..c90b69f45 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandoutput.cpp @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandbackend.h" +#include "waylandoutput.h" + +namespace Aurora { + +namespace Platform { + +static Output::Subpixel convertSubpixel(KWayland::Client::Output::SubPixel subpixel) +{ + switch (subpixel) { + case KWayland::Client::Output::SubPixel::None: + return Output::Subpixel::None; + case KWayland::Client::Output::SubPixel::HorizontalRGB: + return Output::Subpixel::HorizontalRGB; + case KWayland::Client::Output::SubPixel::HorizontalBGR: + return Output::Subpixel::HorizontalBGR; + case KWayland::Client::Output::SubPixel::VerticalRGB: + return Output::Subpixel::VerticalRGB; + case KWayland::Client::Output::SubPixel::VerticalBGR: + return Output::Subpixel::VerticalBGR; + default: + return Output::Subpixel::Unknown; + } +} + +static Output::Transform convertTransform(KWayland::Client::Output::Transform transform) +{ + switch (transform) { + case KWayland::Client::Output::Transform::Rotated90: + return Output::Transform::Rotated90; + case KWayland::Client::Output::Transform::Rotated180: + return Output::Transform::Rotated180; + case KWayland::Client::Output::Transform::Rotated270: + return Output::Transform::Rotated270; + case KWayland::Client::Output::Transform::Flipped: + return Output::Transform::Flipped; + case KWayland::Client::Output::Transform::Flipped90: + return Output::Transform::Flipped90; + case KWayland::Client::Output::Transform::Flipped180: + return Output::Transform::Flipped180; + case KWayland::Client::Output::Transform::Flipped270: + return Output::Transform::Flipped270; + default: + return Output::Transform::Normal; + } +} + +static Output::Mode convertMode(const KWayland::Client::Output::Mode &mode) +{ + Output::Mode m; + + if (mode.flags.testFlag(KWayland::Client::Output::Mode::Flag::Preferred)) + m.flags.setFlag(Output::Mode::Flag::Preferred); + if (mode.flags.testFlag(KWayland::Client::Output::Mode::Flag::Current)) + m.flags.setFlag(Output::Mode::Flag::Current); + + m.size = mode.size; + m.refreshRate = mode.refreshRate; + + return m; +} + +WaylandOutput::WaylandOutput(KWayland::Client::Output *output, const QString &name, QObject *parent) + : Output(parent) + , m_name(name) + , m_output(output) +{ + updateInfo(); + + connect(output, &KWayland::Client::Output::changed, this, &WaylandOutput::updateInfo); + connect(output, &KWayland::Client::Output::modeChanged, this, + &WaylandOutput::handleModeChanged); +} + +WaylandOutput::~WaylandOutput() +{ + destroy(); +} + +QString WaylandOutput::name() const +{ + return m_name; +} + +QString WaylandOutput::description() const +{ + return m_output ? m_output->description() : QString(); +} + +void WaylandOutput::destroy() +{ + if (m_output) { + m_output->destroy(); + m_output->deleteLater(); + } +} + +void WaylandOutput::updateInfo() +{ + auto *d = OutputPrivate::get(this); + + d->setManufacturer(m_output->manufacturer()); + d->setModel(m_output->model()); + d->setPhysicalSize(m_output->physicalSize()); + d->setSubpixel(convertSubpixel(m_output->subPixel())); + d->setTransform(convertTransform(m_output->transform())); + d->setGlobalPosition(m_output->globalPosition()); + d->setScale(m_output->scale()); + + if (d->modes.length() == 0) { + const auto modes = m_output->modes(); + for (const auto &mode : modes) { + handleModeAdded(mode); + if (mode.flags.testFlag(KWayland::Client::Output::Mode::Flag::Current)) + handleModeChanged(mode); + } + } +} + +void WaylandOutput::handleModeAdded(const KWayland::Client::Output::Mode &mode) +{ + auto m = convertMode(mode); + + auto *d = OutputPrivate::get(this); + d->modes.append(m); + + emit modeAdded(m); +} + +void WaylandOutput::handleModeChanged(const KWayland::Client::Output::Mode &newMode) +{ + auto *d = OutputPrivate::get(this); + + QList::iterator it; + for (it = d->modes.begin(); it != d->modes.end(); ++it) { + const auto mode = (*it); + + if (mode.refreshRate == newMode.refreshRate && mode.size == mode.size) { + if (mode.flags.testFlag(Output::Mode::Flag::Preferred) + == newMode.flags.testFlag(KWayland::Client::Output::Mode::Flag::Preferred)) { + d->currentMode = it; + emit modeChanged(mode); + emit pixelSizeChanged(pixelSize()); + emit modeSizeChanged(modeSize()); + emit refreshRateChanged(refreshRate()); + emit geometryChanged(geometry()); + } + } + } +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandoutput.h b/src/plugins/deviceintegration/wayland/waylandoutput.h new file mode 100644 index 000000000..22fe78a64 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandoutput.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class WaylandIntegration; + +class WaylandOutput : public Output +{ + Q_OBJECT +public: + explicit WaylandOutput(KWayland::Client::Output *output, const QString &name, + QObject *parent = nullptr); + ~WaylandOutput(); + + QString name() const override; + QString description() const override; + + void destroy(); + +protected: + KWayland::Client::Output *m_output = nullptr; + +private: + QString m_name; + +private slots: + void updateInfo(); + void handleModeAdded(const KWayland::Client::Output::Mode &mode); + void handleModeChanged(const KWayland::Client::Output::Mode &mode); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandpointer.cpp b/src/plugins/deviceintegration/wayland/waylandpointer.cpp new file mode 100644 index 000000000..f6e6a73c9 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandpointer.cpp @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include + +#include "waylandbackend.h" +#include "waylandcursor.h" +#include "waylandcursor.h" +#include "waylandoutput.h" +#include "waylandpointer.h" +#include "waylandwindow.h" + +#include + +static const QHash s_buttonToQtMouseButton = { + { BTN_LEFT, Qt::LeftButton }, + { BTN_MIDDLE, Qt::MiddleButton }, + { BTN_RIGHT, Qt::RightButton }, + { BTN_SIDE, Qt::ExtraButton1 }, + { BTN_EXTRA, Qt::ExtraButton2 }, + { BTN_BACK, Qt::BackButton }, + { BTN_FORWARD, Qt::ForwardButton }, + { BTN_TASK, Qt::TaskButton }, + { 0x118, Qt::ExtraButton6 }, + { 0x119, Qt::ExtraButton7 }, + { 0x11a, Qt::ExtraButton8 }, + { 0x11b, Qt::ExtraButton9 }, + { 0x11c, Qt::ExtraButton10 }, + { 0x11d, Qt::ExtraButton11 }, + { 0x11e, Qt::ExtraButton12 }, + { 0x11f, Qt::ExtraButton13 }, +}; + +namespace Aurora { + +namespace Platform { + +WaylandPointer::WaylandPointer(KWayland::Client::Seat *hostSeat, QObject *parent) + : PointerDevice(parent) + , m_hostSeat(hostSeat) + , m_hostPointer(hostSeat->createPointer(this)) +{ + connect(m_hostPointer, &KWayland::Client::Pointer::entered, this, + [this](quint32, const QPointF &relativeToSurface) { + m_enteredSurface = m_hostPointer->enteredSurface(); + + auto *wlWindow = + WaylandBackend::instance()->findWindow(m_hostPointer->enteredSurface()); + if (wlWindow) { + wlWindow->cursor()->setPointer(m_hostPointer); + wlWindow->cursor()->setEnabled(true); + } + }); + connect(m_hostPointer, &KWayland::Client::Pointer::left, this, [this](quint32) { + if (!m_enteredSurface.isNull()) { + auto *wlWindow = WaylandBackend::instance()->findWindow(m_enteredSurface.data()); + if (wlWindow) { + wlWindow->cursor()->setPointer(nullptr); + wlWindow->cursor()->setEnabled(false); + } + + m_enteredSurface.clear(); + } + }); + connect(m_hostPointer, &KWayland::Client::Pointer::motion, this, + [this](const QPointF &relativeToSurface, quint32) { + if (!m_enteredSurface.isNull()) { + auto *wlWindow = + WaylandBackend::instance()->findWindow(m_enteredSurface.data()); + if (wlWindow) { + auto absPos = wlWindow->output()->globalPosition() + relativeToSurface; + emit motion(absPos); + } + } + }); + connect(m_hostPointer, &KWayland::Client::Pointer::buttonStateChanged, this, + [this](quint32 serial, quint32, quint32 button, + KWayland::Client::Pointer::ButtonState state) { + Qt::MouseButton mouseButton = buttonToQtMouseButton(button); + + if (state == KWayland::Client::Pointer::ButtonState::Pressed) + emit buttonPressed(mouseButton); + else if (state == KWayland::Client::Pointer::ButtonState::Released) + emit buttonReleased(mouseButton); + }); +} + +QString WaylandPointer::seatName() const +{ + return m_hostSeat->name(); +} + +Qt::MouseButton WaylandPointer::buttonToQtMouseButton(quint32 button) +{ + return s_buttonToQtMouseButton.value(button, Qt::MaxMouseButton); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandpointer.h b/src/plugins/deviceintegration/wayland/waylandpointer.h new file mode 100644 index 000000000..2f4d12b92 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandpointer.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include +#include + +namespace Aurora { + +namespace Platform { + +class WaylandPointer : public PointerDevice +{ + Q_OBJECT +public: + explicit WaylandPointer(KWayland::Client::Seat *hostSeat, QObject *parent = nullptr); + + QString seatName() const override; + +private: + KWayland::Client::Seat *const m_hostSeat = nullptr; + KWayland::Client::Pointer *m_hostPointer = nullptr; + QPointer m_enteredSurface; + + Qt::MouseButton buttonToQtMouseButton(quint32 button); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandtouch.cpp b/src/plugins/deviceintegration/wayland/waylandtouch.cpp new file mode 100644 index 000000000..3f37435f4 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandtouch.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "waylandtouch.h" + +namespace Aurora { + +namespace Platform { + +WaylandTouch::WaylandTouch(KWayland::Client::Seat *hostSeat, QObject *parent) + : TouchDevice(parent) + , m_hostSeat(hostSeat) + , m_hostTouch(hostSeat->createTouch(this)) +{ +} + +QString WaylandTouch::seatName() const +{ + return m_hostSeat->name(); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandtouch.h b/src/plugins/deviceintegration/wayland/waylandtouch.h new file mode 100644 index 000000000..e7dbf441e --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandtouch.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include + +#include +#include + +namespace Aurora { + +namespace Platform { + +class WaylandTouch : public TouchDevice +{ + Q_OBJECT +public: + explicit WaylandTouch(KWayland::Client::Seat *hostSeat, QObject *parent = nullptr); + + QString seatName() const override; + +private: + KWayland::Client::Seat *const m_hostSeat = nullptr; + KWayland::Client::Touch *m_hostTouch = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandwindow.cpp b/src/plugins/deviceintegration/wayland/waylandwindow.cpp new file mode 100644 index 000000000..7b0c5c0b2 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.cpp @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandbackend.h" +#include "waylandcursor.h" +#include "waylandloggingcategories.h" +#include "waylandoutput.h" +#include "waylandwindow.h" + +enum ResourceType { + WlSurfaceResourceType +}; + +static int resourceType(const QByteArray &name) +{ + static const QByteArray names[] = { QByteArrayLiteral("wl_surface") }; + const int numResourceTypes = sizeof(names) / sizeof(names[0]); + + for (int i = 0; i < numResourceTypes; ++i) { + if (name.toLower() == names[i]) + return i; + } + + return -1; +} + +namespace Aurora { + +namespace Platform { + +WaylandWindow::WaylandWindow(Output *output, QWindow *qtWindow, QObject *parent) + : Window(output, qtWindow, parent) + , m_output(static_cast(output)) + , m_cursor(new WaylandCursor(this, this)) + , m_shapeCursorSource(new Core::ShapeCursorSource(this)) +{ + WaylandBackend::instance()->registerWindow(this); +} + +WaylandWindow::~WaylandWindow() +{ + WaylandBackend::instance()->unregisterWindow(this); + destroy(); +} + +WaylandCursor *WaylandWindow::cursor() const +{ + return m_cursor; +} + +KWayland::Client::Surface *WaylandWindow::surface() const +{ + return m_surface; +} + +void *WaylandWindow::resource(const QByteArray &name) +{ + void *result = nullptr; + + switch (resourceType(name)) { + case WlSurfaceResourceType: + if (m_surface) + result = m_surface->operator wl_surface *(); + break; + default: + break; + } + + return result; +} + +bool WaylandWindow::create() +{ + // Do not create twice + if (m_surface || m_xdgToplevel) + return true; + + const auto geometry = qtWindow()->screen()->geometry(); + const auto size = geometry.size(); + + // Create a surface + m_surface = WaylandBackend::instance()->compositor()->createSurface(this); + m_surface->setSize(size); + m_surface->setOpaqueRegion( + WaylandBackend::instance()->compositor()->createRegion(QRegion(geometry)).get()); + m_surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Create a toplevel window + m_xdgToplevel = WaylandBackend::instance()->xdgShell()->createSurface(m_surface, this); + connect(m_xdgToplevel, &KWayland::Client::XdgShellSurface::configureRequested, this, + [this](const QSize &size, KWayland::Client::XdgShellSurface::States states, + quint32 serial) { m_xdgToplevel->ackConfigure(serial); }); + m_xdgToplevel->setTitle(QStringLiteral("Aurora Compositor")); + m_xdgToplevel->setMinSize(size); + m_xdgToplevel->setMaxSize(size); + + // We don't want the compositor to draw any decoration for us + if (WaylandBackend::instance()->xdgDecorationManager()) { + m_xdgDecoration = WaylandBackend::instance()->xdgDecorationManager()->getToplevelDecoration( + m_xdgToplevel, this); + m_xdgDecoration->setMode(KWayland::Client::XdgDecoration::Mode::ClientSide); + } + + // Load X cursor theme + connect(m_shapeCursorSource, &Core::CursorSource::changed, this, [this] { + m_cursor->update(m_shapeCursorSource->image(), m_shapeCursorSource->hotSpot().toPoint(), + m_output->scale()); + }); + m_shapeCursorSource->loadTheme(QStringLiteral("default"), 32, m_output->scale()); + + // Set default cursor + QCursor cursor(Qt::ArrowCursor); + changeCursor(&cursor); + + return true; +} + +void WaylandWindow::destroy() +{ + if (m_destroyed) + return; + + m_destroyed = true; + + qCDebug(gLcWayland) << "Window for" << qtWindow() << "is about to be destroyed..."; + + if (m_xdgDecoration) { + m_xdgDecoration->release(); + m_xdgDecoration->destroy(); + m_xdgDecoration = nullptr; + } + + if (m_xdgToplevel) { + m_xdgToplevel->release(); + m_xdgToplevel->destroy(); + m_xdgToplevel = nullptr; + } + + if (m_surface) { + m_surface->release(); + m_surface->destroy(); + m_surface = nullptr; + } +} + +void WaylandWindow::changeCursor(QCursor *cursor) +{ + const Qt::CursorShape newShape = cursor ? cursor->shape() : Qt::ArrowCursor; + if (m_cursorShape == newShape && newShape != Qt::BitmapCursor) + return; + + if (m_cursorShape == Qt::BitmapCursor) + m_cursorImage = QImage(); + + m_cursorShape = newShape; + if (newShape == Qt::BitmapCursor) { + // Use the custom cursor image + m_cursor->update(cursor->pixmap().toImage(), cursor->hotSpot(), m_output->scale()); + } else { + // Load the image from the cursor theme + m_shapeCursorSource->setShape(newShape); + } +} + +void WaylandWindow::present() +{ + m_surface->commit(KWayland::Client::Surface::CommitFlag::None); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandwindow.h b/src/plugins/deviceintegration/wayland/waylandwindow.h new file mode 100644 index 000000000..0b7a005a9 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include +#include +#include +#include + +struct wl_egl_window; + +namespace Aurora { + +namespace Platform { + +class WaylandCursor; + +class WaylandWindow : public Window +{ + Q_OBJECT +public: + explicit WaylandWindow(Output *output, QWindow *window, QObject *parent = nullptr); + ~WaylandWindow(); + + WaylandCursor *cursor() const; + + KWayland::Client::Surface *surface() const; + + void *resource(const QByteArray &name) override; + + bool create() override; + void destroy() override; + + void changeCursor(QCursor *cursor) override; + + void present(); + +private: + bool m_destroyed = false; + WaylandOutput *m_output = nullptr; + WaylandCursor *m_cursor = nullptr; + KWayland::Client::Surface *m_surface = nullptr; + KWayland::Client::XdgShellSurface *m_xdgToplevel = nullptr; + KWayland::Client::XdgDecoration *m_xdgDecoration = nullptr; + + Core::ShapeCursorSource *m_shapeCursorSource = nullptr; + Qt::CursorShape m_cursorShape = Qt::BlankCursor; + QImage m_cursorImage; +}; + +} // namespace Platform + +} // namespace Aurora