From 3cb80e11ab0a2951021410ef73f13c6bd676749a 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 | 38 ++ .../wayland/eglintegration.cpp | 86 +++++ .../wayland/eglintegration.h | 47 +++ .../deviceintegration/wayland/wayland.json | 3 + .../wayland/waylandinputmanager.cpp | 29 ++ .../wayland/waylandinputmanager.h | 24 ++ .../wayland/waylandintegration.cpp | 348 ++++++++++++++++++ .../wayland/waylandintegration.h | 95 +++++ .../wayland/waylandintegrationplugin.cpp | 23 ++ .../wayland/waylandintegrationplugin.h | 24 ++ .../wayland/waylandkeyboard.cpp | 21 ++ .../wayland/waylandkeyboard.h | 22 ++ .../wayland/waylandoutput.cpp | 146 ++++++++ .../deviceintegration/wayland/waylandoutput.h | 33 ++ .../wayland/waylandscreenwindow.cpp | 72 ++++ .../wayland/waylandscreenwindow.h | 37 ++ .../wayland/waylandwindow.cpp | 92 +++++ .../deviceintegration/wayland/waylandwindow.h | 47 +++ 20 files changed, 1213 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/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/waylandscreenwindow.cpp create mode 100644 src/plugins/deviceintegration/wayland/waylandscreenwindow.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 a738d45d9..a4b824ed9 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 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..ada3a4a7c --- /dev/null +++ b/src/plugins/deviceintegration/wayland/CMakeLists.txt @@ -0,0 +1,38 @@ +include(ECMQtDeclareLoggingCategory) +ecm_qt_declare_logging_category( + AuroraDeviceIntegrationWayland_SOURCES + HEADER "waylandloggingcategories.h" + IDENTIFIER "Aurora::Core::gLcWayland" + CATEGORY_NAME "aurora.core.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 + waylandinputmanager.cpp waylandinputmanager.h + waylandintegrationplugin.cpp waylandintegrationplugin.h + waylandkeyboard.cpp waylandkeyboard.h + waylandintegration.cpp waylandintegration.h + waylandoutput.cpp waylandoutput.h + waylandscreenwindow.cpp waylandscreenwindow.h + waylandwindow.cpp waylandwindow.h + wayland.json + ${AuroraDeviceIntegrationWayland_SOURCES} + PUBLIC_LIBRARIES + Qt5::Core + Qt5::Gui + Liri::AuroraCore + Liri::AuroraCorePrivate + 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..f11fb11ca --- /dev/null +++ b/src/plugins/deviceintegration/wayland/eglintegration.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "eglintegration.h" +#include "waylandloggingcategories.h" +#include "waylandintegration.h" + +namespace Aurora { + +namespace Core { + +EglIntegration::EglIntegration(WaylandIntegration *integration) : m_integration(integration) { } + +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, m_integration->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(m_integration->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 Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/eglintegration.h b/src/plugins/deviceintegration/wayland/eglintegration.h new file mode 100644 index 000000000..addd15357 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/eglintegration.h @@ -0,0 +1,47 @@ +// 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 Core { + +class WaylandIntegration; + +class EglIntegration +{ +public: + EglIntegration(WaylandIntegration *integration); + ~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; + QPointer m_integration; + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; +}; + +} // namespace Core + +} // 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/waylandinputmanager.cpp b/src/plugins/deviceintegration/wayland/waylandinputmanager.cpp new file mode 100644 index 000000000..61caaeae9 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandinputmanager.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandinputmanager.h" +#include "waylandintegration.h" + +namespace Aurora { + +namespace Core { + +WaylandInputManager::WaylandInputManager(WaylandIntegration *integration, QObject *parent) + : InputManager(parent), m_integration(integration) +{ + auto *seat = m_integration->seat(); + connect(seat, &KWayland::Client::Seat::hasKeyboardChanged, this, + [this, seat](bool hasKeyboard) { + if (hasKeyboard) { + auto *hostKeyboard = seat->createKeyboard(this); + m_keyboard = new WaylandKeyboard(hostKeyboard, this); + emit deviceAdded(m_keyboard); + } + }); +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandinputmanager.h b/src/plugins/deviceintegration/wayland/waylandinputmanager.h new file mode 100644 index 000000000..1fa49bff0 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandinputmanager.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +namespace Aurora { + +namespace Core { + +class WaylandIntegration; + +class WaylandInputManager : public InputManager +{ + Q_OBJECT +public: + WaylandInputManager(WaylandIntegration *integration, QObject *parent = nullptr); + +private: + QPointer m_integration; +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegration.cpp b/src/plugins/deviceintegration/wayland/waylandintegration.cpp new file mode 100644 index 000000000..4f36aea58 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegration.cpp @@ -0,0 +1,348 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include +#include +#include + +#include "eglintegration.h" +#include "waylandinputmanager.h" +#include "waylandintegration.h" +#include "waylandloggingcategories.h" +#include "waylandoutput.h" +#include "waylandscreenwindow.h" +#include "waylandwindow.h" + +#include + +namespace Aurora { + +namespace Core { + +using namespace KWayland::Client; + +WaylandIntegration::WaylandIntegration(QObject *parent) + : DeviceIntegration(parent), + m_eglIntegration(new EglIntegration(this)), + m_connectionThread(new QThread()), + m_connectionThreadObject(new KWayland::Client::ConnectionThread()), + m_eventQueue(new EventQueue()), + m_registry(new Registry()), + m_compositor(new Compositor()), + m_subCompositor(new SubCompositor()), + m_shmPool(new ShmPool()), + m_screenWindow(new WaylandScreenWindow(this)) +{ + m_connectionThreadObject->moveToThread(m_connectionThread.get()); +} + +WaylandIntegration::~WaylandIntegration() +{ + destroy(); +} + +void WaylandIntegration::initialize() +{ + // Compositor + connect(m_registry.data(), &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.reset(m_registry->createCompositor(name, version)); + }); + + // Compositor for sub-surfaces + connect(m_registry.data(), &Registry::subCompositorAnnounced, this, + [this](quint32 name, quint32 version) { + m_subCompositor.reset(m_registry->createSubCompositor(name, version)); + }); + + // Shared memory pool + connect(m_registry.data(), &Registry::shmAnnounced, this, + [this](quint32 name, quint32 version) { + m_shmPool.reset(m_registry->createShmPool(name, version)); + }); + + // Seat + connect(m_registry.data(), &Registry::seatAnnounced, this, + [this](quint32 name, quint32 version) { + m_seat.reset(m_registry->createSeat(name, version)); + }); + + // Output + connect(m_registry.data(), &Registry::outputAnnounced, this, + [this](quint32 name, quint32 version) { + auto *hostOutput = m_registry->createOutput(name, version); + m_hostOutputsCount++; + connect(hostOutput, &KWayland::Client::Output::changed, this, + &WaylandIntegration::handleOutputChanged); + }); + + // XDG shell + connect(m_registry.data(), &Registry::xdgShellStableAnnounced, this, + [this](quint32 name, quint32 version) { + m_xdgShell.reset(m_registry->createXdgShell(name, version)); + }); + + // XDG decoration + connect(m_registry.data(), &Registry::xdgDecorationAnnounced, this, + [this](quint32 name, quint32 version) { + m_xdgDecorationManager.reset(m_registry->createXdgDecorationManager(name, version)); + }); + + // Verify we have what we need + connect(m_registry.data(), &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.get(), &ConnectionThread::connected, this, + [this]() { + qCDebug(gLcWayland) << "Connected to:" << m_connectionThreadObject->socketName(); + + // Create the event queue + m_eventQueue->setup(m_connectionThreadObject.get()); + m_registry->setEventQueue(m_eventQueue.get()); + + // Registry + m_registry->create(m_connectionThreadObject.get()); + m_registry->setup(); + m_connectionThreadObject->flush(); + }, + Qt::QueuedConnection); + connect( + m_connectionThreadObject.get(), &ConnectionThread::connectionDied, this, + [this]() { + setReady(false); + + if (m_seat) + m_seat->destroy(); + + if (m_shmPool) + m_shmPool->destroy(); + + if (m_screenWindow) + m_screenWindow->destroy(); + + destroyOutputs(); + + 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_connectionThread->start(); + m_connectionThreadObject->initConnection(); + + qCInfo(gLcWayland, "Wayland device integration initialized successfully"); +} + +void WaylandIntegration::destroy() +{ + if (m_eventQueue) + m_eventQueue->release(); + + if (m_screenWindow) + m_screenWindow->destroy(); + + destroyOutputs(); + + if (m_xdgDecorationManager) + m_xdgDecorationManager->release(); + if (m_xdgShell) + m_xdgShell->release(); + + if (m_subCompositor) + m_subCompositor->release(); + if (m_compositor) + m_compositor->release(); + if (m_shmPool) + m_shmPool->release(); + if (m_seat) + m_seat->destroy(); + if (m_registry) + m_registry->release(); + + if (m_connectionThread) { + m_connectionThread->quit(); + m_connectionThread->wait(); + } + + 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 +{ + if (m_connectionThreadObject) + return m_connectionThreadObject->display(); + return nullptr; +} + +EGLNativeDisplayType WaylandIntegration::platformDisplay() const +{ + return reinterpret_cast(display()); +} + +KWayland::Client::Compositor *WaylandIntegration::compositor() const +{ + return m_compositor.data(); +} + +KWayland::Client::SubCompositor *WaylandIntegration::subCompositor() const +{ + return m_subCompositor.data(); +} + +KWayland::Client::ShmPool *WaylandIntegration::shmPool() const +{ + return m_shmPool.data(); +} + +KWayland::Client::Seat *WaylandIntegration::seat() const +{ + return m_seat.data(); +} + +KWayland::Client::XdgShell *WaylandIntegration::xdgShell() const +{ + return m_xdgShell.data(); +} + +KWayland::Client::XdgDecorationManager *WaylandIntegration::xdgDecorationManager() const +{ + return m_xdgDecorationManager.data(); +} + +WaylandScreenWindow *WaylandIntegration::screenWindow() const +{ + return m_screenWindow.data(); +} + +void WaylandIntegration::flush() +{ + if (m_connectionThreadObject) + m_connectionThreadObject->flush(); +} + +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(QWindow *window) +{ + return new WaylandWindow(this, window); +} + +InputManager *WaylandIntegration::createInputManager() +{ + return new WaylandInputManager(this); +} + +Outputs WaylandIntegration::outputs() const +{ + return m_outputs; +} + +void WaylandIntegration::handleOutputChanged() +{ + auto *hostOutput = static_cast(sender()); + disconnect(hostOutput, &KWayland::Client::Output::changed, this, + &WaylandIntegration::handleOutputChanged); + + 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) { + setReady(true); + + if (m_screenWindow) { + // Determine the overall screen size of all outputs combined + QRect geometry = m_outputs[0]->geometry(); + for (int i = 1; i < m_outputs.size(); i++) + geometry = geometry.united(m_outputs[i]->geometry()); + + m_screenWindow->create(geometry); + } + } +} + +void WaylandIntegration::destroyOutputs() +{ + auto it = m_outputs.begin(); + while (it != m_outputs.end()) { + auto *output = (*it); + emit outputRemoved(output); + output->deleteLater(); + it = m_outputs.erase(it); + } +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegration.h b/src/plugins/deviceintegration/wayland/waylandintegration.h new file mode 100644 index 000000000..e2e55c8a7 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegration.h @@ -0,0 +1,95 @@ +// 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 + +namespace Aurora { + +namespace Core { + +class EglIntegration; +class WaylandWindow; +class WaylandScreenWindow; + +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; + + 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; + + WaylandScreenWindow *screenWindow() const; + + void flush(); + + 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(QWindow *window) override; + + InputManager *createInputManager() override; + + Outputs outputs() const override; + +private: + QScopedPointer m_eglIntegration; + + QScopedPointer m_connectionThread; + QScopedPointer m_connectionThreadObject; + QScopedPointer m_eventQueue; + QScopedPointer m_registry; + QScopedPointer m_compositor; + QScopedPointer m_subCompositor; + QScopedPointer m_shmPool; + QScopedPointer m_seat; + QScopedPointer m_xdgShell; + QScopedPointer m_xdgDecorationManager; + + QScopedPointer m_screenWindow; + + int m_hostOutputsCount = 0; + Outputs m_outputs; + +private slots: + void handleOutputChanged(); + void destroyOutputs(); + void createScreenSurface(); + void destroyScreenSurface(); +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegrationplugin.cpp b/src/plugins/deviceintegration/wayland/waylandintegrationplugin.cpp new file mode 100644 index 000000000..2da528d3e --- /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 Core { + +WaylandIntegrationPlugin::WaylandIntegrationPlugin(QObject *parent) + : DeviceIntegrationPlugin(parent) +{ +} + +DeviceIntegration *WaylandIntegrationPlugin::create() +{ + return new WaylandIntegration(this); +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandintegrationplugin.h b/src/plugins/deviceintegration/wayland/waylandintegrationplugin.h new file mode 100644 index 000000000..3fcc9adfd --- /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 Core { + +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 Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandkeyboard.cpp b/src/plugins/deviceintegration/wayland/waylandkeyboard.cpp new file mode 100644 index 000000000..4b161bbf0 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandkeyboard.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "waylandkeyboard.h" + +namespace Aurora { + +namespace Core { + +WaylandKeyboard::WaylandKeyboard(KWayland::Client::Keyboard *hostKeyboard, QObject *parent) + : KeyboardDevice(parent), m_keyboard(hostKeyboard) +{ + connect(m_keyboard.data(), &KWayland::Client::Keyboard::keyChanged, this, + [this](quint32 key, KWayland::Client::Keyboard::KeyState state, quint32 time) { + + }); +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandkeyboard.h b/src/plugins/deviceintegration/wayland/waylandkeyboard.h new file mode 100644 index 000000000..a3f6657b0 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandkeyboard.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +namespace Aurora { + +namespace Core { + +class WaylandKeyboard : public KeyboardDevice +{ + Q_OBJECT +public: + WaylandKeyboard(KWayland::Client::Keyboard *hostKeyboard, QObject *parent = nullptr); + +private: + QPointer m_keyboard; +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandoutput.cpp b/src/plugins/deviceintegration/wayland/waylandoutput.cpp new file mode 100644 index 000000000..f295afc77 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandoutput.cpp @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandoutput.h" + +namespace Aurora { + +namespace Core { + +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) +{ + 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() +{ + 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 Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandoutput.h b/src/plugins/deviceintegration/wayland/waylandoutput.h new file mode 100644 index 000000000..c612499c7 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandoutput.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +namespace Aurora { + +namespace Core { + +class WaylandOutput : public Output +{ + Q_OBJECT +public: + explicit WaylandOutput(KWayland::Client::Output *output, const QString &name, + QObject *parent = nullptr); + ~WaylandOutput(); + +protected: + QScopedPointer m_output; + +private slots: + void updateInfo(); + void handleModeAdded(const KWayland::Client::Output::Mode &mode); + void handleModeChanged(const KWayland::Client::Output::Mode &mode); +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandscreenwindow.cpp b/src/plugins/deviceintegration/wayland/waylandscreenwindow.cpp new file mode 100644 index 000000000..b892a93cc --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandscreenwindow.cpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandintegration.h" +#include "waylandscreenwindow.h" + +namespace Aurora { + +namespace Core { + +WaylandScreenWindow::WaylandScreenWindow(WaylandIntegration *integration, QObject *parent) + : QObject(parent), m_integration(integration) +{ +} + +WaylandScreenWindow::~WaylandScreenWindow() +{ + destroy(); +} + +KWayland::Client::Surface *WaylandScreenWindow::surface() const +{ + return m_screenSurface.data(); +} + +void WaylandScreenWindow::create(const QRect &geometry) +{ + // Create a shm buffer + auto image = QImage(geometry.size(), QImage::Format_RGB32); + image.fill(Qt::black); + m_screenBuffer = m_integration->shmPool()->createBuffer(image); + + // Create a surface + m_screenSurface.reset(m_integration->compositor()->createSurface()); + m_screenSurface->setOpaqueRegion( + m_integration->compositor()->createRegion(QRegion(geometry)).get()); + m_screenSurface->attachBuffer(m_screenBuffer); + + // Create a toplevel window + m_screenWindow.reset(m_integration->xdgShell()->createSurface(m_screenSurface.data())); + m_screenWindow->setTitle(QStringLiteral("Aurora Compositor")); + m_screenWindow->setMinSize(geometry.size()); + m_screenWindow->setMaxSize(geometry.size()); + + // We don't want the compositor to draw any decoration for us + if (m_integration->xdgDecorationManager()) { + m_screenDecoration.reset(m_integration->xdgDecorationManager()->getToplevelDecoration( + m_screenWindow.data())); + m_screenDecoration->setMode(KWayland::Client::XdgDecoration::Mode::ClientSide); + } + + // Show window + m_screenSurface->commit(KWayland::Client::Surface::CommitFlag::None); +} + +void WaylandScreenWindow::destroy() +{ + if (m_screenSurface) + m_screenSurface->release(); + + if (m_screenDecoration) + m_screenDecoration->release(); + + if (m_screenWindow) + m_screenWindow->release(); +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandscreenwindow.h b/src/plugins/deviceintegration/wayland/waylandscreenwindow.h new file mode 100644 index 000000000..91df5ccd1 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandscreenwindow.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include +#include +#include + +namespace Aurora { + +namespace Core { + +class WaylandIntegration; + +class WaylandScreenWindow : public QObject +{ +public: + WaylandScreenWindow(WaylandIntegration *integration, QObject *parent = nullptr); + ~WaylandScreenWindow(); + + KWayland::Client::Surface *surface() const; + + void create(const QRect &geometry); + void destroy(); + +private: + QPointer m_integration; + KWayland::Client::Buffer::Ptr m_screenBuffer; + QScopedPointer m_screenSurface; + QScopedPointer m_screenWindow; + QScopedPointer m_screenDecoration; +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandwindow.cpp b/src/plugins/deviceintegration/wayland/waylandwindow.cpp new file mode 100644 index 000000000..d399340b9 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.cpp @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "waylandintegration.h" +#include "waylandloggingcategories.h" +#include "waylandscreenwindow.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 Core { + +WaylandWindow::WaylandWindow(WaylandIntegration *integration, QWindow *window, QObject *parent) + : Window(parent), m_integration(integration), m_window(window) +{ +} + +WaylandWindow::~WaylandWindow() +{ + destroy(); +} + +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() +{ + const auto size = m_window->screen()->geometry().size(); + + m_surface.reset(m_integration->compositor()->createSurface()); + m_surface->setSize(size); + m_surface->commit(KWayland::Client::Surface::CommitFlag::None); + + if (m_integration->subCompositor()) { + auto *subSurface = m_integration->subCompositor()->createSubSurface( + m_surface.data(), m_integration->screenWindow()->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_subSurface) + m_subSurface->destroy(); + + if (m_surface) + m_surface->destroy(); +} + +void WaylandWindow::present() +{ + qWarning() << "present"; +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandwindow.h b/src/plugins/deviceintegration/wayland/waylandwindow.h new file mode 100644 index 000000000..bb5063934 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.h @@ -0,0 +1,47 @@ +// 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 Core { + +class WaylandIntegration; + +class WaylandWindow : public Window +{ + Q_OBJECT +public: + explicit WaylandWindow(WaylandIntegration *integration, QWindow *window, + QObject *parent = nullptr); + ~WaylandWindow(); + + void *resource(const QByteArray &name) override; + + bool create() override; + void destroy() override; + + void present(); + +protected: + QPointer m_integration; + QPointer m_window; + +private: + QScopedPointer m_surface; + QScopedPointer m_subSurface; +}; + +} // namespace Core + +} // namespace Aurora