Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Device integration for Wayland
Browse files Browse the repository at this point in the history
Closes: #42
plfiorini committed Oct 21, 2023
1 parent e45e85b commit ce60f84
Showing 26 changed files with 1,859 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -99,6 +99,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)
23 changes: 23 additions & 0 deletions features.cmake
Original file line number Diff line number Diff line change
@@ -471,6 +471,29 @@ if(FEATURE_aurora_vulkan_server_buffer)
endif()
set(LIRI_FEATURE_aurora_vulkan_server_buffer "$<IF:${FEATURE_aurora_vulkan_server_buffer},1,0>")

# 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 "$<IF:${FEATURE_aurora_deviceintegration_wayland},1,0>")

# xwayland
option(FEATURE_aurora_xwayland "XWayland support" ON)
if(FEATURE_aurora_xwayland)
45 changes: 45 additions & 0 deletions src/plugins/deviceintegration/wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
include(ECMQtDeclareLoggingCategory)
ecm_qt_declare_logging_category(
AuroraDeviceIntegrationWayland_SOURCES
HEADER "waylandloggingcategories.h"
IDENTIFIER "Aurora::Platform::gLcWayland"
CATEGORY_NAME "aurora.platform.wayland"
DEFAULT_SEVERITY "Info"
DESCRIPTION "Aurora device integration for Wayland"
)

liri_add_plugin(AuroraDeviceIntegrationWayland
TYPE
"aurora/deviceintegration"
OUTPUT_NAME
wayland
SOURCES
eglintegration.cpp eglintegration.h
waylandbackend.cpp waylandbackend.h
waylandcursor.cpp waylandcursor.h
waylandinputmanager.cpp waylandinputmanager.h
waylandintegrationplugin.cpp waylandintegrationplugin.h
waylandkeyboard.cpp waylandkeyboard.h
waylandintegration.cpp waylandintegration.h
waylandoutput.cpp waylandoutput.h
waylandpointer.cpp waylandpointer.h
waylandtouch.cpp waylandtouch.h
waylandwindow.cpp waylandwindow.h
wayland.json
${AuroraDeviceIntegrationWayland_SOURCES}
PUBLIC_LIBRARIES
Qt::Core
Qt::Gui
Liri::AuroraCore
Liri::AuroraPlatform
Liri::AuroraPlatformPrivate
Liri::AuroraXkbCommonSupport
Liri::AuroraXkbCommonSupportPrivate
EGL::EGL
Wayland::Egl
LIBRARIES
Liri::AuroraGlobalPrivate
KF5::WaylandClient
)

liri_finalize_plugin(AuroraDeviceIntegrationWayland)
88 changes: 88 additions & 0 deletions src/plugins/deviceintegration/wayland/eglintegration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QByteArray>
#include <QList>

#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<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
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<EGLNativeDisplayType>(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<QByteArray> extensions =
QByteArray(reinterpret_cast<const char *>(eglQueryString(display, EGL_EXTENSIONS)))
.split(' ');
return extensions.contains(name);
}

} // namespace Platform

} // namespace Aurora
44 changes: 44 additions & 0 deletions src/plugins/deviceintegration/wayland/eglintegration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QPointer>

#ifndef EGL_NO_X11
# define EGL_NO_X11
#endif
#ifndef MESA_EGL_NO_X11_HEADERS
# define MESA_EGL_NO_X11_HEADERS
#endif

#include <EGL/egl.h>
#include <EGL/eglext.h>

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
3 changes: 3 additions & 0 deletions src/plugins/deviceintegration/wayland/wayland.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Keys": [ "wayland" ]
}
324 changes: 324 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandbackend.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <KWayland/Client/region.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgshell.h>

#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;
}

#if LIRI_FEATURE_aurora_xkbcommon
struct xkb_context *WaylandBackend::xkbContext() const
{
return m_xkbContext.get();
}
#endif

