diff --git a/CMakeLists.txt b/CMakeLists.txt index c776f193..5cac4d80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,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 158a9a5a..2adba8e9 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 00000000..a88269cb --- /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 + mousebuttons.cpp mousebuttons.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::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 00000000..aefea10c --- /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 00000000..63bd0acc --- /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/mousebuttons.cpp b/src/plugins/deviceintegration/wayland/mousebuttons.cpp new file mode 100644 index 00000000..5e9298b8 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/mousebuttons.cpp @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2022 David Redondo +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include + +#include "mousebuttons.h" + +#include + +namespace Aurora { + +namespace Platform { + +static const QHash s_buttonToQtMouseButton = { + { BTN_LEFT, Qt::LeftButton }, + { BTN_MIDDLE, Qt::MiddleButton }, + { BTN_RIGHT, Qt::RightButton }, + // in QtWayland mapped like that + { BTN_SIDE, Qt::ExtraButton1 }, + // in QtWayland mapped like that + { BTN_EXTRA, Qt::ExtraButton2 }, + { BTN_BACK, Qt::BackButton }, + { BTN_FORWARD, Qt::ForwardButton }, + { BTN_TASK, Qt::TaskButton }, + // mapped like that in QtWayland + { 0x118, Qt::ExtraButton6 }, + { 0x119, Qt::ExtraButton7 }, + { 0x11a, Qt::ExtraButton8 }, + { 0x11b, Qt::ExtraButton9 }, + { 0x11c, Qt::ExtraButton10 }, + { 0x11d, Qt::ExtraButton11 }, + { 0x11e, Qt::ExtraButton12 }, + { 0x11f, Qt::ExtraButton13 }, +}; + +quint32 qtMouseButtonToButton(Qt::MouseButton button) +{ + return s_buttonToQtMouseButton.key(button); +} + +Qt::MouseButton buttonToQtMouseButton(quint32 button) +{ + // All other values get mapped to ExtraButton24 + // this is actually incorrect but doesn't matter in our usage + // we internally doesn't use these high extra buttons anyway + // it's only needed for recognizing whether buttons are pressed + // if multiple buttons are mapped to the value the evaluation whether + // buttons are pressed is correct and that's all we care about. + return s_buttonToQtMouseButton.value(button, Qt::ExtraButton24); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/mousebuttons.h b/src/plugins/deviceintegration/wayland/mousebuttons.h new file mode 100644 index 00000000..295d4781 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/mousebuttons.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2022 David Redondo +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include + +namespace Aurora { + +namespace Platform { + +quint32 qtMouseButtonToButton(Qt::MouseButton button); +Qt::MouseButton buttonToQtMouseButton(quint32 button); + +} // 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 00000000..8e56c4fd --- /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 00000000..e97779d8 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandbackend.cpp @@ -0,0 +1,312 @@ +// 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; +} + +WaylandOutput *WaylandBackend::findOutput(KWayland::Client::Surface *surface) const +{ + for (auto *o : qAsConst(m_outputs)) { + auto *output = static_cast(o); + + if (output && output->surface() == surface) + return 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)); + output->createSurface(); + 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 00000000..60365a78 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandbackend.h @@ -0,0 +1,88 @@ +// 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; + + 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 00000000..30ca1e9a --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandcursor.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include + +#include "waylandbackend.h" +#include "waylandcursor.h" +#include "waylandoutput.h" + +namespace Aurora { + +namespace Platform { + +WaylandCursor::WaylandCursor(WaylandOutput *output, QObject *parent) + : QObject(parent) + , m_surface(WaylandBackend::instance()->compositor()->createSurface(this)) + , m_subSurface(WaylandBackend::instance()->subCompositor()->createSubSurface(m_surface, output->surface(), this)) +{ +} + +WaylandCursor::~WaylandCursor() +{ + destroy(); +} + +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 KWayland::Client::Buffer::Ptr &buffer, qreal scale, + const QPoint &hotspot) +{ + if (m_buffer != buffer || m_scale != scale || m_hotspot != hotspot) { + m_buffer = buffer; + m_scale = scale; + m_hotspot = hotspot; + render(); + } +} + +void WaylandCursor::destroy() +{ + if (m_surface) { + m_surface->release(); + m_surface->destroy(); + } +} + +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); + + if (m_pointer) + m_pointer->setCursor(m_surface, m_hotspot); +} + +} // 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 00000000..79932785 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandcursor.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace Aurora { + +namespace Platform { + +class WaylandOutput; + +class WaylandCursor : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) +public: + explicit WaylandCursor(WaylandOutput *output, 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 KWayland::Client::Buffer::Ptr &buffer, qreal scale, const QPoint &hotspot); + + void destroy(); + +signals: + void enabledChanged(bool enabled); + +private: + bool m_enabled = true; + KWayland::Client::Surface *m_surface = nullptr; + KWayland::Client::SubSurface *m_subSurface = nullptr; + KWayland::Client::Pointer *m_pointer = nullptr; + 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 00000000..c95b293e --- /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 00000000..502209a8 --- /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 00000000..642081f7 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegration.cpp @@ -0,0 +1,130 @@ +// 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() +{ + WaylandBackend::instance()->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); +} + +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"); +} + +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(); +} + +EGLNativeDisplayType WaylandIntegration::platformDisplay() const +{ + return reinterpret_cast(display()); +} + +EGLDisplay WaylandIntegration::createDisplay(EGLNativeDisplayType nativeDisplay) +{ + if (m_eglIntegration->initialize()) + return m_eglIntegration->display(); + return EGL_NO_DISPLAY; +} + +EGLNativeWindowType WaylandIntegration::createNativeWindow(Window *window, const QSize &size, + const QSurfaceFormat &format) +{ + Q_UNUSED(format) + + wl_surface *surface = static_cast(window->resource("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())); + } + + 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 *window) +{ + return new WaylandWindow(output, window); +} + +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 00000000..73450e2b --- /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; + + EGLDisplay eglDisplay() const; + wl_display *display() const; + EGLNativeDisplayType platformDisplay() const override; + + EGLDisplay createDisplay(EGLNativeDisplayType nativeDisplay) override; + + 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 *window) 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 00000000..1b4aae28 --- /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 00000000..330cafde --- /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 00000000..71ec13bb --- /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 00000000..c7f7a0c5 --- /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 00000000..dc4aa1c9 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandoutput.cpp @@ -0,0 +1,224 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandbackend.h" +#include "waylandcursor.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_output(output) + , m_cursor(new WaylandCursor(this, this)) +{ + auto *d = OutputPrivate::get(this); + d->name = name; + d->description = m_output->description(); + d->depth = 32; + d->format = QImage::Format_RGB32; + updateInfo(); + + connect(output, &KWayland::Client::Output::changed, this, &WaylandOutput::updateInfo); + connect(output, &KWayland::Client::Output::modeChanged, this, + &WaylandOutput::handleModeChanged); +} + +WaylandOutput::~WaylandOutput() +{ + destroy(); +} + +KWayland::Client::Surface *WaylandOutput::surface() const +{ + return m_screenSurface.data(); +} + +WaylandCursor *WaylandOutput::cursor() const +{ + return m_cursor; +} + +void WaylandOutput::createSurface() +{ + const auto geometry = m_output->geometry(); + + // Create a shm buffer + auto image = QImage(geometry.size(), QImage::Format_RGB32); + image.fill(Qt::black); + m_screenBuffer = WaylandBackend::instance()->shmPool()->createBuffer(image); + + // Create a surface + m_screenSurface.reset(WaylandBackend::instance()->compositor()->createSurface()); + m_screenSurface->setOpaqueRegion( + WaylandBackend::instance()->compositor()->createRegion(QRegion(geometry)).get()); + m_screenSurface->attachBuffer(m_screenBuffer); + + // Create a toplevel window + m_xdgToplevel.reset( + WaylandBackend::instance()->xdgShell()->createSurface(m_screenSurface.data())); + m_xdgToplevel->setTitle(QStringLiteral("Aurora Compositor")); + m_xdgToplevel->setMinSize(geometry.size()); + m_xdgToplevel->setMaxSize(geometry.size()); + + // We don't want the compositor to draw any decoration for us + if (WaylandBackend::instance()->xdgDecorationManager()) { + m_screenDecoration.reset( + WaylandBackend::instance()->xdgDecorationManager()->getToplevelDecoration( + m_xdgToplevel.data())); + m_screenDecoration->setMode(KWayland::Client::XdgDecoration::Mode::ClientSide); + } + + // Show window + m_screenSurface->commit(KWayland::Client::Surface::CommitFlag::None); +} + +void WaylandOutput::destroySurface() +{ + if (m_screenDecoration) { + m_screenDecoration->release(); + m_screenDecoration->destroy(); + } + + if (m_xdgToplevel) { + m_xdgToplevel->release(); + m_xdgToplevel->destroy(); + } + + if (m_screenSurface) { + m_screenSurface->release(); + m_screenSurface->destroy(); + } +} + +void WaylandOutput::destroy() +{ + if (m_cursor) { + m_cursor->destroy(); + m_cursor = nullptr; + } + + destroySurface(); + + 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 00000000..c64c161a --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandoutput.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace Aurora { + +namespace Platform { + +class WaylandIntegration; +class WaylandCursor; + +class WaylandOutput : public Output +{ + Q_OBJECT + Q_PROPERTY(WaylandCursor *cursor READ cursor CONSTANT) +public: + explicit WaylandOutput(KWayland::Client::Output *output, const QString &name, + QObject *parent = nullptr); + ~WaylandOutput(); + + KWayland::Client::Surface *surface() const; + + WaylandCursor *cursor() const; + + void createSurface(); + void destroySurface(); + + void destroy(); + +protected: + KWayland::Client::Output *m_output = nullptr; + KWayland::Client::Buffer::Ptr m_screenBuffer; + QScopedPointer m_screenSurface; + QScopedPointer m_xdgToplevel; + QScopedPointer m_screenDecoration; + WaylandCursor *m_cursor = nullptr; + +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 00000000..c0c3f9de --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandpointer.cpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include + +#include "mousebuttons.h" +#include "waylandbackend.h" +#include "waylandcursor.h" +#include "waylandoutput.h" +#include "waylandpointer.h" +#include "waylandwindow.h" + +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 serial, const QPointF &relativeToSurface) { + auto *output = + WaylandBackend::instance()->findOutput(m_hostPointer->enteredSurface()); + if (output) { + output->cursor()->setPointer(m_hostPointer); + } + }); + connect(m_hostPointer, &KWayland::Client::Pointer::motion, this, + [this](const QPointF &relativeToSurface, quint32) { + auto *surface = m_hostPointer->enteredSurface(); + if (surface) { + const auto windows = WaylandBackend::instance()->windows(); + for (auto *window : windows) { + if (window->surface() == surface) { + auto absPos = window->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(); +} + +} // 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 00000000..c95a29fa --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandpointer.h @@ -0,0 +1,28 @@ +// 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; +}; + +} // 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 00000000..3f37435f --- /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 00000000..e7dbf441 --- /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 00000000..ec623b64 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.cpp @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "waylandbackend.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 *window, QObject *parent) + : Window(output, parent) + , m_window(window) +{ + WaylandBackend::instance()->registerWindow(this); +} + +WaylandWindow::~WaylandWindow() +{ + WaylandBackend::instance()->unregisterWindow(this); + destroy(); +} + +KWayland::Client::Surface *WaylandWindow::surface() const +{ + return m_surface.data(); +} + +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() +{ + auto *wlOutput = static_cast(output()); + if (!wlOutput) { + qCWarning(gLcWayland) << "Output" << output() << "is not a WaylandOutput"; + return false; + } + + const auto size = m_window->screen()->geometry().size(); + + m_surface.reset(WaylandBackend::instance()->compositor()->createSurface()); + m_surface->setSize(size); + m_surface->commit(KWayland::Client::Surface::CommitFlag::None); + + if (auto *subCompositor = WaylandBackend::instance()->subCompositor()) { + auto *subSurface = subCompositor->createSubSurface(m_surface.data(), wlOutput->surface()); + subSurface->setPosition(m_window->screen()->geometry().topLeft()); + subSurface->setMode(KWayland::Client::SubSurface::Mode::Desynchronized); + m_subSurface.reset(subSurface); + } + + return true; +} + +void WaylandWindow::destroy() +{ + if (m_destroyed) + return; + + m_destroyed = true; + + qCDebug(gLcWayland) << "Window for" << m_window << "is about to be destroyed..."; + + if (m_subSurface) { + m_subSurface->release(); + m_subSurface->destroy(); + } + + if (m_surface) { + m_surface->release(); + m_surface->destroy(); + } +} + +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 00000000..7d7e59f4 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include +#include + +struct wl_egl_window; + +class QWindow; + +namespace Aurora { + +namespace Platform { + +class WaylandWindow : public Window +{ + Q_OBJECT +public: + explicit WaylandWindow(Output *output, QWindow *window, QObject *parent = nullptr); + ~WaylandWindow(); + + KWayland::Client::Surface *surface() const; + + void *resource(const QByteArray &name) override; + + bool create() override; + void destroy() override; + + void present(); + +protected: + QWindow *const m_window = nullptr; + +private: + bool m_destroyed = false; + QScopedPointer m_surface; + QScopedPointer m_subSurface; +}; + +} // namespace Platform + +} // namespace Aurora