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..eacadb03 --- /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 + 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 + waylandscreenwindow.cpp waylandscreenwindow.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..2327f90c --- /dev/null +++ b/src/plugins/deviceintegration/wayland/eglintegration.cpp @@ -0,0 +1,89 @@ +// 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 Platform { + +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 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..503d06a7 --- /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 Platform { + +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 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/waylandcursor.cpp b/src/plugins/deviceintegration/wayland/waylandcursor.cpp new file mode 100644 index 00000000..e4e60d2f --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandcursor.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandcursor.h" +#include "waylandintegration.h" + +namespace Aurora { + +namespace Platform { + +WaylandCursor::WaylandCursor(WaylandIntegration *integration, QObject *parent) + : QObject(parent) + , m_integration(integration) + , m_surface(integration->compositor()->createSurface(this)) +{ +} + +WaylandCursor::~WaylandCursor() +{ +} + +} // 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..51fab129 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandcursor.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class WaylandIntegration; + +class WaylandCursor : public QObject +{ + Q_OBJECT +public: + explicit WaylandCursor(WaylandIntegration *integration, QObject *parent = nullptr); + ~WaylandCursor(); + +private: + WaylandIntegration *const m_integration; + KWayland::Client::Surface *m_surface = nullptr; +}; + +} // 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..80b9fb5c --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandinputmanager.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandinputmanager.h" +#include "waylandintegration.h" +#include "waylandkeyboard.h" +#include "waylandpointer.h" +#include "waylandtouch.h" + +namespace Aurora { + +namespace Platform { + +WaylandInputManager::WaylandInputManager(WaylandIntegration *integration, QObject *parent) + : InputManager(parent) + , m_integration(integration) +{ + auto *seat = m_integration->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 = m_integration->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 = m_integration->seat(); + + if (hasPointer) { + m_pointer = new WaylandPointer(m_integration, 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 = m_integration->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..03e05d4f --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandinputmanager.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +class WaylandIntegration; +class WaylandKeyboard; +class WaylandPointer; +class WaylandTouch; + +typedef QMap InputDevicesMap; + +class WaylandInputManager : public InputManager +{ + Q_OBJECT +public: + WaylandInputManager(WaylandIntegration *integration, QObject *parent = nullptr); + ~WaylandInputManager(); + + QList keyboardDevices() const override; + QList pointerDevices() const override; + QList touchDevices() const override; + + int deviceCount(InputDevice::DeviceType deviceType) const override; + +private: + WaylandIntegration *const m_integration = nullptr; + 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..bfb2c089 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegration.cpp @@ -0,0 +1,365 @@ +// 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 Platform { + +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_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_eventQueue) + m_eventQueue->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(Output *output, QWindow *window) +{ + return new WaylandWindow(this, output, window); +} + +InputManager *WaylandIntegration::createInputManager() +{ + return new WaylandInputManager(this); +} + +Outputs WaylandIntegration::outputs() const +{ + return m_outputs; +} + +QList WaylandIntegration::windows() const +{ + return m_windows; +} + +void WaylandIntegration::registerWindow(WaylandWindow *window) +{ + m_windows.append(window); +} + +void WaylandIntegration::unregisterWindow(WaylandWindow *window) +{ + m_windows.removeOne(window); +} + +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 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..02b87380 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandintegration.h @@ -0,0 +1,102 @@ +// 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 Platform { + +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(Output *output, QWindow *window) override; + + InputManager *createInputManager() override; + + Outputs outputs() const override; + + QList windows() const; + + void registerWindow(WaylandWindow *window); + void unregisterWindow(WaylandWindow *window); + +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; + + QList m_windows; + +private slots: + void handleOutputChanged(); + void destroyOutputs(); + void createScreenSurface(); + void destroyScreenSurface(); +}; + +} // 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..764e1b57 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandoutput.cpp @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#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) +{ + 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 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..de083d6f --- /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 Platform { + +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 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..6e4adfeb --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandpointer.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include + +#include "mousebuttons.h" +#include "waylandintegration.h" +#include "waylandpointer.h" +#include "waylandwindow.h" + +namespace Aurora { + +namespace Platform { + +WaylandPointer::WaylandPointer(WaylandIntegration *integration, KWayland::Client::Seat *hostSeat, + QObject *parent) + : PointerDevice(parent) + , m_integration(integration) + , m_hostSeat(hostSeat) + , m_hostPointer(hostSeat->createPointer(this)) +{ + connect(m_hostPointer, &KWayland::Client::Pointer::motion, this, + [this](const QPointF &relativeToSurface, quint32) { + auto *surface = m_hostPointer->enteredSurface(); + if (surface) { + const auto windows = m_integration->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..757c30cb --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandpointer.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 WaylandIntegration; + +class WaylandPointer : public PointerDevice +{ + Q_OBJECT +public: + explicit WaylandPointer(WaylandIntegration *integration, KWayland::Client::Seat *hostSeat, + QObject *parent = nullptr); + + QString seatName() const override; + +private: + WaylandIntegration *const m_integration = nullptr; + KWayland::Client::Seat *const m_hostSeat = nullptr; + KWayland::Client::Pointer *m_hostPointer = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandscreenwindow.cpp b/src/plugins/deviceintegration/wayland/waylandscreenwindow.cpp new file mode 100644 index 00000000..2e970232 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandscreenwindow.cpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "waylandintegration.h" +#include "waylandscreenwindow.h" + +namespace Aurora { + +namespace Platform { + +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 Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/wayland/waylandscreenwindow.h b/src/plugins/deviceintegration/wayland/waylandscreenwindow.h new file mode 100644 index 00000000..3112a4ba --- /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 Platform { + +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 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..ffe16711 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.cpp @@ -0,0 +1,104 @@ +// 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 Platform { + +WaylandWindow::WaylandWindow(WaylandIntegration *integration, Output *output, QWindow *window, + QObject *parent) + : Window(output, parent) + , m_integration(integration) + , m_window(window) +{ + m_integration->registerWindow(this); +} + +WaylandWindow::~WaylandWindow() +{ + m_integration->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() +{ + 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() +{ + 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..a35276e9 --- /dev/null +++ b/src/plugins/deviceintegration/wayland/waylandwindow.h @@ -0,0 +1,49 @@ +// 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 WaylandIntegration; + +class WaylandWindow : public Window +{ + Q_OBJECT +public: + explicit WaylandWindow(WaylandIntegration *integration, 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: + WaylandIntegration *const m_integration = nullptr; + QWindow *const m_window = nullptr; + +private: + QScopedPointer m_surface; + QScopedPointer m_subSurface; +}; + +} // namespace Platform + +} // namespace Aurora