void WaylandBackend::initialize()
{
// Compositor
connect(m_registry, &Registry::compositorAnnounced, this,
[this](quint32 name, quint32 version) {
if (version < 4) {
qCCritical(gLcWayland, "wl_compositor version 4 or later is required");
qFatal("Aborting...");
}
m_compositor = m_registry->createCompositor(name, version);
});

// Compositor for sub-surfaces
connect(m_registry, &Registry::subCompositorAnnounced, this,
[this](quint32 name, quint32 version) {
m_subCompositor = m_registry->createSubCompositor(name, version);
});

// Shared memory pool
connect(m_registry, &Registry::shmAnnounced, this, [this](quint32 name, quint32 version) {
m_shmPool = m_registry->createShmPool(name, version);
});

// Seat
connect(m_registry, &Registry::seatAnnounced, this, [this](quint32 name, quint32 version) {
m_seat = m_registry->createSeat(name, version);
});

// Output
connect(m_registry, &Registry::outputAnnounced, this, [this](quint32 name, quint32 version) {
m_hostOutputsCount++;

auto *hostOutput = m_registry->createOutput(name, version);

connect(hostOutput, &KWayland::Client::Output::changed, this,
&WaylandBackend::handleOutputChanged);
});

// XDG shell
connect(m_registry, &Registry::xdgShellStableAnnounced, this,
[this](quint32 name, quint32 version) {
m_xdgShell = m_registry->createXdgShell(name, version);
});

// XDG decoration
connect(m_registry, &Registry::xdgDecorationAnnounced, this,
[this](quint32 name, quint32 version) {
m_xdgDecorationManager = m_registry->createXdgDecorationManager(name, version);
});

// Verify we have what we need
connect(m_registry, &Registry::interfacesAnnounced, this, [this]() {
if (!m_xdgShell || !m_xdgShell->isValid())
qCritical("The xdg_wm_base interface was not found, cannot continue!");
});

// Connection
connect(
m_connectionThreadObject, &ConnectionThread::connected, this,
[this]() {
qCDebug(gLcWayland) << "Connected to:" << m_connectionThreadObject->socketName();

// Create the event queue
m_eventQueue->setup(m_connectionThreadObject);
m_registry->setEventQueue(m_eventQueue);

// Registry
m_registry->create(m_connectionThreadObject);
m_registry->setup();
m_connectionThreadObject->flush();
},
Qt::QueuedConnection);
connect(
m_connectionThreadObject, &ConnectionThread::connectionDied, this,
[this]() {
emit statusChanged(DeviceIntegration::Status::Failed);

destroyOutputs();

if (m_seat)
m_seat->destroy();

if (m_shmPool)
m_shmPool->destroy();

if (m_xdgDecorationManager)
m_xdgDecorationManager->destroy();
if (m_xdgShell)
m_xdgShell->destroy();

if (m_subCompositor)
m_subCompositor->destroy();
if (m_compositor)
m_compositor->destroy();
if (m_registry)
m_registry->destroy();
if (m_eventQueue)
m_eventQueue->destroy();
},
Qt::QueuedConnection);
connect(m_connectionThreadObject, &ConnectionThread::failed, this, [this]() {
qCDebug(gLcWayland) << "Failed to connect to:" << m_connectionThreadObject->socketName();

emit statusChanged(DeviceIntegration::Status::Failed);

destroyOutputs();
});

// Connect
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}

void WaylandBackend::destroy()
{
destroyOutputs();

if (m_xdgDecorationManager) {
m_xdgDecorationManager->release();
m_xdgDecorationManager->destroy();
}
if (m_xdgShell) {
m_xdgShell->release();
m_xdgShell->destroy();
}

if (m_subCompositor) {
m_subCompositor->release();
m_subCompositor->destroy();
}
if (m_seat) {
m_seat->release();
m_seat->destroy();
}
if (m_shmPool) {
m_shmPool->release();
m_shmPool->destroy();
}
if (m_compositor) {
m_compositor->release();
m_compositor->destroy();
}
if (m_registry) {
m_registry->release();
m_registry->destroy();
}

if (m_connectionThread) {
m_connectionThread->quit();
m_connectionThread->wait();
}

if (m_eventQueue)
m_eventQueue->release();
}

void WaylandBackend::flush()
{
if (m_connectionThreadObject)
m_connectionThreadObject->flush();
}

WaylandBackend *WaylandBackend::instance()
{
return gWaylandBackend();
}

KWayland::Client::Compositor *WaylandBackend::compositor() const
{
return m_compositor;
}

KWayland::Client::SubCompositor *WaylandBackend::subCompositor() const
{
return m_subCompositor;
}

KWayland::Client::ShmPool *WaylandBackend::shmPool() const
{
return m_shmPool;
}

KWayland::Client::Seat *WaylandBackend::seat() const
{
return m_seat;
}

KWayland::Client::XdgShell *WaylandBackend::xdgShell() const
{
return m_xdgShell;
}

KWayland::Client::XdgDecorationManager *WaylandBackend::xdgDecorationManager() const
{
return m_xdgDecorationManager;
}

Outputs WaylandBackend::outputs() const
{
return m_outputs;
}

WaylandWindow *WaylandBackend::findWindow(KWayland::Client::Surface *surface) const
{
for (auto *wlWindow : qAsConst(m_windows)) {
if (wlWindow->surface() == surface)
return wlWindow;
}

return nullptr;
}

WaylandOutput *WaylandBackend::findOutput(KWayland::Client::Surface *surface) const
{
if (auto *wlWindow = findWindow(surface))
return static_cast<WaylandOutput *>(wlWindow->output());

return nullptr;
}

QList<WaylandWindow *> 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<KWayland::Client::Output *>(sender());
if (!hostOutput)
return;

const auto index = m_outputs.size() + 1;
auto *output = new WaylandOutput(hostOutput, QStringLiteral("WL-%1").arg(index));
m_outputs.append(output);
emit outputAdded(output);

if (m_outputs.size() == m_hostOutputsCount)
emit statusChanged(DeviceIntegration::Status::Ready);
}

void WaylandBackend::destroyOutputs()
{
auto it = m_outputs.begin();
while (it != m_outputs.end()) {
auto *output = static_cast<WaylandOutput *>(*it);
emit outputRemoved(output);
output->destroy();
output->deleteLater();
it = m_outputs.erase(it);
}
}

} // namespace Platform

} // namespace Aurora
89 changes: 89 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandbackend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QThread>

#include <LiriAuroraPlatform/DeviceIntegration>

#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/event_queue.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/subcompositor.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/xdgdecoration.h>
#include <KWayland/Client/xdgshell.h>

namespace Aurora {

namespace Platform {

class WaylandOutput;
class WaylandWindow;

class WaylandBackend : public QObject
{
Q_OBJECT
public:
explicit WaylandBackend(QObject *parent = nullptr);
~WaylandBackend();

wl_display *display() const;

void initialize();
void destroy();

void flush();

KWayland::Client::Compositor *compositor() const;
KWayland::Client::SubCompositor *subCompositor() const;
KWayland::Client::ShmPool *shmPool() const;
KWayland::Client::Seat *seat() const;
KWayland::Client::XdgShell *xdgShell() const;
KWayland::Client::XdgDecorationManager *xdgDecorationManager() const;

Outputs outputs() const;

WaylandWindow *findWindow(KWayland::Client::Surface *surface) const;
WaylandOutput *findOutput(KWayland::Client::Surface *surface) const;

QList<WaylandWindow *> 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<WaylandWindow *> m_windows;

private slots:
void handleOutputChanged();
void destroyOutputs();
};

} // namespace Platform

} // namespace Aurora
88 changes: 88 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandcursor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QtMath>

#include <KWayland/Client/compositor.h>

#include "waylandbackend.h"
#include "waylandcursor.h"
#include "waylandwindow.h"

namespace Aurora {

namespace Platform {

WaylandCursor::WaylandCursor(WaylandWindow *window, QObject *parent)
: QObject(parent)
, m_window(window)
, m_surface(WaylandBackend::instance()->compositor()->createSurface(this))
{
}

WaylandCursor::~WaylandCursor()
{
if (m_surface) {
m_surface->release();
m_surface->destroy();
m_surface = nullptr;
}
}

bool WaylandCursor::isEnabled() const
{
return m_enabled;
}

void WaylandCursor::setEnabled(bool enabled)
{
if (m_enabled == enabled)
return;

m_enabled = enabled;
render();
}

KWayland::Client::Pointer *WaylandCursor::pointer() const
{
return m_pointer;
}

void WaylandCursor::setPointer(KWayland::Client::Pointer *pointer)
{
if (m_pointer == pointer)
return;

m_pointer = pointer;
if (m_pointer)
m_pointer->setCursor(m_surface, m_hotSpot);
}

void WaylandCursor::update(const QImage &image, const QPoint &hotSpot, qreal scale)
{
if (m_image != image || m_scale != scale || m_hotSpot != hotSpot) {
m_image = image;
m_buffer = WaylandBackend::instance()->shmPool()->createBuffer(m_image);
m_scale = scale;
m_hotSpot = hotSpot;
render();
}
}

void WaylandCursor::render()
{
if (m_enabled) {
m_surface->attachBuffer(m_buffer);
m_surface->setScale(qCeil(m_scale));
m_surface->damageBuffer(QRect(0, 0, INT32_MAX, INT32_MAX));
} else {
m_surface->attachBuffer(KWayland::Client::Buffer::Ptr());
}
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);

m_window->present();
}

} // namespace Platform

} // namespace Aurora
49 changes: 49 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandcursor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <KWayland/Client/pointer.h>
#include <KWayland/Client/surface.h>

namespace Aurora {

namespace Platform {

class WaylandWindow;

class WaylandCursor : public QObject
{
Q_OBJECT
Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged)
public:
explicit WaylandCursor(WaylandWindow *window, QObject *parent = nullptr);
~WaylandCursor();

bool isEnabled() const;
void setEnabled(bool enabled);

KWayland::Client::Pointer *pointer() const;
void setPointer(KWayland::Client::Pointer *pointer);

void update(const QImage &image, const QPoint &hotSpot, qreal scale);

signals:
void enabledChanged(bool enabled);

private:
bool m_enabled = false;
QPointer<WaylandWindow> m_window;
KWayland::Client::Surface *m_surface = nullptr;
KWayland::Client::Pointer *m_pointer = nullptr;
QImage m_image;
KWayland::Client::Buffer::Ptr m_buffer;
QPoint m_hotSpot;
qreal m_scale = 1;

void render();
};

} // namespace Platform

} // namespace Aurora
120 changes: 120 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandinputmanager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// 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<KeyboardDevice *> WaylandInputManager::keyboardDevices() const
{
return QList<KeyboardDevice *>() << m_keyboard;
}

QList<PointerDevice *> WaylandInputManager::pointerDevices() const
{
return QList<PointerDevice *>() << m_pointer;
}

QList<TouchDevice *> WaylandInputManager::touchDevices() const
{
return QList<TouchDevice *>() << 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
45 changes: 45 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandinputmanager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QMap>
#include <QPointer>

#include <LiriAuroraPlatform/InputManager>

namespace Aurora {

namespace Platform {

class WaylandKeyboard;
class WaylandPointer;
class WaylandTouch;

typedef QMap<InputDevice::DeviceType, InputDevice *> InputDevicesMap;

class WaylandInputManager : public InputManager
{
Q_OBJECT
public:
WaylandInputManager(QObject *parent = nullptr);
~WaylandInputManager();

QList<KeyboardDevice *> keyboardDevices() const override;
QList<PointerDevice *> pointerDevices() const override;
QList<TouchDevice *> 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
146 changes: 146 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandintegration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QImage>

#include "eglintegration.h"
#include "waylandbackend.h"
#include "waylandinputmanager.h"
#include "waylandintegration.h"
#include "waylandloggingcategories.h"
#include "waylandoutput.h"
#include "waylandwindow.h"

#include <wayland-egl.h>

namespace Aurora {

namespace Platform {

WaylandIntegration::WaylandIntegration(QObject *parent)
: DeviceIntegration(parent)
, m_eglIntegration(new EglIntegration())
{
}

WaylandIntegration::~WaylandIntegration()
{
}

void WaylandIntegration::initialize()
{
connect(WaylandBackend::instance(), &WaylandBackend::statusChanged, this,
[this](DeviceIntegration::Status status) { DeviceIntegration::setStatus(status); });
connect(WaylandBackend::instance(), &WaylandBackend::outputAdded, this,
&WaylandIntegration::outputAdded);
connect(WaylandBackend::instance(), &WaylandBackend::outputRemoved, this,
&WaylandIntegration::outputRemoved);

WaylandBackend::instance()->initialize();

if (m_eglIntegration)
m_eglIntegration->initialize();
}

void WaylandIntegration::destroy()
{
qCDebug(gLcWayland, "Wayland device integration is about to be destroyed...");

WaylandBackend::instance()->destroy();

if (m_eglIntegration)
m_eglIntegration->destroy();

qCInfo(gLcWayland, "Wayland device integration destroyed successfully");
}

EGLNativeDisplayType WaylandIntegration::platformDisplay() const
{
return reinterpret_cast<EGLNativeDisplayType>(display());
}

EGLDisplay WaylandIntegration::eglDisplay() const
{
if (m_eglIntegration && m_eglIntegration->isInitialized())
return m_eglIntegration->display();
return EGL_NO_DISPLAY;
}

wl_display *WaylandIntegration::display() const
{
return WaylandBackend::instance()->display();
}

EGLNativeWindowType WaylandIntegration::createNativeWindow(Window *window, const QSize &size,
const QSurfaceFormat &format)
{
Q_UNUSED(format)

auto *wlWindow = static_cast<WaylandWindow *>(window);
if (!wlWindow) {
qCWarning(gLcWayland) << "Window" << window << "cannot be cast to WaylandWindow";
return 0;
}

auto *surface = wlWindow->surface()->operator wl_surface *();
if (surface) {
qCDebug(gLcWayland, "Creating native window with size %dx%d...", size.width(),
size.height());
return reinterpret_cast<EGLNativeWindowType>(
wl_egl_window_create(surface, size.width(), size.height()));
} else {
qCWarning(gLcWayland) << "Unable to get Wayland surface from window" << window;
return 0;
}

return 0;
}

void WaylandIntegration::destroyNativeWindow(EGLNativeWindowType nativeWindow)
{
if (nativeWindow) {
auto *eglWindow = reinterpret_cast<wl_egl_window *>(nativeWindow);
wl_egl_window_destroy(eglWindow);
}
}

void WaylandIntegration::waitForVSync(Window *window) const
{
}

void WaylandIntegration::presentBuffer(Window *window)
{
auto *waylandWindow = static_cast<WaylandWindow *>(window);
if (waylandWindow)
waylandWindow->present();
}

Window *WaylandIntegration::createWindow(Output *output, QWindow *qtWindow)
{
return new WaylandWindow(output, qtWindow);
}

Window *WaylandIntegration::getWindow(QWindow *qtWindow) const
{
const auto windows = WaylandBackend::instance()->windows();
for (auto *window : windows) {
if (window->qtWindow() == qtWindow)
return static_cast<Window *>(window);
}

return nullptr;
}

InputManager *WaylandIntegration::createInputManager(QObject *parent)
{
return new WaylandInputManager(parent);
}

Outputs WaylandIntegration::outputs() const
{
return WaylandBackend::instance()->outputs();
}

} // namespace Platform

} // namespace Aurora
52 changes: 52 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandintegration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <LiriAuroraPlatform/DeviceIntegration>

struct wl_display;

namespace Aurora {

namespace Platform {

class EglIntegration;
class WaylandWindow;

class WaylandIntegration : public DeviceIntegration
{
Q_OBJECT
public:
explicit WaylandIntegration(QObject *parent = nullptr);
~WaylandIntegration();

void initialize() override;
void destroy() override;

EGLNativeDisplayType platformDisplay() const override;
EGLDisplay eglDisplay() const override;

wl_display *display() const;

EGLNativeWindowType createNativeWindow(Window *window, const QSize &size,
const QSurfaceFormat &format) override;
void destroyNativeWindow(EGLNativeWindowType nativeWindow) override;

void waitForVSync(Window *window) const override;
void presentBuffer(Window *window) override;

Window *createWindow(Output *output, QWindow *qtWindow) override;
Window *getWindow(QWindow *qtWindow) const override;

InputManager *createInputManager(QObject *parent = nullptr) override;

Outputs outputs() const override;

private:
QScopedPointer<EglIntegration> m_eglIntegration;
};

} // namespace Platform

} // namespace Aurora
23 changes: 23 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandintegrationplugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// 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
24 changes: 24 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandintegrationplugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <LiriAuroraPlatform/DeviceIntegrationPlugin>

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
44 changes: 44 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandkeyboard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// 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::keymapChanged, this,
[this](int fd, quint32 size) { handleKeymapChanged(fd, size); });
connect(m_hostKeyboard, &KWayland::Client::Keyboard::keyChanged, this,
[this](quint32 key, KWayland::Client::Keyboard::KeyState state, quint32 time) {
handleKeyChanged(key,
state == KWayland::Client::Keyboard::KeyState::Pressed
? KeyState::Pressed
: KeyState::Released,
time);
});
connect(m_hostKeyboard, &KWayland::Client::Keyboard::modifiersChanged, this,
[this](quint32 depressed, quint32 latched, quint32 locked, quint32 group) {
handleModifiers(depressed, latched, locked, group);
});
connect(m_hostKeyboard, &KWayland::Client::Keyboard::keyRepeatChanged, this,
[this]() {
setKeyRepeatEnabled(true);
setKeyRepeatRate(m_hostKeyboard->keyRepeatRate());
setKeyRepeatDelay(m_hostKeyboard->keyRepeatDelay());
});
}

QString WaylandKeyboard::seatName() const
{
return m_hostSeat->name();
}

} // namespace Platform

} // namespace Aurora
29 changes: 29 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandkeyboard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <LiriAuroraPlatform/KeyboardDevice>

#include <KWayland/Client/seat.h>
#include <KWayland/Client/keyboard.h>

namespace Aurora {

namespace Platform {

class WaylandKeyboard : public KeyboardDevice
{
Q_OBJECT
public:
explicit WaylandKeyboard(KWayland::Client::Seat *hostSeat, QObject *parent = nullptr);

QString seatName() const override;

private:
KWayland::Client::Seat *const m_hostSeat = nullptr;
KWayland::Client::Keyboard *m_hostKeyboard = nullptr;

};

} // namespace Platform

} // namespace Aurora
159 changes: 159 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandoutput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <LiriAuroraPlatform/private/output_p.h>

#include "waylandbackend.h"
#include "waylandoutput.h"

namespace Aurora {

namespace Platform {

static Output::Subpixel convertSubpixel(KWayland::Client::Output::SubPixel subpixel)
{
switch (subpixel) {
case KWayland::Client::Output::SubPixel::None:
return Output::Subpixel::None;
case KWayland::Client::Output::SubPixel::HorizontalRGB:
return Output::Subpixel::HorizontalRGB;
case KWayland::Client::Output::SubPixel::HorizontalBGR:
return Output::Subpixel::HorizontalBGR;
case KWayland::Client::Output::SubPixel::VerticalRGB:
return Output::Subpixel::VerticalRGB;
case KWayland::Client::Output::SubPixel::VerticalBGR:
return Output::Subpixel::VerticalBGR;
default:
return Output::Subpixel::Unknown;
}
}

static Output::Transform convertTransform(KWayland::Client::Output::Transform transform)
{
switch (transform) {
case KWayland::Client::Output::Transform::Rotated90:
return Output::Transform::Rotated90;
case KWayland::Client::Output::Transform::Rotated180:
return Output::Transform::Rotated180;
case KWayland::Client::Output::Transform::Rotated270:
return Output::Transform::Rotated270;
case KWayland::Client::Output::Transform::Flipped:
return Output::Transform::Flipped;
case KWayland::Client::Output::Transform::Flipped90:
return Output::Transform::Flipped90;
case KWayland::Client::Output::Transform::Flipped180:
return Output::Transform::Flipped180;
case KWayland::Client::Output::Transform::Flipped270:
return Output::Transform::Flipped270;
default:
return Output::Transform::Normal;
}
}

static Output::Mode convertMode(const KWayland::Client::Output::Mode &mode)
{
Output::Mode m;

if (mode.flags.testFlag(KWayland::Client::Output::Mode::Flag::Preferred))
m.flags.setFlag(Output::Mode::Flag::Preferred);
if (mode.flags.testFlag(KWayland::Client::Output::Mode::Flag::Current))
m.flags.setFlag(Output::Mode::Flag::Current);

m.size = mode.size;
m.refreshRate = mode.refreshRate;

return m;
}

WaylandOutput::WaylandOutput(KWayland::Client::Output *output, const QString &name, QObject *parent)
: Output(parent)
, m_name(name)
, m_output(output)
{
updateInfo();

connect(output, &KWayland::Client::Output::changed, this, &WaylandOutput::updateInfo);
connect(output, &KWayland::Client::Output::modeChanged, this,
&WaylandOutput::handleModeChanged);
}

WaylandOutput::~WaylandOutput()
{
destroy();
}

QString WaylandOutput::name() const
{
return m_name;
}

QString WaylandOutput::description() const
{
return m_output ? m_output->description() : QString();
}

void WaylandOutput::destroy()
{
if (m_output) {
m_output->destroy();
m_output->deleteLater();
}
}

void WaylandOutput::updateInfo()
{
auto *d = OutputPrivate::get(this);

d->setManufacturer(m_output->manufacturer());
d->setModel(m_output->model());
d->setPhysicalSize(m_output->physicalSize());
d->setSubpixel(convertSubpixel(m_output->subPixel()));
d->setTransform(convertTransform(m_output->transform()));
d->setGlobalPosition(m_output->globalPosition());
d->setScale(m_output->scale());

if (d->modes.length() == 0) {
const auto modes = m_output->modes();
for (const auto &mode : modes) {
handleModeAdded(mode);
if (mode.flags.testFlag(KWayland::Client::Output::Mode::Flag::Current))
handleModeChanged(mode);
}
}
}

void WaylandOutput::handleModeAdded(const KWayland::Client::Output::Mode &mode)
{
auto m = convertMode(mode);

auto *d = OutputPrivate::get(this);
d->modes.append(m);

emit modeAdded(m);
}

void WaylandOutput::handleModeChanged(const KWayland::Client::Output::Mode &newMode)
{
auto *d = OutputPrivate::get(this);

QList<Output::Mode>::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
43 changes: 43 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandoutput.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <LiriAuroraPlatform/Output>

#include <KWayland/Client/output.h>

namespace Aurora {

namespace Platform {

class WaylandIntegration;

class WaylandOutput : public Output
{
Q_OBJECT
public:
explicit WaylandOutput(KWayland::Client::Output *output, const QString &name,
QObject *parent = nullptr);
~WaylandOutput();

QString name() const override;
QString description() const override;

void destroy();

protected:
KWayland::Client::Output *m_output = nullptr;

private:
QString m_name;

private slots:
void updateInfo();
void handleModeAdded(const KWayland::Client::Output::Mode &mode);
void handleModeChanged(const KWayland::Client::Output::Mode &mode);
};

} // namespace Platform

} // namespace Aurora
102 changes: 102 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandpointer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <LiriAuroraPlatform/Output>

#include <KWayland/Client/surface.h>

#include "waylandbackend.h"
#include "waylandcursor.h"
#include "waylandcursor.h"
#include "waylandoutput.h"
#include "waylandpointer.h"
#include "waylandwindow.h"

#include <linux/input-event-codes.h>

static const QHash<quint32, Qt::MouseButton> s_buttonToQtMouseButton = {
{ BTN_LEFT, Qt::LeftButton },
{ BTN_MIDDLE, Qt::MiddleButton },
{ BTN_RIGHT, Qt::RightButton },
{ BTN_SIDE, Qt::ExtraButton1 },
{ BTN_EXTRA, Qt::ExtraButton2 },
{ BTN_BACK, Qt::BackButton },
{ BTN_FORWARD, Qt::ForwardButton },
{ BTN_TASK, Qt::TaskButton },
{ 0x118, Qt::ExtraButton6 },
{ 0x119, Qt::ExtraButton7 },
{ 0x11a, Qt::ExtraButton8 },
{ 0x11b, Qt::ExtraButton9 },
{ 0x11c, Qt::ExtraButton10 },
{ 0x11d, Qt::ExtraButton11 },
{ 0x11e, Qt::ExtraButton12 },
{ 0x11f, Qt::ExtraButton13 },
};

namespace Aurora {

namespace Platform {

WaylandPointer::WaylandPointer(KWayland::Client::Seat *hostSeat, QObject *parent)
: PointerDevice(parent)
, m_hostSeat(hostSeat)
, m_hostPointer(hostSeat->createPointer(this))
{
connect(m_hostPointer, &KWayland::Client::Pointer::entered, this,
[this](quint32, const QPointF &relativeToSurface) {
m_enteredSurface = m_hostPointer->enteredSurface();

auto *wlWindow =
WaylandBackend::instance()->findWindow(m_hostPointer->enteredSurface());
if (wlWindow) {
wlWindow->cursor()->setPointer(m_hostPointer);
wlWindow->cursor()->setEnabled(true);
}
});
connect(m_hostPointer, &KWayland::Client::Pointer::left, this, [this](quint32) {
if (!m_enteredSurface.isNull()) {
auto *wlWindow = WaylandBackend::instance()->findWindow(m_enteredSurface.data());
if (wlWindow) {
wlWindow->cursor()->setPointer(nullptr);
wlWindow->cursor()->setEnabled(false);
}

m_enteredSurface.clear();
}
});
connect(m_hostPointer, &KWayland::Client::Pointer::motion, this,
[this](const QPointF &relativeToSurface, quint32) {
if (!m_enteredSurface.isNull()) {
auto *wlWindow =
WaylandBackend::instance()->findWindow(m_enteredSurface.data());
if (wlWindow) {
auto absPos = wlWindow->output()->globalPosition() + relativeToSurface;
emit motion(absPos);
}
}
});
connect(m_hostPointer, &KWayland::Client::Pointer::buttonStateChanged, this,
[this](quint32 serial, quint32, quint32 button,
KWayland::Client::Pointer::ButtonState state) {
Qt::MouseButton mouseButton = buttonToQtMouseButton(button);

if (state == KWayland::Client::Pointer::ButtonState::Pressed)
emit buttonPressed(mouseButton);
else if (state == KWayland::Client::Pointer::ButtonState::Released)
emit buttonReleased(mouseButton);
});
}

QString WaylandPointer::seatName() const
{
return m_hostSeat->name();
}

Qt::MouseButton WaylandPointer::buttonToQtMouseButton(quint32 button)
{
return s_buttonToQtMouseButton.value(button, Qt::MaxMouseButton);
}

} // namespace Platform

} // namespace Aurora
31 changes: 31 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandpointer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <LiriAuroraPlatform/PointerDevice>

#include <KWayland/Client/seat.h>
#include <KWayland/Client/pointer.h>

namespace Aurora {

namespace Platform {

class WaylandPointer : public PointerDevice
{
Q_OBJECT
public:
explicit WaylandPointer(KWayland::Client::Seat *hostSeat, QObject *parent = nullptr);

QString seatName() const override;

private:
KWayland::Client::Seat *const m_hostSeat = nullptr;
KWayland::Client::Pointer *m_hostPointer = nullptr;
QPointer<KWayland::Client::Surface> m_enteredSurface;

Qt::MouseButton buttonToQtMouseButton(quint32 button);
};

} // namespace Platform

} // namespace Aurora
24 changes: 24 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandtouch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// 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
30 changes: 30 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandtouch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QPointer>

#include <LiriAuroraPlatform/TouchDevice>

#include <KWayland/Client/seat.h>
#include <KWayland/Client/touch.h>

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
174 changes: 174 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandwindow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QScreen>

#include "waylandbackend.h"
#include "waylandcursor.h"
#include "waylandloggingcategories.h"
#include "waylandoutput.h"
#include "waylandwindow.h"

enum ResourceType {
WlSurfaceResourceType
};

static int resourceType(const QByteArray &name)
{
static const QByteArray names[] = { QByteArrayLiteral("wl_surface") };
const int numResourceTypes = sizeof(names) / sizeof(names[0]);

for (int i = 0; i < numResourceTypes; ++i) {
if (name.toLower() == names[i])
return i;
}

return -1;
}

namespace Aurora {

namespace Platform {

WaylandWindow::WaylandWindow(Output *output, QWindow *qtWindow, QObject *parent)
: Window(output, qtWindow, parent)
, m_output(static_cast<WaylandOutput *>(output))
, m_cursor(new WaylandCursor(this, this))
, m_shapeCursorSource(new Core::ShapeCursorSource(this))
{
WaylandBackend::instance()->registerWindow(this);
}

WaylandWindow::~WaylandWindow()
{
WaylandBackend::instance()->unregisterWindow(this);
destroy();
}

WaylandCursor *WaylandWindow::cursor() const
{
return m_cursor;
}

KWayland::Client::Surface *WaylandWindow::surface() const
{
return m_surface;
}

void *WaylandWindow::resource(const QByteArray &name)
{
void *result = nullptr;

switch (resourceType(name)) {
case WlSurfaceResourceType:
if (m_surface)
result = m_surface->operator wl_surface *();
break;
default:
break;
}

return result;
}

bool WaylandWindow::create()
{
// Do not create twice
if (m_surface || m_xdgToplevel)
return true;

const auto geometry = qtWindow()->screen()->geometry();
const auto size = geometry.size();

// Create a surface
m_surface = WaylandBackend::instance()->compositor()->createSurface(this);
m_surface->setSize(size);
m_surface->setOpaqueRegion(
WaylandBackend::instance()->compositor()->createRegion(QRegion(geometry)).get());
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);

// Create a toplevel window
m_xdgToplevel = WaylandBackend::instance()->xdgShell()->createSurface(m_surface, this);
connect(m_xdgToplevel, &KWayland::Client::XdgShellSurface::configureRequested, this,
[this](const QSize &size, KWayland::Client::XdgShellSurface::States states,
quint32 serial) { m_xdgToplevel->ackConfigure(serial); });
m_xdgToplevel->setTitle(QStringLiteral("Aurora Compositor"));
m_xdgToplevel->setMinSize(size);
m_xdgToplevel->setMaxSize(size);

// We don't want the compositor to draw any decoration for us
if (WaylandBackend::instance()->xdgDecorationManager()) {
m_xdgDecoration = WaylandBackend::instance()->xdgDecorationManager()->getToplevelDecoration(
m_xdgToplevel, this);
m_xdgDecoration->setMode(KWayland::Client::XdgDecoration::Mode::ClientSide);
}

// Load X cursor theme
connect(m_shapeCursorSource, &Core::CursorSource::changed, this, [this] {
m_cursor->update(m_shapeCursorSource->image(), m_shapeCursorSource->hotSpot().toPoint(),
m_output->scale());
});
m_shapeCursorSource->loadTheme(QStringLiteral("default"), 32, m_output->scale());

// Set default cursor
QCursor cursor(Qt::ArrowCursor);
changeCursor(&cursor);

return true;
}

void WaylandWindow::destroy()
{
if (m_destroyed)
return;

m_destroyed = true;

qCDebug(gLcWayland) << "Window for" << qtWindow() << "is about to be destroyed...";

if (m_xdgDecoration) {
m_xdgDecoration->release();
m_xdgDecoration->destroy();
m_xdgDecoration = nullptr;
}

if (m_xdgToplevel) {
m_xdgToplevel->release();
m_xdgToplevel->destroy();
m_xdgToplevel = nullptr;
}

if (m_surface) {
m_surface->release();
m_surface->destroy();
m_surface = nullptr;
}
}

void WaylandWindow::changeCursor(QCursor *cursor)
{
const Qt::CursorShape newShape = cursor ? cursor->shape() : Qt::ArrowCursor;
if (m_cursorShape == newShape && newShape != Qt::BitmapCursor)
return;

if (m_cursorShape == Qt::BitmapCursor)
m_cursorImage = QImage();

m_cursorShape = newShape;
if (newShape == Qt::BitmapCursor) {
// Use the custom cursor image
m_cursor->update(cursor->pixmap().toImage(), cursor->hotSpot(), m_output->scale());
} else {
// Load the image from the cursor theme
m_shapeCursorSource->setShape(newShape);
}
}

void WaylandWindow::present()
{
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
}

} // namespace Platform

} // namespace Aurora
57 changes: 57 additions & 0 deletions src/plugins/deviceintegration/wayland/waylandwindow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <LiriAuroraPlatform/Window>
#include <LiriAuroraCore/ShapeCursorSource>

#include <KWayland/Client/xdgdecoration.h>
#include <KWayland/Client/xdgshell.h>
#include <KWayland/Client/region.h>
#include <KWayland/Client/surface.h>

struct wl_egl_window;

namespace Aurora {

namespace Platform {

class WaylandCursor;

class WaylandWindow : public Window
{
Q_OBJECT
public:
explicit WaylandWindow(Output *output, QWindow *window, QObject *parent = nullptr);
~WaylandWindow();

WaylandCursor *cursor() const;

KWayland::Client::Surface *surface() const;

void *resource(const QByteArray &name) override;

bool create() override;
void destroy() override;

void changeCursor(QCursor *cursor) override;

void present();

private:
bool m_destroyed = false;
WaylandOutput *m_output = nullptr;
WaylandCursor *m_cursor = nullptr;
KWayland::Client::Surface *m_surface = nullptr;
KWayland::Client::XdgShellSurface *m_xdgToplevel = nullptr;
KWayland::Client::XdgDecoration *m_xdgDecoration = nullptr;

Core::ShapeCursorSource *m_shapeCursorSource = nullptr;
Qt::CursorShape m_cursorShape = Qt::BlankCursor;
QImage m_cursorImage;
};

} // namespace Platform

} // namespace Aurora

0 comments on commit ce60f84

Please sign in to comment.