Add kwin full source tree, greeter login, zsh, pcid service, and build system improvements

This commit is contained in:
2026-04-26 22:31:07 +01:00
parent d4a6b356eb
commit 70a84cefee
3416 changed files with 1360518 additions and 10522 deletions
@@ -0,0 +1,10 @@
target_sources(kwin PRIVATE
wayland_backend.cpp
wayland_display.cpp
wayland_egl_backend.cpp
wayland_logging.cpp
wayland_output.cpp
wayland_qpainter_backend.cpp
)
target_link_libraries(kwin PRIVATE Plasma::KWaylandClient Wayland::Client gbm::gbm)
@@ -0,0 +1,714 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_backend.h"
#include "core/drmdevice.h"
#include "input.h"
#include "wayland_display.h"
#include "wayland_egl_backend.h"
#include "wayland_logging.h"
#include "wayland_output.h"
#include "wayland_qpainter_backend.h"
#include <KWayland/Client/keyboard.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointergestures.h>
#include <KWayland/Client/relativepointer.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/touch.h>
#include <QAbstractEventDispatcher>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <gbm.h>
#include <linux/input.h>
#include <unistd.h>
#include <wayland-client-core.h>
#include "wayland-linux-dmabuf-unstable-v1-client-protocol.h"
namespace KWin
{
namespace Wayland
{
using namespace KWayland::Client;
inline static QPointF sizeToPoint(const QSizeF &size)
{
return QPointF(size.width(), size.height());
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Keyboard *keyboard, WaylandSeat *seat)
: m_seat(seat)
, m_keyboard(keyboard)
{
connect(keyboard, &Keyboard::left, this, [this](quint32 time) {
for (quint32 key : std::as_const(m_pressedKeys)) {
Q_EMIT keyChanged(key, KeyboardKeyState::Released, std::chrono::milliseconds(time), this);
}
m_pressedKeys.clear();
});
connect(keyboard, &Keyboard::keyChanged, this, [this](quint32 key, Keyboard::KeyState nativeState, quint32 time) {
KeyboardKeyState state;
switch (nativeState) {
case Keyboard::KeyState::Pressed:
if (key == KEY_RIGHTCTRL) {
m_seat->backend()->togglePointerLock();
}
state = KeyboardKeyState::Pressed;
m_pressedKeys.insert(key);
break;
case Keyboard::KeyState::Released:
m_pressedKeys.remove(key);
state = KeyboardKeyState::Released;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT keyChanged(key, state, std::chrono::milliseconds(time), this);
});
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Pointer *pointer, WaylandSeat *seat)
: m_seat(seat)
, m_pointer(pointer)
{
connect(pointer, &Pointer::entered, this, [this](quint32 serial, const QPointF &relativeToSurface) {
WaylandOutput *output = m_seat->backend()->findOutput(m_pointer->enteredSurface());
Q_ASSERT(output);
output->cursor()->setPointer(m_pointer.get());
});
connect(pointer, &Pointer::left, this, [this]() {
// wl_pointer.leave carries the wl_surface, but KWayland::Client::Pointer::left does not.
const auto outputs = m_seat->backend()->outputs();
for (Output *output : outputs) {
WaylandOutput *waylandOutput = static_cast<WaylandOutput *>(output);
if (waylandOutput->cursor()->pointer()) {
waylandOutput->cursor()->setPointer(nullptr);
}
}
});
connect(pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) {
WaylandOutput *output = m_seat->backend()->findOutput(m_pointer->enteredSurface());
Q_ASSERT(output);
const QPointF absolutePos = output->geometry().topLeft() + relativeToSurface;
Q_EMIT pointerMotionAbsolute(absolutePos, std::chrono::milliseconds(time), this);
});
connect(pointer, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState nativeState) {
PointerButtonState state;
switch (nativeState) {
case Pointer::ButtonState::Pressed:
state = PointerButtonState::Pressed;
break;
case Pointer::ButtonState::Released:
state = PointerButtonState::Released;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT pointerButtonChanged(button, state, std::chrono::milliseconds(time), this);
});
// TODO: Send discreteDelta and source as well.
connect(pointer, &Pointer::axisChanged, this, [this](quint32 time, Pointer::Axis nativeAxis, qreal delta) {
PointerAxis axis;
switch (nativeAxis) {
case Pointer::Axis::Horizontal:
axis = PointerAxis::Horizontal;
break;
case Pointer::Axis::Vertical:
axis = PointerAxis::Vertical;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT pointerAxisChanged(axis, delta, 0, PointerAxisSource::Unknown, false, std::chrono::milliseconds(time), this);
});
connect(pointer, &Pointer::frame, this, [this]() {
Q_EMIT pointerFrame(this);
});
KWayland::Client::PointerGestures *pointerGestures = m_seat->backend()->display()->pointerGestures();
if (pointerGestures) {
m_pinchGesture.reset(pointerGestures->createPinchGesture(m_pointer.get(), this));
connect(m_pinchGesture.get(), &PointerPinchGesture::started, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureBegin(m_pinchGesture->fingerCount(), std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::updated, this, [this](const QSizeF &delta, qreal scale, qreal rotation, quint32 time) {
Q_EMIT pinchGestureUpdate(scale, rotation, sizeToPoint(delta), std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::ended, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureEnd(std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::cancelled, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureCancelled(std::chrono::milliseconds(time), this);
});
m_swipeGesture.reset(pointerGestures->createSwipeGesture(m_pointer.get(), this));
connect(m_swipeGesture.get(), &PointerSwipeGesture::started, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureBegin(m_swipeGesture->fingerCount(), std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::updated, this, [this](const QSizeF &delta, quint32 time) {
Q_EMIT swipeGestureUpdate(sizeToPoint(delta), std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::ended, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureEnd(std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::cancelled, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureCancelled(std::chrono::milliseconds(time), this);
});
}
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::RelativePointer *relativePointer, WaylandSeat *seat)
: m_seat(seat)
, m_relativePointer(relativePointer)
{
connect(relativePointer, &RelativePointer::relativeMotion, this, [this](const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp) {
Q_EMIT pointerMotion(sizeToPoint(delta), sizeToPoint(deltaNonAccelerated), std::chrono::microseconds(timestamp), this);
});
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Touch *touch, WaylandSeat *seat)
: m_seat(seat)
, m_touch(touch)
{
connect(touch, &Touch::sequenceCanceled, this, [this]() {
Q_EMIT touchCanceled(this);
});
connect(touch, &Touch::frameEnded, this, [this]() {
Q_EMIT touchFrame(this);
});
connect(touch, &Touch::sequenceStarted, this, [this](TouchPoint *tp) {
auto o = m_seat->backend()->findOutput(tp->surface());
Q_ASSERT(o);
const QPointF position = o->geometry().topLeft() + tp->position();
Q_EMIT touchDown(tp->id(), position, std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointAdded, this, [this](TouchPoint *tp) {
auto o = m_seat->backend()->findOutput(tp->surface());
Q_ASSERT(o);
const QPointF position = o->geometry().topLeft() + tp->position();
Q_EMIT touchDown(tp->id(), position, std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointRemoved, this, [this](TouchPoint *tp) {
Q_EMIT touchUp(tp->id(), std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointMoved, this, [this](TouchPoint *tp) {
auto o = m_seat->backend()->findOutput(tp->surface());
Q_ASSERT(o);
const QPointF position = o->geometry().topLeft() + tp->position();
Q_EMIT touchMotion(tp->id(), position, std::chrono::milliseconds(tp->time()), this);
});
}
WaylandInputDevice::~WaylandInputDevice()
{
}
QString WaylandInputDevice::name() const
{
return QString();
}
bool WaylandInputDevice::isEnabled() const
{
return true;
}
void WaylandInputDevice::setEnabled(bool enabled)
{
}
bool WaylandInputDevice::isKeyboard() const
{
return m_keyboard != nullptr;
}
bool WaylandInputDevice::isPointer() const
{
return m_pointer || m_relativePointer;
}
bool WaylandInputDevice::isTouchpad() const
{
return false;
}
bool WaylandInputDevice::isTouch() const
{
return m_touch != nullptr;
}
bool WaylandInputDevice::isTabletTool() const
{
return false;
}
bool WaylandInputDevice::isTabletPad() const
{
return false;
}
bool WaylandInputDevice::isTabletModeSwitch() const
{
return false;
}
bool WaylandInputDevice::isLidSwitch() const
{
return false;
}
KWayland::Client::Pointer *WaylandInputDevice::nativePointer() const
{
return m_pointer.get();
}
WaylandInputBackend::WaylandInputBackend(WaylandBackend *backend, QObject *parent)
: InputBackend(parent)
, m_backend(backend)
{
}
void WaylandInputBackend::initialize()
{
WaylandSeat *seat = m_backend->seat();
if (seat->relativePointerDevice()) {
Q_EMIT deviceAdded(seat->relativePointerDevice());
}
if (seat->pointerDevice()) {
Q_EMIT deviceAdded(seat->pointerDevice());
}
if (seat->keyboardDevice()) {
Q_EMIT deviceAdded(seat->keyboardDevice());
}
if (seat->touchDevice()) {
Q_EMIT deviceAdded(seat->touchDevice());
}
connect(seat, &WaylandSeat::deviceAdded, this, &InputBackend::deviceAdded);
connect(seat, &WaylandSeat::deviceRemoved, this, &InputBackend::deviceRemoved);
}
WaylandSeat::WaylandSeat(KWayland::Client::Seat *nativeSeat, WaylandBackend *backend)
: QObject(nullptr)
, m_seat(nativeSeat)
, m_backend(backend)
{
auto updateKeyboardDevice = [this](){
if (m_seat->hasKeyboard()) {
createKeyboardDevice();
} else {
destroyKeyboardDevice();
}
};
updateKeyboardDevice();
connect(m_seat, &Seat::hasKeyboardChanged, this, updateKeyboardDevice);
auto updatePointerDevice = [this]() {
if (m_seat->hasPointer()) {
createPointerDevice();
} else {
destroyPointerDevice();
}
};
updatePointerDevice();
connect(m_seat, &Seat::hasPointerChanged, this, updatePointerDevice);
auto updateTouchDevice = [this]() {
if (m_seat->hasTouch()) {
createTouchDevice();
} else {
destroyTouchDevice();
}
};
updateTouchDevice();
connect(m_seat, &Seat::hasTouchChanged, this, updateTouchDevice);
}
WaylandSeat::~WaylandSeat()
{
destroyPointerDevice();
destroyKeyboardDevice();
destroyTouchDevice();
}
void WaylandSeat::createPointerDevice()
{
m_pointerDevice = std::make_unique<WaylandInputDevice>(m_seat->createPointer(), this);
Q_EMIT deviceAdded(m_pointerDevice.get());
}
void WaylandSeat::destroyPointerDevice()
{
if (m_pointerDevice) {
Q_EMIT deviceRemoved(m_pointerDevice.get());
destroyRelativePointer();
m_pointerDevice.reset();
}
}
void WaylandSeat::createRelativePointer()
{
KWayland::Client::RelativePointerManager *manager = m_backend->display()->relativePointerManager();
if (manager) {
m_relativePointerDevice = std::make_unique<WaylandInputDevice>(manager->createRelativePointer(m_pointerDevice->nativePointer()), this);
Q_EMIT deviceAdded(m_relativePointerDevice.get());
}
}
void WaylandSeat::destroyRelativePointer()
{
if (m_relativePointerDevice) {
Q_EMIT deviceRemoved(m_relativePointerDevice.get());
m_relativePointerDevice.reset();
}
}
void WaylandSeat::createKeyboardDevice()
{
m_keyboardDevice = std::make_unique<WaylandInputDevice>(m_seat->createKeyboard(), this);
Q_EMIT deviceAdded(m_keyboardDevice.get());
}
void WaylandSeat::destroyKeyboardDevice()
{
if (m_keyboardDevice) {
Q_EMIT deviceRemoved(m_keyboardDevice.get());
m_keyboardDevice.reset();
}
}
void WaylandSeat::createTouchDevice()
{
m_touchDevice = std::make_unique<WaylandInputDevice>(m_seat->createTouch(), this);
Q_EMIT deviceAdded(m_touchDevice.get());
}
void WaylandSeat::destroyTouchDevice()
{
if (m_touchDevice) {
Q_EMIT deviceRemoved(m_touchDevice.get());
m_touchDevice.reset();
}
}
WaylandBackend::WaylandBackend(const WaylandBackendOptions &options, QObject *parent)
: OutputBackend(parent)
, m_options(options)
{
}
WaylandBackend::~WaylandBackend()
{
m_eglDisplay.reset();
destroyOutputs();
m_buffers.clear();
m_seat.reset();
m_display.reset();
qCDebug(KWIN_WAYLAND_BACKEND) << "Destroyed Wayland display";
}
bool WaylandBackend::initialize()
{
m_display = std::make_unique<WaylandDisplay>();
if (!m_display->initialize(m_options.socketName)) {
return false;
}
if (WaylandLinuxDmabufV1 *dmabuf = m_display->linuxDmabuf()) {
m_drmDevice = DrmDevice::open(dmabuf->mainDevice());
if (!m_drmDevice) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Failed to open drm render node" << dmabuf->mainDevice();
}
}
createOutputs();
m_seat = std::make_unique<WaylandSeat>(m_display->seat(), this);
QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance();
QObject::connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, m_display.get(), &WaylandDisplay::flush);
QObject::connect(dispatcher, &QAbstractEventDispatcher::awake, m_display.get(), &WaylandDisplay::flush);
connect(this, &WaylandBackend::pointerLockChanged, this, [this](bool locked) {
if (locked) {
m_seat->createRelativePointer();
} else {
m_seat->destroyRelativePointer();
}
});
return true;
}
void WaylandBackend::createOutputs()
{
// we need to multiply the initial window size with the scale in order to
// create an output window of this size in the end
const QSize pixelSize = m_options.outputSize * m_options.outputScale;
for (int i = 0; i < m_options.outputCount; i++) {
WaylandOutput *output = createOutput(QStringLiteral("WL-%1").arg(i), pixelSize, m_options.outputScale);
m_outputs << output;
Q_EMIT outputAdded(output);
output->updateEnabled(true);
}
Q_EMIT outputsQueried();
}
WaylandOutput *WaylandBackend::createOutput(const QString &name, const QSize &size, qreal scale)
{
WaylandOutput *waylandOutput = new WaylandOutput(name, this);
waylandOutput->init(size, scale);
// Wait until the output window is configured by the host compositor.
while (!waylandOutput->isReady()) {
wl_display_roundtrip(m_display->nativeDisplay());
}
return waylandOutput;
}
void WaylandBackend::destroyOutputs()
{
while (!m_outputs.isEmpty()) {
WaylandOutput *output = m_outputs.takeLast();
output->updateEnabled(false);
Q_EMIT outputRemoved(output);
delete output;
}
}
std::unique_ptr<InputBackend> WaylandBackend::createInputBackend()
{
return std::make_unique<WaylandInputBackend>(this);
}
std::unique_ptr<OpenGLBackend> WaylandBackend::createOpenGLBackend()
{
return std::make_unique<WaylandEglBackend>(this);
}
std::unique_ptr<QPainterBackend> WaylandBackend::createQPainterBackend()
{
return std::make_unique<WaylandQPainterBackend>(this);
}
WaylandOutput *WaylandBackend::findOutput(KWayland::Client::Surface *nativeSurface) const
{
for (WaylandOutput *output : m_outputs) {
if (output->surface() == nativeSurface) {
return output;
}
}
return nullptr;
}
bool WaylandBackend::supportsPointerLock()
{
return m_display->pointerConstraints() && m_display->relativePointerManager();
}
void WaylandBackend::togglePointerLock()
{
if (!supportsPointerLock()) {
return;
}
if (!m_seat) {
return;
}
auto pointer = m_seat->pointerDevice()->nativePointer();
if (!pointer) {
return;
}
if (m_outputs.isEmpty()) {
return;
}
for (auto output : std::as_const(m_outputs)) {
output->lockPointer(m_seat->pointerDevice()->nativePointer(), !m_pointerLockRequested);
}
m_pointerLockRequested = !m_pointerLockRequested;
}
QList<CompositingType> WaylandBackend::supportedCompositors() const
{
QList<CompositingType> ret;
if (m_display->linuxDmabuf() && m_drmDevice) {
ret.append(OpenGLCompositing);
}
ret.append(QPainterCompositing);
return ret;
}
Outputs WaylandBackend::outputs() const
{
return m_outputs;
}
Output *WaylandBackend::createVirtualOutput(const QString &name, const QString &description, const QSize &size, double scale)
{
return createOutput(name, size * scale, scale);
}
void WaylandBackend::removeVirtualOutput(Output *output)
{
WaylandOutput *waylandOutput = dynamic_cast<WaylandOutput *>(output);
if (waylandOutput && m_outputs.removeAll(waylandOutput)) {
waylandOutput->updateEnabled(false);
Q_EMIT outputRemoved(waylandOutput);
Q_EMIT outputsQueried();
waylandOutput->unref();
}
}
static wl_buffer *importDmaBufBuffer(WaylandDisplay *display, const DmaBufAttributes *attributes)
{
zwp_linux_buffer_params_v1 *params = zwp_linux_dmabuf_v1_create_params(display->linuxDmabuf()->handle());
for (int i = 0; i < attributes->planeCount; ++i) {
zwp_linux_buffer_params_v1_add(params,
attributes->fd[i].get(),
i,
attributes->offset[i],
attributes->pitch[i],
attributes->modifier >> 32,
attributes->modifier & 0xffffffff);
}
wl_buffer *buffer = zwp_linux_buffer_params_v1_create_immed(params, attributes->width, attributes->height, attributes->format, 0);
zwp_linux_buffer_params_v1_destroy(params);
return buffer;
}
static wl_buffer *importShmBuffer(WaylandDisplay *display, const ShmAttributes *attributes)
{
wl_shm_format format;
switch (attributes->format) {
case DRM_FORMAT_ARGB8888:
format = WL_SHM_FORMAT_ARGB8888;
break;
case DRM_FORMAT_XRGB8888:
format = WL_SHM_FORMAT_XRGB8888;
break;
default:
format = static_cast<wl_shm_format>(attributes->format);
break;
}
wl_shm_pool *pool = wl_shm_create_pool(display->shm(), attributes->fd.get(), attributes->size.height() * attributes->stride);
wl_buffer *buffer = wl_shm_pool_create_buffer(pool,
attributes->offset,
attributes->size.width(),
attributes->size.height(),
attributes->stride,
format);
wl_shm_pool_destroy(pool);
return buffer;
}
wl_buffer *WaylandBackend::importBuffer(GraphicsBuffer *graphicsBuffer)
{
auto &buffer = m_buffers[graphicsBuffer];
if (!buffer) {
wl_buffer *handle = nullptr;
if (const DmaBufAttributes *attributes = graphicsBuffer->dmabufAttributes()) {
handle = importDmaBufBuffer(m_display.get(), attributes);
} else if (const ShmAttributes *attributes = graphicsBuffer->shmAttributes()) {
handle = importShmBuffer(m_display.get(), attributes);
} else {
qCWarning(KWIN_WAYLAND_BACKEND) << graphicsBuffer << "has unknown type";
return nullptr;
}
buffer = std::make_unique<WaylandBuffer>(handle, graphicsBuffer);
connect(buffer.get(), &WaylandBuffer::defunct, this, [this, graphicsBuffer]() {
m_buffers.erase(graphicsBuffer);
});
static const wl_buffer_listener listener = {
.release = [](void *userData, wl_buffer *buffer) {
WaylandBuffer *slot = static_cast<WaylandBuffer *>(userData);
slot->unlock();
},
};
wl_buffer_add_listener(handle, &listener, buffer.get());
}
buffer->lock();
return buffer->handle();
}
void WaylandBackend::setEglDisplay(std::unique_ptr<EglDisplay> &&display)
{
m_eglDisplay = std::move(display);
}
EglDisplay *WaylandBackend::sceneEglDisplayObject() const
{
return m_eglDisplay.get();
}
DrmDevice *WaylandBackend::drmDevice() const
{
return m_drmDevice.get();
}
WaylandBuffer::WaylandBuffer(wl_buffer *handle, GraphicsBuffer *graphicsBuffer)
: m_graphicsBuffer(graphicsBuffer)
, m_handle(handle)
{
connect(graphicsBuffer, &GraphicsBuffer::destroyed, this, &WaylandBuffer::defunct);
}
WaylandBuffer::~WaylandBuffer()
{
m_graphicsBuffer->disconnect(this);
if (m_locked) {
m_graphicsBuffer->unref();
}
wl_buffer_destroy(m_handle);
}
wl_buffer *WaylandBuffer::handle() const
{
return m_handle;
}
void WaylandBuffer::lock()
{
if (!m_locked) {
m_locked = true;
m_graphicsBuffer->ref();
}
}
void WaylandBuffer::unlock()
{
if (m_locked) {
m_locked = false;
m_graphicsBuffer->unref();
}
}
}
} // KWin
#include "moc_wayland_backend.cpp"
@@ -0,0 +1,269 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "config-kwin.h"
// KWin
#include "core/inputbackend.h"
#include "core/inputdevice.h"
#include "core/outputbackend.h"
#include "effect/globals.h"
#include "utils/filedescriptor.h"
// Qt
#include <QHash>
#include <QImage>
#include <QObject>
#include <QPoint>
#include <QSize>
struct wl_buffer;
struct wl_display;
namespace KWayland
{
namespace Client
{
class Keyboard;
class Pointer;
class PointerSwipeGesture;
class PointerPinchGesture;
class RelativePointer;
class Seat;
class Surface;
class Touch;
}
}
namespace KWin
{
class GraphicsBuffer;
class DrmDevice;
namespace Wayland
{
class WaylandBackend;
class WaylandSeat;
class WaylandOutput;
class WaylandEglBackend;
class WaylandDisplay;
class WaylandInputDevice : public InputDevice
{
Q_OBJECT
public:
WaylandInputDevice(KWayland::Client::Touch *touch, WaylandSeat *seat);
WaylandInputDevice(KWayland::Client::Keyboard *keyboard, WaylandSeat *seat);
WaylandInputDevice(KWayland::Client::RelativePointer *relativePointer, WaylandSeat *seat);
WaylandInputDevice(KWayland::Client::Pointer *pointer, WaylandSeat *seat);
~WaylandInputDevice() override;
QString name() const override;
bool isEnabled() const override;
void setEnabled(bool enabled) override;
bool isKeyboard() const override;
bool isPointer() const override;
bool isTouchpad() const override;
bool isTouch() const override;
bool isTabletTool() const override;
bool isTabletPad() const override;
bool isTabletModeSwitch() const override;
bool isLidSwitch() const override;
KWayland::Client::Pointer *nativePointer() const;
private:
WaylandSeat *const m_seat;
std::unique_ptr<KWayland::Client::Keyboard> m_keyboard;
std::unique_ptr<KWayland::Client::Touch> m_touch;
std::unique_ptr<KWayland::Client::RelativePointer> m_relativePointer;
std::unique_ptr<KWayland::Client::Pointer> m_pointer;
std::unique_ptr<KWayland::Client::PointerPinchGesture> m_pinchGesture;
std::unique_ptr<KWayland::Client::PointerSwipeGesture> m_swipeGesture;
QSet<quint32> m_pressedKeys;
};
class WaylandInputBackend : public InputBackend
{
Q_OBJECT
public:
explicit WaylandInputBackend(WaylandBackend *backend, QObject *parent = nullptr);
void initialize() override;
private:
WaylandBackend *m_backend;
};
class WaylandSeat : public QObject
{
Q_OBJECT
public:
WaylandSeat(KWayland::Client::Seat *nativeSeat, WaylandBackend *backend);
~WaylandSeat() override;
WaylandBackend *backend() const
{
return m_backend;
}
WaylandInputDevice *pointerDevice() const
{
return m_pointerDevice.get();
}
WaylandInputDevice *relativePointerDevice() const
{
return m_relativePointerDevice.get();
}
WaylandInputDevice *keyboardDevice() const
{
return m_keyboardDevice.get();
}
WaylandInputDevice *touchDevice() const
{
return m_touchDevice.get();
}
void createRelativePointer();
void destroyRelativePointer();
Q_SIGNALS:
void deviceAdded(WaylandInputDevice *device);
void deviceRemoved(WaylandInputDevice *device);
private:
void createPointerDevice();
void destroyPointerDevice();
void createKeyboardDevice();
void destroyKeyboardDevice();
void createTouchDevice();
void destroyTouchDevice();
KWayland::Client::Seat *m_seat;
WaylandBackend *m_backend;
std::unique_ptr<WaylandInputDevice> m_pointerDevice;
std::unique_ptr<WaylandInputDevice> m_relativePointerDevice;
std::unique_ptr<WaylandInputDevice> m_keyboardDevice;
std::unique_ptr<WaylandInputDevice> m_touchDevice;
};
class WaylandBuffer : public QObject
{
Q_OBJECT
public:
WaylandBuffer(wl_buffer *handle, GraphicsBuffer *graphicsBuffer);
~WaylandBuffer() override;
wl_buffer *handle() const;
void lock();
void unlock();
Q_SIGNALS:
void defunct();
private:
GraphicsBuffer *m_graphicsBuffer;
wl_buffer *m_handle;
bool m_locked = false;
};
struct WaylandBackendOptions
{
QString socketName;
int outputCount = 1;
qreal outputScale = 1;
QSize outputSize = QSize(1024, 768);
};
/**
* @brief Class encapsulating all Wayland data structures needed by the Egl backend.
*
* It creates the connection to the Wayland Compositor, sets up the registry and creates
* the Wayland output surfaces and its shell mappings.
*/
class KWIN_EXPORT WaylandBackend : public OutputBackend
{
Q_OBJECT
public:
explicit WaylandBackend(const WaylandBackendOptions &options, QObject *parent = nullptr);
~WaylandBackend() override;
bool initialize() override;
std::unique_ptr<InputBackend> createInputBackend() override;
std::unique_ptr<OpenGLBackend> createOpenGLBackend() override;
std::unique_ptr<QPainterBackend> createQPainterBackend() override;
WaylandDisplay *display() const
{
return m_display.get();
}
WaylandSeat *seat() const
{
return m_seat.get();
}
bool supportsPointerLock();
void togglePointerLock();
QList<CompositingType> supportedCompositors() const override;
WaylandOutput *findOutput(KWayland::Client::Surface *nativeSurface) const;
Outputs outputs() const override;
QList<WaylandOutput *> waylandOutputs() const
{
return m_outputs;
}
Output *createVirtualOutput(const QString &name, const QString &description, const QSize &size, double scale) override;
void removeVirtualOutput(Output *output) override;
wl_buffer *importBuffer(GraphicsBuffer *graphicsBuffer);
DrmDevice *drmDevice() const;
void setEglBackend(WaylandEglBackend *eglBackend)
{
m_eglBackend = eglBackend;
}
void setEglDisplay(std::unique_ptr<EglDisplay> &&display);
EglDisplay *sceneEglDisplayObject() const override;
Q_SIGNALS:
void pointerLockChanged(bool locked);
private:
void createOutputs();
void destroyOutputs();
WaylandOutput *createOutput(const QString &name, const QSize &size, qreal scale);
WaylandBackendOptions m_options;
std::unique_ptr<WaylandDisplay> m_display;
std::unique_ptr<WaylandSeat> m_seat;
WaylandEglBackend *m_eglBackend = nullptr;
QList<WaylandOutput *> m_outputs;
bool m_pointerLockRequested = false;
std::unique_ptr<DrmDevice> m_drmDevice;
std::unique_ptr<EglDisplay> m_eglDisplay;
std::map<GraphicsBuffer *, std::unique_ptr<WaylandBuffer>> m_buffers;
};
} // namespace Wayland
} // namespace KWin
@@ -0,0 +1,482 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_display.h"
#include "utils/memorymap.h"
#include "wayland_logging.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/pointerconstraints.h>
#include <KWayland/Client/pointergestures.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/relativepointer.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/subcompositor.h>
#include <KWayland/Client/xdgdecoration.h>
#include <KWayland/Client/xdgshell.h>
#include <QMutex>
#include <QThread>
#include <QWaitCondition>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <poll.h>
#include <span>
#include <unistd.h>
#include <wayland-client.h>
#include <xf86drm.h>
// Generated in src/wayland.
#include "wayland-linux-dmabuf-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-pointer-gestures-unstable-v1-server-protocol.h"
#include "wayland-presentation-time-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "wayland-xdg-shell-client-protocol.h"
namespace KWin
{
namespace Wayland
{
class WaylandEventThread : public QThread
{
Q_OBJECT
public:
WaylandEventThread(wl_display *display)
: m_display(display)
, m_fd(wl_display_get_fd(display))
, m_quitPipe{-1, -1}
, m_reading(true)
, m_quitting(false)
{
if (pipe2(m_quitPipe, O_CLOEXEC) == -1) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Failed to create quite pipe in WaylandEventThread";
}
}
~WaylandEventThread() override
{
if (m_quitPipe[0] != -1) {
close(m_quitPipe[0]);
close(m_quitPipe[1]);
}
}
void dispatch()
{
while (true) {
if (wl_display_dispatch_pending(m_display) < 0) {
qFatal("Wayland connection broke");
}
wl_display_flush(m_display);
if (m_reading.loadAcquire()) {
break;
}
if (wl_display_prepare_read(m_display) == 0) {
QMutexLocker lock(&m_mutex);
m_reading.storeRelease(true);
m_cond.wakeOne();
break;
}
}
}
void stop()
{
if (m_quitPipe[1] != -1) {
write(m_quitPipe[1], "\0", 1);
}
m_mutex.lock();
m_quitting = true;
m_cond.wakeOne();
m_mutex.unlock();
wait();
}
Q_SIGNALS:
void available();
protected:
void run() override
{
while (true) {
m_reading.storeRelease(false);
Q_EMIT available();
m_mutex.lock();
while (!m_reading.loadRelaxed() && !m_quitting) {
m_cond.wait(&m_mutex);
}
m_mutex.unlock();
if (m_quitting) {
break;
}
pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_quitPipe[0], POLLIN, 0 } };
poll(fds, 2, -1);
if (fds[1].revents & POLLIN) {
wl_display_cancel_read(m_display);
break;
}
if (fds[0].revents & POLLIN) {
wl_display_read_events(m_display);
} else {
wl_display_cancel_read(m_display);
}
}
}
private:
wl_display *const m_display;
int m_fd;
int m_quitPipe[2];
QAtomicInteger<bool> m_reading;
QMutex m_mutex;
QWaitCondition m_cond;
bool m_quitting;
};
static dev_t deserializeDeviceId(wl_array *data)
{
Q_ASSERT(sizeof(dev_t) == data->size);
dev_t ret;
std::memcpy(&ret, data->data, data->size);
return ret;
}
class WaylandLinuxDmabufFeedbackV1
{
public:
WaylandLinuxDmabufFeedbackV1(zwp_linux_dmabuf_feedback_v1 *feedback)
: feedback(feedback)
{
static const struct zwp_linux_dmabuf_feedback_v1_listener feedbackListener = {
.done = done,
.format_table = format_table,
.main_device = main_device,
.tranche_done = tranche_done,
.tranche_target_device = tranche_target_device,
.tranche_formats = tranche_formats,
.tranche_flags = tranche_flags,
};
zwp_linux_dmabuf_feedback_v1_add_listener(feedback, &feedbackListener, this);
}
~WaylandLinuxDmabufFeedbackV1()
{
zwp_linux_dmabuf_feedback_v1_destroy(feedback);
}
zwp_linux_dmabuf_feedback_v1 *feedback;
QByteArray mainDevice;
dev_t mainDeviceId = 0;
dev_t trancheDeviceId = 0;
MemoryMap formatTable;
QHash<uint32_t, QList<uint64_t>> formats;
private:
static void done(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1)
{
// Nothing to do
}
static void format_table(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, int32_t fd, uint32_t size)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->formatTable = MemoryMap(size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
}
static void main_device(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, wl_array *deviceId)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->mainDeviceId = deserializeDeviceId(deviceId);
drmDevice *device = nullptr;
if (drmGetDeviceFromDevId(feedback->mainDeviceId, 0, &device) != 0) {
qCWarning(KWIN_WAYLAND_BACKEND) << "drmGetDeviceFromDevId() failed";
return;
}
if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
feedback->mainDevice = QByteArray(device->nodes[DRM_NODE_RENDER]);
} else if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) {
// We can't reliably find the render node from the primary node if the display and
// render devices are split, so just fallback to the primary node.
feedback->mainDevice = QByteArray(device->nodes[DRM_NODE_PRIMARY]);
}
drmFreeDevice(&device);
}
static void tranche_done(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->trancheDeviceId = 0;
}
static void tranche_target_device(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, wl_array *deviceId)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
feedback->trancheDeviceId = deserializeDeviceId(deviceId);
}
static void tranche_formats(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, wl_array *indices)
{
WaylandLinuxDmabufFeedbackV1 *feedback = static_cast<WaylandLinuxDmabufFeedbackV1 *>(data);
if (!feedback->formatTable.isValid()) {
return;
}
if (feedback->mainDeviceId != feedback->trancheDeviceId) {
return;
}
struct linux_dmabuf_feedback_v1_table_entry
{
uint32_t format;
uint32_t pad; // unused
uint64_t modifier;
};
const auto entries = static_cast<linux_dmabuf_feedback_v1_table_entry *>(feedback->formatTable.data());
for (const uint16_t &index : std::span(static_cast<uint16_t *>(indices->data), indices->size / sizeof(uint16_t))) {
const linux_dmabuf_feedback_v1_table_entry &entry = entries[index];
feedback->formats[entry.format].append(entry.modifier);
}
}
static void tranche_flags(void *data, zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, uint32_t flags)
{
// Nothing to do
}
};
WaylandLinuxDmabufV1::WaylandLinuxDmabufV1(wl_registry *registry, uint32_t name, uint32_t version)
{
m_dmabuf = static_cast<zwp_linux_dmabuf_v1 *>(wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version));
static const struct zwp_linux_dmabuf_v1_listener dmabufListener = {
.format = format,
.modifier = modifier,
};
zwp_linux_dmabuf_v1_add_listener(m_dmabuf, &dmabufListener, this);
m_defaultFeedback = std::make_unique<WaylandLinuxDmabufFeedbackV1>(zwp_linux_dmabuf_v1_get_default_feedback(m_dmabuf));
}
WaylandLinuxDmabufV1::~WaylandLinuxDmabufV1()
{
zwp_linux_dmabuf_v1_destroy(m_dmabuf);
}
zwp_linux_dmabuf_v1 *WaylandLinuxDmabufV1::handle() const
{
return m_dmabuf;
}
QByteArray WaylandLinuxDmabufV1::mainDevice() const
{
return m_defaultFeedback->mainDevice;
}
QHash<uint32_t, QList<uint64_t>> WaylandLinuxDmabufV1::formats() const
{
return m_defaultFeedback->formats;
}
void WaylandLinuxDmabufV1::format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format)
{
// Not sent in v4 and onward.
}
void WaylandLinuxDmabufV1::modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo)
{
// Not sent in v4 and onward.
}
WaylandDisplay::WaylandDisplay()
{
}
WaylandDisplay::~WaylandDisplay()
{
m_eventThread->stop();
m_eventThread.reset();
m_compositor.reset();
m_pointerConstraints.reset();
m_pointerGestures.reset();
m_relativePointerManager.reset();
m_seat.reset();
m_xdgDecorationManager.reset();
m_xdgShell.reset();
m_linuxDmabuf.reset();
if (m_shm) {
wl_shm_destroy(m_shm);
}
if (m_presentationTime) {
wp_presentation_destroy(m_presentationTime);
}
if (m_registry) {
wl_registry_destroy(m_registry);
}
if (m_display) {
wl_display_disconnect(m_display);
}
}
void WaylandDisplay::flush()
{
m_eventThread->dispatch();
}
bool WaylandDisplay::initialize(const QString &socketName)
{
m_display = wl_display_connect(socketName.toUtf8());
if (!m_display) {
return false;
}
m_eventThread = std::make_unique<WaylandEventThread>(m_display);
connect(m_eventThread.get(), &WaylandEventThread::available, this, &WaylandDisplay::flush, Qt::QueuedConnection);
m_eventThread->start();
static wl_registry_listener registryListener {
.global = registry_global,
.global_remove = registry_global_remove,
};
m_registry = wl_display_get_registry(m_display);
wl_registry_add_listener(m_registry, &registryListener, this);
wl_display_roundtrip(m_display);
wl_display_roundtrip(m_display); // get dmabuf formats
return true;
}
wl_display *WaylandDisplay::nativeDisplay() const
{
return m_display;
}
KWayland::Client::Compositor *WaylandDisplay::compositor() const
{
return m_compositor.get();
}
KWayland::Client::PointerConstraints *WaylandDisplay::pointerConstraints() const
{
return m_pointerConstraints.get();
}
KWayland::Client::PointerGestures *WaylandDisplay::pointerGestures() const
{
return m_pointerGestures.get();
}
KWayland::Client::RelativePointerManager *WaylandDisplay::relativePointerManager() const
{
return m_relativePointerManager.get();
}
wl_shm *WaylandDisplay::shm() const
{
return m_shm;
}
KWayland::Client::Seat *WaylandDisplay::seat() const
{
return m_seat.get();
}
KWayland::Client::XdgShell *WaylandDisplay::xdgShell() const
{
return m_xdgShell.get();
}
KWayland::Client::XdgDecorationManager *WaylandDisplay::xdgDecorationManager() const
{
return m_xdgDecorationManager.get();
}
WaylandLinuxDmabufV1 *WaylandDisplay::linuxDmabuf() const
{
return m_linuxDmabuf.get();
}
wp_presentation *WaylandDisplay::presentationTime() const
{
return m_presentationTime;
}
void WaylandDisplay::registry_global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
{
WaylandDisplay *display = static_cast<WaylandDisplay *>(data);
if (strcmp(interface, wl_compositor_interface.name) == 0) {
if (version < 4) {
qFatal("wl_compositor version 4 or later is required");
}
display->m_compositor = std::make_unique<KWayland::Client::Compositor>();
display->m_compositor->setup(static_cast<wl_compositor *>(wl_registry_bind(registry, name, &wl_compositor_interface, std::min(version, 4u))));
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
display->m_shm = static_cast<wl_shm *>(wl_registry_bind(registry, name, &wl_shm_interface, std::min(version, 1u)));
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
display->m_seat = std::make_unique<KWayland::Client::Seat>();
display->m_seat->setup(static_cast<wl_seat *>(wl_registry_bind(registry, name, &wl_seat_interface, std::min(version, 5u))));
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
display->m_xdgShell = std::make_unique<KWayland::Client::XdgShellStable>();
display->m_xdgShell->setup(static_cast<xdg_wm_base *>(wl_registry_bind(registry, name, &xdg_wm_base_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) {
display->m_pointerConstraints = std::make_unique<KWayland::Client::PointerConstraints>();
display->m_pointerConstraints->setup(static_cast<zwp_pointer_constraints_v1 *>(wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) {
display->m_pointerGestures = std::make_unique<KWayland::Client::PointerGestures>();
display->m_pointerGestures->setup(static_cast<zwp_pointer_gestures_v1 *>(wl_registry_bind(registry, name, &zwp_pointer_gestures_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) {
display->m_relativePointerManager = std::make_unique<KWayland::Client::RelativePointerManager>();
display->m_relativePointerManager->setup(static_cast<zwp_relative_pointer_manager_v1 *>(wl_registry_bind(registry, name, &zwp_relative_pointer_manager_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
display->m_xdgDecorationManager = std::make_unique<KWayland::Client::XdgDecorationManager>();
display->m_xdgDecorationManager->setup(static_cast<zxdg_decoration_manager_v1 *>(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, std::min(version, 1u))));
} else if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) {
if (version < 4) {
qWarning("zwp_linux_dmabuf_v1 v4 or newer is needed");
return;
}
display->m_linuxDmabuf = std::make_unique<WaylandLinuxDmabufV1>(registry, name, std::min(version, 4u));
} else if (strcmp(interface, wp_presentation_interface.name) == 0) {
display->m_presentationTime = reinterpret_cast<wp_presentation *>(wl_registry_bind(registry, name, &wp_presentation_interface, std::min(version, 2u)));
}
}
void WaylandDisplay::registry_global_remove(void *data, wl_registry *registry, uint32_t name)
{
}
}
}
#include "wayland_display.moc"
#include "moc_wayland_display.cpp"
@@ -0,0 +1,105 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QHash>
#include <QObject>
#include <memory>
struct wl_display;
struct wl_registry;
struct wl_shm;
struct zwp_linux_dmabuf_v1;
struct wp_presentation;
namespace KWayland
{
namespace Client
{
class Compositor;
class PointerConstraints;
class PointerGestures;
class RelativePointerManager;
class Seat;
class XdgDecorationManager;
class XdgShell;
}
}
namespace KWin
{
namespace Wayland
{
class WaylandEventThread;
class WaylandLinuxDmabufFeedbackV1;
class WaylandLinuxDmabufV1
{
public:
WaylandLinuxDmabufV1(wl_registry *registry, uint32_t name, uint32_t version);
~WaylandLinuxDmabufV1();
zwp_linux_dmabuf_v1 *handle() const;
QByteArray mainDevice() const;
QHash<uint32_t, QList<uint64_t>> formats() const;
private:
static void format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format);
static void modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo);
zwp_linux_dmabuf_v1 *m_dmabuf;
std::unique_ptr<WaylandLinuxDmabufFeedbackV1> m_defaultFeedback;
};
class WaylandDisplay : public QObject
{
Q_OBJECT
public:
WaylandDisplay();
~WaylandDisplay() override;
bool initialize(const QString &socketName);
wl_display *nativeDisplay() const;
KWayland::Client::Compositor *compositor() const;
KWayland::Client::PointerConstraints *pointerConstraints() const;
KWayland::Client::PointerGestures *pointerGestures() const;
KWayland::Client::RelativePointerManager *relativePointerManager() const;
KWayland::Client::Seat *seat() const;
KWayland::Client::XdgDecorationManager *xdgDecorationManager() const;
wl_shm *shm() const;
KWayland::Client::XdgShell *xdgShell() const;
WaylandLinuxDmabufV1 *linuxDmabuf() const;
wp_presentation *presentationTime() const;
public Q_SLOTS:
void flush();
private:
static void registry_global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version);
static void registry_global_remove(void *data, wl_registry *registry, uint32_t name);
wl_display *m_display = nullptr;
wl_registry *m_registry = nullptr;
wl_shm *m_shm = nullptr;
wp_presentation *m_presentationTime = nullptr;
std::unique_ptr<WaylandEventThread> m_eventThread;
std::unique_ptr<WaylandLinuxDmabufV1> m_linuxDmabuf;
std::unique_ptr<KWayland::Client::Compositor> m_compositor;
std::unique_ptr<KWayland::Client::PointerConstraints> m_pointerConstraints;
std::unique_ptr<KWayland::Client::PointerGestures> m_pointerGestures;
std::unique_ptr<KWayland::Client::RelativePointerManager> m_relativePointerManager;
std::unique_ptr<KWayland::Client::Seat> m_seat;
std::unique_ptr<KWayland::Client::XdgDecorationManager> m_xdgDecorationManager;
std::unique_ptr<KWayland::Client::XdgShell> m_xdgShell;
};
}
}
@@ -0,0 +1,358 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_egl_backend.h"
#include "core/drmdevice.h"
#include "core/gbmgraphicsbufferallocator.h"
#include "opengl/eglswapchain.h"
#include "opengl/glrendertimequery.h"
#include "opengl/glutils.h"
#include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h"
#include "scene/surfaceitem_wayland.h"
#include "wayland/surface.h"
#include "wayland_backend.h"
#include "wayland_display.h"
#include "wayland_logging.h"
#include "wayland_output.h"
#include <KWayland/Client/surface.h>
#include <cmath>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <unistd.h>
namespace KWin
{
namespace Wayland
{
static const bool bufferAgeEnabled = qEnvironmentVariable("KWIN_USE_BUFFER_AGE") != QStringLiteral("0");
WaylandEglPrimaryLayer::WaylandEglPrimaryLayer(WaylandOutput *output, WaylandEglBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
WaylandEglPrimaryLayer::~WaylandEglPrimaryLayer()
{
}
GLFramebuffer *WaylandEglPrimaryLayer::fbo() const
{
return m_buffer->framebuffer();
}
std::shared_ptr<GLTexture> WaylandEglPrimaryLayer::texture() const
{
return m_buffer->texture();
}
std::optional<OutputLayerBeginFrameInfo> WaylandEglPrimaryLayer::doBeginFrame()
{
if (!m_backend->openglContext()->makeCurrent()) {
qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed";
return std::nullopt;
}
const QSize nativeSize = m_output->modeSize();
if (!m_swapchain || m_swapchain->size() != nativeSize) {
const QHash<uint32_t, QList<uint64_t>> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats();
for (const uint32_t &candidateFormat : {DRM_FORMAT_XRGB2101010, DRM_FORMAT_XRGB8888}) {
auto it = formatTable.constFind(candidateFormat);
if (it == formatTable.constEnd()) {
continue;
}
m_swapchain = EglSwapchain::create(m_backend->drmDevice()->allocator(), m_backend->openglContext(), nativeSize, it.key(), it.value());
if (m_swapchain) {
break;
}
}
if (!m_swapchain) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Could not find a suitable render format";
return std::nullopt;
}
}
m_buffer = m_swapchain->acquire();
if (!m_buffer) {
return std::nullopt;
}
const QRegion repair = bufferAgeEnabled ? m_damageJournal.accumulate(m_buffer->age(), infiniteRegion()) : infiniteRegion();
m_query = std::make_unique<GLRenderTimeQuery>(m_backend->openglContextRef());
m_query->begin();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_buffer->framebuffer()),
.repaint = repair,
};
}
bool WaylandEglPrimaryLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_query->end();
frame->addRenderTimeQuery(std::move(m_query));
// Flush rendering commands to the dmabuf.
glFlush();
EGLNativeFence releaseFence{m_backend->eglDisplayObject()};
static_cast<WaylandOutput *>(m_output)->setPrimaryBuffer(m_backend->backend()->importBuffer(m_buffer->buffer()));
m_swapchain->release(m_buffer, releaseFence.takeFileDescriptor());
m_damageJournal.add(damagedRegion);
return true;
}
bool WaylandEglPrimaryLayer::doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame)
{
// TODO use viewporter to relax this check
if (sourceRect() != targetRect() || targetRect() != QRectF(QPointF(0, 0), m_output->modeSize())) {
return false;
}
if (offloadTransform() != OutputTransform::Kind::Normal || color != ColorDescription::sRGB) {
return false;
}
auto presentationBuffer = m_backend->backend()->importBuffer(buffer);
if (presentationBuffer) {
static_cast<WaylandOutput *>(m_output)->setPrimaryBuffer(presentationBuffer);
}
return presentationBuffer;
}
DrmDevice *WaylandEglPrimaryLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandEglPrimaryLayer::supportedDrmFormats() const
{
return m_backend->backend()->display()->linuxDmabuf()->formats();
}
WaylandEglCursorLayer::WaylandEglCursorLayer(WaylandOutput *output, WaylandEglBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
WaylandEglCursorLayer::~WaylandEglCursorLayer()
{
m_backend->openglContext()->makeCurrent();
}
std::optional<OutputLayerBeginFrameInfo> WaylandEglCursorLayer::doBeginFrame()
{
if (!m_backend->openglContext()->makeCurrent()) {
qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed";
return std::nullopt;
}
const auto tmp = targetRect().size().expandedTo(QSize(64, 64));
const QSize bufferSize(std::ceil(tmp.width()), std::ceil(tmp.height()));
if (!m_swapchain || m_swapchain->size() != bufferSize) {
const QHash<uint32_t, QList<uint64_t>> formatTable = m_backend->backend()->display()->linuxDmabuf()->formats();
uint32_t format = DRM_FORMAT_INVALID;
QList<uint64_t> modifiers;
for (const uint32_t &candidateFormat : {DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB8888}) {
auto it = formatTable.constFind(candidateFormat);
if (it != formatTable.constEnd()) {
format = it.key();
modifiers = it.value();
break;
}
}
if (format == DRM_FORMAT_INVALID) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Could not find a suitable render format";
return std::nullopt;
}
m_swapchain = EglSwapchain::create(m_backend->drmDevice()->allocator(), m_backend->openglContext(), bufferSize, format, modifiers);
if (!m_swapchain) {
return std::nullopt;
}
}
m_buffer = m_swapchain->acquire();
if (!m_buffer) {
return std::nullopt;
}
m_query = std::make_unique<GLRenderTimeQuery>(m_backend->openglContextRef());
m_query->begin();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_buffer->framebuffer()),
.repaint = infiniteRegion(),
};
}
bool WaylandEglCursorLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_query->end();
if (frame) {
frame->addRenderTimeQuery(std::move(m_query));
}
// Flush rendering commands to the dmabuf.
glFlush();
wl_buffer *buffer = m_backend->backend()->importBuffer(m_buffer->buffer());
Q_ASSERT(buffer);
static_cast<WaylandOutput *>(m_output)->cursor()->update(buffer, scale(), hotspot().toPoint());
EGLNativeFence releaseFence{m_backend->eglDisplayObject()};
m_swapchain->release(m_buffer, releaseFence.takeFileDescriptor());
return true;
}
DrmDevice *WaylandEglCursorLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandEglCursorLayer::supportedDrmFormats() const
{
return m_backend->supportedFormats();
}
WaylandEglBackend::WaylandEglBackend(WaylandBackend *b)
: AbstractEglBackend()
, m_backend(b)
{
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandEglBackend::createEglWaylandOutput);
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *output) {
m_outputs.erase(output);
});
b->setEglBackend(this);
}
WaylandEglBackend::~WaylandEglBackend()
{
cleanup();
}
WaylandBackend *WaylandEglBackend::backend() const
{
return m_backend;
}
DrmDevice *WaylandEglBackend::drmDevice() const
{
return m_backend->drmDevice();
}
void WaylandEglBackend::cleanupSurfaces()
{
m_outputs.clear();
}
bool WaylandEglBackend::createEglWaylandOutput(Output *waylandOutput)
{
m_outputs[waylandOutput] = Layers{
.primaryLayer = std::make_unique<WaylandEglPrimaryLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
.cursorLayer = std::make_unique<WaylandEglCursorLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
};
return true;
}
bool WaylandEglBackend::initializeEgl()
{
initClientExtensions();
if (!m_backend->sceneEglDisplayObject()) {
for (const QByteArray &extension : {QByteArrayLiteral("EGL_EXT_platform_base"), QByteArrayLiteral("EGL_KHR_platform_gbm")}) {
if (!hasClientExtension(extension)) {
qCWarning(KWIN_WAYLAND_BACKEND) << extension << "client extension is not supported by the platform";
return false;
}
}
m_backend->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, m_backend->drmDevice()->gbmDevice(), nullptr)));
}
const auto display = m_backend->sceneEglDisplayObject();
if (!display) {
return false;
}
setEglDisplay(display);
return true;
}
void WaylandEglBackend::init()
{
if (!initializeEgl()) {
setFailed("Could not initialize egl");
return;
}
if (!initRenderingContext()) {
setFailed("Could not initialize rendering context");
return;
}
initWayland();
}
bool WaylandEglBackend::initRenderingContext()
{
if (!createContext(EGL_NO_CONFIG_KHR)) {
return false;
}
auto waylandOutputs = m_backend->waylandOutputs();
// we only allow to start with at least one output
if (waylandOutputs.isEmpty()) {
return false;
}
for (auto *out : waylandOutputs) {
if (!createEglWaylandOutput(out)) {
return false;
}
}
if (m_outputs.empty()) {
qCCritical(KWIN_WAYLAND_BACKEND) << "Create Window Surfaces failed";
return false;
}
return makeCurrent();
}
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> WaylandEglBackend::textureForOutput(KWin::Output *output) const
{
return std::make_pair(m_outputs.at(output).primaryLayer->texture(), ColorDescription::sRGB);
}
std::unique_ptr<SurfaceTexture> WaylandEglBackend::createSurfaceTextureWayland(SurfacePixmap *pixmap)
{
return std::make_unique<BasicEGLSurfaceTextureWayland>(this, pixmap);
}
bool WaylandEglBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
static_cast<WaylandOutput *>(output)->present(frame);
return true;
}
OutputLayer *WaylandEglBackend::primaryLayer(Output *output)
{
return m_outputs[output].primaryLayer.get();
}
OutputLayer *WaylandEglBackend::cursorLayer(Output *output)
{
return m_outputs[output].cursorLayer.get();
}
}
}
#include "moc_wayland_egl_backend.cpp"
@@ -0,0 +1,128 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/outputlayer.h"
#include "opengl/eglnativefence.h"
#include "platformsupport/scenes/opengl/abstract_egl_backend.h"
#include "utils/damagejournal.h"
#include <memory>
struct wl_buffer;
namespace KWin
{
class EglSwapchainSlot;
class EglSwapchain;
class GLFramebuffer;
class GraphicsBufferAllocator;
class GLRenderTimeQuery;
namespace Wayland
{
class WaylandBackend;
class WaylandOutput;
class WaylandEglBackend;
class WaylandEglPrimaryLayer : public OutputLayer
{
public:
WaylandEglPrimaryLayer(WaylandOutput *output, WaylandEglBackend *backend);
~WaylandEglPrimaryLayer() override;
GLFramebuffer *fbo() const;
std::shared_ptr<GLTexture> texture() const;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
bool doImportScanoutBuffer(GraphicsBuffer *buffer, const ColorDescription &color, RenderingIntent intent, const std::shared_ptr<OutputFrame> &frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
DamageJournal m_damageJournal;
std::shared_ptr<EglSwapchain> m_swapchain;
std::shared_ptr<EglSwapchainSlot> m_buffer;
std::unique_ptr<GLRenderTimeQuery> m_query;
WaylandEglBackend *const m_backend;
friend class WaylandEglBackend;
};
class WaylandEglCursorLayer : public OutputLayer
{
Q_OBJECT
public:
WaylandEglCursorLayer(WaylandOutput *output, WaylandEglBackend *backend);
~WaylandEglCursorLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
WaylandEglBackend *m_backend;
std::shared_ptr<EglSwapchain> m_swapchain;
std::shared_ptr<EglSwapchainSlot> m_buffer;
std::unique_ptr<GLRenderTimeQuery> m_query;
};
/**
* @brief OpenGL Backend using Egl on a Wayland surface.
*
* This Backend is the basis for a session compositor running on top of a Wayland system compositor.
* It creates a Surface as large as the screen and maps it as a fullscreen shell surface on the
* system compositor. The OpenGL context is created on the Wayland surface, so for rendering X11 is
* not involved.
*
* Also in repainting the backend is currently still rather limited. Only supported mode is fullscreen
* repaints, which is obviously not optimal. Best solution is probably to go for buffer_age extension
* and make it the only available solution next to fullscreen repaints.
*/
class WaylandEglBackend : public AbstractEglBackend
{
Q_OBJECT
public:
WaylandEglBackend(WaylandBackend *b);
~WaylandEglBackend() override;
WaylandBackend *backend() const;
DrmDevice *drmDevice() const override;
std::unique_ptr<SurfaceTexture> createSurfaceTextureWayland(SurfacePixmap *pixmap) override;
void init() override;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
OutputLayer *primaryLayer(Output *output) override;
OutputLayer *cursorLayer(Output *output) override;
std::pair<std::shared_ptr<KWin::GLTexture>, ColorDescription> textureForOutput(KWin::Output *output) const override;
private:
bool initializeEgl();
bool initRenderingContext();
bool createEglWaylandOutput(Output *output);
void cleanupSurfaces() override;
struct Layers
{
std::unique_ptr<WaylandEglPrimaryLayer> primaryLayer;
std::unique_ptr<WaylandEglCursorLayer> cursorLayer;
};
WaylandBackend *m_backend;
std::map<Output *, Layers> m_outputs;
};
} // namespace Wayland
} // namespace KWin
@@ -0,0 +1,10 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_logging.h"
Q_LOGGING_CATEGORY(KWIN_WAYLAND_BACKEND, "kwin_wayland_backend", QtWarningMsg)
@@ -0,0 +1,14 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KWIN_WAYLAND_BACKEND)
@@ -0,0 +1,420 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_output.h"
#include "compositor.h"
#include "core/outputlayer.h"
#include "core/renderbackend.h"
#include "core/renderloop_p.h"
#include "wayland_backend.h"
#include "wayland_display.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointerconstraints.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgdecoration.h>
#include "wayland-presentation-time-client-protocol.h"
#include <KLocalizedString>
#include <QPainter>
#include <cmath>
namespace KWin
{
namespace Wayland
{
using namespace KWayland::Client;
static const int s_refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host?
WaylandCursor::WaylandCursor(WaylandBackend *backend)
: m_surface(backend->display()->compositor()->createSurface())
{
}
WaylandCursor::~WaylandCursor() = default;
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.get(), m_hotspot);
}
}
void WaylandCursor::setEnabled(bool enable)
{
if (m_enabled != enable) {
m_enabled = enable;
sync();
}
}
void WaylandCursor::update(wl_buffer *buffer, qreal scale, const QPoint &hotspot)
{
if (m_buffer != buffer || m_scale != scale || m_hotspot != hotspot) {
m_buffer = buffer;
m_scale = scale;
m_hotspot = hotspot;
sync();
}
}
void WaylandCursor::sync()
{
if (!m_enabled) {
m_surface->attachBuffer(KWayland::Client::Buffer::Ptr());
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
} else {
m_surface->attachBuffer(m_buffer);
m_surface->setScale(std::ceil(m_scale));
m_surface->damageBuffer(QRect(0, 0, INT32_MAX, INT32_MAX));
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
}
if (m_pointer) {
m_pointer->setCursor(m_surface.get(), m_hotspot);
}
}
WaylandOutput::WaylandOutput(const QString &name, WaylandBackend *backend)
: Output(backend)
, m_renderLoop(std::make_unique<RenderLoop>(this))
, m_surface(backend->display()->compositor()->createSurface())
, m_xdgShellSurface(backend->display()->xdgShell()->createSurface(m_surface.get()))
, m_backend(backend)
, m_cursor(std::make_unique<WaylandCursor>(backend))
{
if (KWayland::Client::XdgDecorationManager *manager = m_backend->display()->xdgDecorationManager()) {
m_xdgDecoration.reset(manager->getToplevelDecoration(m_xdgShellSurface.get()));
m_xdgDecoration->setMode(KWayland::Client::XdgDecoration::Mode::ServerSide);
}
setInformation(Information{
.name = name,
.model = name,
.capabilities = Capability::Dpms,
});
m_turnOffTimer.setSingleShot(true);
m_turnOffTimer.setInterval(dimAnimationTime());
connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
updateDpmsMode(DpmsMode::Off);
});
m_configureThrottleTimer.setSingleShot(true);
connect(&m_configureThrottleTimer, &QTimer::timeout, this, [this]() {
applyConfigure(m_pendingConfigureSize, m_pendingConfigureSerial);
});
connect(m_surface.get(), &KWayland::Client::Surface::frameRendered, this, [this]() {
Q_ASSERT(m_frame);
m_frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
m_frame.reset();
});
updateWindowTitle();
connect(m_xdgShellSurface.get(), &XdgShellSurface::configureRequested, this, &WaylandOutput::handleConfigure);
connect(m_xdgShellSurface.get(), &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit);
connect(this, &WaylandOutput::enabledChanged, this, &WaylandOutput::updateWindowTitle);
connect(this, &WaylandOutput::dpmsModeChanged, this, &WaylandOutput::updateWindowTitle);
}
WaylandOutput::~WaylandOutput()
{
if (m_presentationFeedback) {
wp_presentation_feedback_destroy(m_presentationFeedback);
m_presentationFeedback = nullptr;
}
m_xdgDecoration.reset();
m_xdgShellSurface.reset();
m_surface.reset();
}
void WaylandOutput::setPrimaryBuffer(wl_buffer *buffer)
{
m_presentationBuffer = buffer;
}
static void handleDiscarded(void *data,
struct wp_presentation_feedback *wp_presentation_feedback)
{
reinterpret_cast<WaylandOutput *>(data)->frameDiscarded();
}
static void handlePresented(void *data,
struct wp_presentation_feedback *wp_presentation_feedback,
uint32_t tv_sec_hi,
uint32_t tv_sec_lo,
uint32_t tv_nsec,
uint32_t refresh,
uint32_t seq_hi,
uint32_t seq_lo,
uint32_t flags)
{
const auto timestamp = std::chrono::seconds((uint64_t(tv_sec_hi) << 32) | tv_sec_lo) + std::chrono::nanoseconds(tv_nsec);
uint32_t refreshRate = 60'000;
if (refresh != 0) {
refreshRate = 1'000'000'000'000 / refresh;
}
reinterpret_cast<WaylandOutput *>(data)->framePresented(timestamp, refreshRate);
}
static void handleSyncOutput(void *data, struct wp_presentation_feedback *, struct wl_output *)
{
// intentionally ignored
}
static constexpr struct wp_presentation_feedback_listener s_presentationListener{
.sync_output = handleSyncOutput,
.presented = handlePresented,
.discarded = handleDiscarded,
};
void WaylandOutput::present(const std::shared_ptr<OutputFrame> &frame)
{
if (!m_presentationBuffer) {
return;
}
m_surface->attachBuffer(m_presentationBuffer);
m_surface->damage(frame->damage());
m_surface->setScale(std::ceil(scale()));
m_presentationBuffer = nullptr;
if (auto presentationTime = m_backend->display()->presentationTime()) {
m_presentationFeedback = wp_presentation_feedback(presentationTime, *m_surface);
wp_presentation_feedback_add_listener(m_presentationFeedback, &s_presentationListener, this);
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
} else {
m_surface->commit(KWayland::Client::Surface::CommitFlag::FrameCallback);
}
m_frame = frame;
Q_EMIT outputChange(frame->damage());
}
void WaylandOutput::frameDiscarded()
{
m_frame.reset();
if (m_presentationFeedback) {
wp_presentation_feedback_destroy(m_presentationFeedback);
m_presentationFeedback = nullptr;
}
}
void WaylandOutput::framePresented(std::chrono::nanoseconds timestamp, uint32_t refreshRate)
{
if (refreshRate != this->refreshRate()) {
m_refreshRate = refreshRate;
const auto mode = std::make_shared<OutputMode>(pixelSize(), m_refreshRate);
State next = m_state;
next.modes = {mode};
next.currentMode = mode;
setState(next);
m_renderLoop->setRefreshRate(m_refreshRate);
}
m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset();
if (m_presentationFeedback) {
wp_presentation_feedback_destroy(m_presentationFeedback);
m_presentationFeedback = nullptr;
}
}
bool WaylandOutput::isReady() const
{
return m_ready;
}
KWayland::Client::Surface *WaylandOutput::surface() const
{
return m_surface.get();
}
WaylandCursor *WaylandOutput::cursor() const
{
return m_cursor.get();
}
WaylandBackend *WaylandOutput::backend() const
{
return m_backend;
}
RenderLoop *WaylandOutput::renderLoop() const
{
return m_renderLoop.get();
}
bool WaylandOutput::updateCursorLayer(std::optional<std::chrono::nanoseconds> allowedVrrDelay)
{
if (m_hasPointerLock) {
m_cursor->setEnabled(false);
return false;
} else {
m_cursor->setEnabled(Compositor::self()->backend()->cursorLayer(this)->isEnabled());
// the layer already takes care of updating the image
return true;
}
}
void WaylandOutput::init(const QSize &pixelSize, qreal scale)
{
m_renderLoop->setRefreshRate(m_refreshRate);
auto mode = std::make_shared<OutputMode>(pixelSize, m_refreshRate);
State initialState;
initialState.modes = {mode};
initialState.currentMode = mode;
initialState.scale = scale;
setState(initialState);
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
}
void WaylandOutput::resize(const QSize &pixelSize)
{
auto mode = std::make_shared<OutputMode>(pixelSize, m_refreshRate);
State next = m_state;
next.modes = {mode};
next.currentMode = mode;
setState(next);
Q_EMIT m_backend->outputsQueried();
}
void WaylandOutput::setDpmsMode(DpmsMode mode)
{
if (mode == DpmsMode::Off) {
if (!m_turnOffTimer.isActive()) {
Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval()));
m_turnOffTimer.start();
}
} else {
m_turnOffTimer.stop();
if (mode != dpmsMode()) {
updateDpmsMode(mode);
Q_EMIT wakeUp();
}
}
}
void WaylandOutput::updateDpmsMode(DpmsMode dpmsMode)
{
State next = m_state;
next.dpmsMode = dpmsMode;
setState(next);
}
void WaylandOutput::updateEnabled(bool enabled)
{
State next = m_state;
next.enabled = enabled;
setState(next);
}
void WaylandOutput::handleConfigure(const QSize &size, XdgShellSurface::States states, quint32 serial)
{
if (!m_ready) {
m_ready = true;
applyConfigure(size, serial);
} else {
// Output resizing is a resource intensive task, so the configure events are throttled.
m_pendingConfigureSerial = serial;
m_pendingConfigureSize = size;
if (!m_configureThrottleTimer.isActive()) {
m_configureThrottleTimer.start(1000000 / m_state.currentMode->refreshRate());
}
}
}
void WaylandOutput::applyConfigure(const QSize &size, quint32 serial)
{
m_xdgShellSurface->ackConfigure(serial);
if (!size.isEmpty()) {
resize(size * scale());
}
}
void WaylandOutput::updateWindowTitle()
{
QString grab;
if (m_hasPointerLock) {
grab = i18n("Press right control to ungrab pointer");
} else if (m_backend->display()->pointerConstraints()) {
grab = i18n("Press right control key to grab pointer");
}
QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument",
"KDE Wayland Compositor %1", name());
if (!isEnabled()) {
title += i18n("- Output disabled");
} else if (dpmsMode() != DpmsMode::On) {
title += i18n("- Output dimmed");
} else if (!grab.isEmpty()) {
title += QStringLiteral("") + grab;
}
m_xdgShellSurface->setTitle(title);
}
void WaylandOutput::lockPointer(Pointer *pointer, bool lock)
{
if (!lock) {
const bool surfaceWasLocked = m_pointerLock && m_hasPointerLock;
m_pointerLock.reset();
m_hasPointerLock = false;
if (surfaceWasLocked) {
updateWindowTitle();
updateCursorLayer(std::nullopt);
Q_EMIT m_backend->pointerLockChanged(false);
}
return;
}
Q_ASSERT(!m_pointerLock);
m_pointerLock.reset(m_backend->display()->pointerConstraints()->lockPointer(surface(), pointer, nullptr, PointerConstraints::LifeTime::OneShot));
if (!m_pointerLock->isValid()) {
m_pointerLock.reset();
return;
}
connect(m_pointerLock.get(), &LockedPointer::locked, this, [this]() {
m_hasPointerLock = true;
updateWindowTitle();
updateCursorLayer(std::nullopt);
Q_EMIT m_backend->pointerLockChanged(true);
});
connect(m_pointerLock.get(), &LockedPointer::unlocked, this, [this]() {
m_pointerLock.reset();
m_hasPointerLock = false;
updateWindowTitle();
updateCursorLayer(std::nullopt);
Q_EMIT m_backend->pointerLockChanged(false);
});
}
}
}
#include "moc_wayland_output.cpp"
@@ -0,0 +1,117 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
#include <KWayland/Client/xdgshell.h>
#include <QObject>
#include <QTimer>
namespace KWayland
{
namespace Client
{
class Surface;
class Pointer;
class LockedPointer;
class XdgDecoration;
}
}
struct wl_buffer;
struct wp_presentation_feedback;
namespace KWin
{
class OutputFrame;
namespace Wayland
{
class WaylandBackend;
class WaylandCursor
{
public:
explicit WaylandCursor(WaylandBackend *backend);
~WaylandCursor();
KWayland::Client::Pointer *pointer() const;
void setPointer(KWayland::Client::Pointer *pointer);
void setEnabled(bool enable);
void update(wl_buffer *buffer, qreal scale, const QPoint &hotspot);
private:
void sync();
KWayland::Client::Pointer *m_pointer = nullptr;
std::unique_ptr<KWayland::Client::Surface> m_surface;
wl_buffer *m_buffer = nullptr;
QPoint m_hotspot;
qreal m_scale = 1;
bool m_enabled = true;
};
class WaylandOutput : public Output
{
Q_OBJECT
public:
WaylandOutput(const QString &name, WaylandBackend *backend);
~WaylandOutput() override;
RenderLoop *renderLoop() const override;
bool updateCursorLayer(std::optional<std::chrono::nanoseconds> allowedVrrDelay) override;
void init(const QSize &pixelSize, qreal scale);
bool isReady() const;
KWayland::Client::Surface *surface() const;
WaylandCursor *cursor() const;
WaylandBackend *backend() const;
void lockPointer(KWayland::Client::Pointer *pointer, bool lock);
void resize(const QSize &pixelSize);
void setDpmsMode(DpmsMode mode) override;
void updateDpmsMode(DpmsMode dpmsMode);
void updateEnabled(bool enabled);
void present(const std::shared_ptr<OutputFrame> &frame);
void setPrimaryBuffer(wl_buffer *buffer);
void frameDiscarded();
void framePresented(std::chrono::nanoseconds timestamp, uint32_t refreshRate);
private:
void handleConfigure(const QSize &size, KWayland::Client::XdgShellSurface::States states, quint32 serial);
void updateWindowTitle();
void applyConfigure(const QSize &size, quint32 serial);
std::unique_ptr<RenderLoop> m_renderLoop;
std::unique_ptr<KWayland::Client::Surface> m_surface;
std::unique_ptr<KWayland::Client::XdgShellSurface> m_xdgShellSurface;
std::unique_ptr<KWayland::Client::LockedPointer> m_pointerLock;
std::unique_ptr<KWayland::Client::XdgDecoration> m_xdgDecoration;
WaylandBackend *const m_backend;
std::unique_ptr<WaylandCursor> m_cursor;
QTimer m_turnOffTimer;
bool m_hasPointerLock = false;
bool m_ready = false;
std::shared_ptr<OutputFrame> m_frame;
wl_buffer *m_presentationBuffer = nullptr;
quint32 m_pendingConfigureSerial = 0;
QSize m_pendingConfigureSize;
QTimer m_configureThrottleTimer;
wp_presentation_feedback *m_presentationFeedback = nullptr;
uint32_t m_refreshRate = 60'000;
};
} // namespace Wayland
} // namespace KWin
@@ -0,0 +1,188 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_qpainter_backend.h"
#include "core/graphicsbufferview.h"
#include "core/shmgraphicsbufferallocator.h"
#include "platformsupport/scenes/qpainter/qpainterswapchain.h"
#include "wayland_backend.h"
#include "wayland_output.h"
#include <KWayland/Client/surface.h>
#include <cmath>
#include <drm_fourcc.h>
#include <wayland-client-protocol.h>
namespace KWin
{
namespace Wayland
{
WaylandQPainterPrimaryLayer::WaylandQPainterPrimaryLayer(WaylandOutput *output, WaylandQPainterBackend *backend)
: OutputLayer(output)
, m_waylandOutput(output)
, m_backend(backend)
{
}
WaylandQPainterPrimaryLayer::~WaylandQPainterPrimaryLayer()
{
}
QRegion WaylandQPainterPrimaryLayer::accumulateDamage(int bufferAge) const
{
return m_damageJournal.accumulate(bufferAge, infiniteRegion());
}
std::optional<OutputLayerBeginFrameInfo> WaylandQPainterPrimaryLayer::doBeginFrame()
{
const QSize nativeSize(m_waylandOutput->modeSize());
if (!m_swapchain || m_swapchain->size() != nativeSize) {
m_swapchain = std::make_unique<QPainterSwapchain>(m_backend->graphicsBufferAllocator(), nativeSize, DRM_FORMAT_XRGB8888);
}
m_back = m_swapchain->acquire();
if (!m_back) {
return std::nullopt;
}
m_renderTime = std::make_unique<CpuRenderTimeQuery>();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_back->view()->image()),
.repaint = accumulateDamage(m_back->age()),
};
}
bool WaylandQPainterPrimaryLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
m_renderTime->end();
frame->addRenderTimeQuery(std::move(m_renderTime));
m_damageJournal.add(damagedRegion);
m_waylandOutput->setPrimaryBuffer(m_waylandOutput->backend()->importBuffer(m_back->buffer()));
m_swapchain->release(m_back);
return true;
}
DrmDevice *WaylandQPainterPrimaryLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandQPainterPrimaryLayer::supportedDrmFormats() const
{
return {{DRM_FORMAT_ARGB8888, {DRM_FORMAT_MOD_LINEAR}}};
}
WaylandQPainterCursorLayer::WaylandQPainterCursorLayer(WaylandOutput *output, WaylandQPainterBackend *backend)
: OutputLayer(output)
, m_backend(backend)
{
}
WaylandQPainterCursorLayer::~WaylandQPainterCursorLayer()
{
}
std::optional<OutputLayerBeginFrameInfo> WaylandQPainterCursorLayer::doBeginFrame()
{
const auto tmp = targetRect().size().expandedTo(QSize(64, 64));
const QSize bufferSize(std::ceil(tmp.width()), std::ceil(tmp.height()));
if (!m_swapchain || m_swapchain->size() != bufferSize) {
m_swapchain = std::make_unique<QPainterSwapchain>(m_backend->graphicsBufferAllocator(), bufferSize, DRM_FORMAT_ARGB8888);
}
m_back = m_swapchain->acquire();
if (!m_back) {
return std::nullopt;
}
m_renderTime = std::make_unique<CpuRenderTimeQuery>();
return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_back->view()->image()),
.repaint = infiniteRegion(),
};
}
bool WaylandQPainterCursorLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
{
if (frame) {
frame->addRenderTimeQuery(std::move(m_renderTime));
}
wl_buffer *buffer = static_cast<WaylandOutput *>(m_output)->backend()->importBuffer(m_back->buffer());
Q_ASSERT(buffer);
static_cast<WaylandOutput *>(m_output)->cursor()->update(buffer, scale(), hotspot().toPoint());
m_swapchain->release(m_back);
return true;
}
DrmDevice *WaylandQPainterCursorLayer::scanoutDevice() const
{
return m_backend->drmDevice();
}
QHash<uint32_t, QList<uint64_t>> WaylandQPainterCursorLayer::supportedDrmFormats() const
{
return {{DRM_FORMAT_ARGB8888, {DRM_FORMAT_MOD_LINEAR}}};
}
WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b)
: QPainterBackend()
, m_backend(b)
, m_allocator(std::make_unique<ShmGraphicsBufferAllocator>())
{
const auto waylandOutputs = m_backend->waylandOutputs();
for (auto *output : waylandOutputs) {
createOutput(output);
}
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandQPainterBackend::createOutput);
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *waylandOutput) {
m_outputs.erase(waylandOutput);
});
}
WaylandQPainterBackend::~WaylandQPainterBackend()
{
}
void WaylandQPainterBackend::createOutput(Output *waylandOutput)
{
m_outputs[waylandOutput] = Layers{
.primaryLayer = std::make_unique<WaylandQPainterPrimaryLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
.cursorLayer = std::make_unique<WaylandQPainterCursorLayer>(static_cast<WaylandOutput *>(waylandOutput), this),
};
}
GraphicsBufferAllocator *WaylandQPainterBackend::graphicsBufferAllocator() const
{
return m_allocator.get();
}
bool WaylandQPainterBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
{
static_cast<WaylandOutput *>(output)->present(frame);
return true;
}
OutputLayer *WaylandQPainterBackend::primaryLayer(Output *output)
{
return m_outputs[output].primaryLayer.get();
}
OutputLayer *WaylandQPainterBackend::cursorLayer(Output *output)
{
return m_outputs[output].cursorLayer.get();
}
}
}
#include "moc_wayland_qpainter_backend.cpp"
@@ -0,0 +1,107 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/outputlayer.h"
#include "platformsupport/scenes/qpainter/qpainterbackend.h"
#include "utils/damagejournal.h"
#include <QImage>
#include <QObject>
#include <chrono>
namespace KWin
{
class Output;
class GraphicsBufferAllocator;
class QPainterSwapchainSlot;
class QPainterSwapchain;
namespace Wayland
{
class WaylandBackend;
class WaylandDisplay;
class WaylandOutput;
class WaylandQPainterBackend;
class WaylandQPainterPrimaryLayer : public OutputLayer
{
public:
WaylandQPainterPrimaryLayer(WaylandOutput *output, WaylandQPainterBackend *backend);
~WaylandQPainterPrimaryLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
QRegion accumulateDamage(int bufferAge) const;
private:
WaylandOutput *m_waylandOutput;
WaylandQPainterBackend *m_backend;
DamageJournal m_damageJournal;
std::unique_ptr<QPainterSwapchain> m_swapchain;
std::shared_ptr<QPainterSwapchainSlot> m_back;
std::unique_ptr<CpuRenderTimeQuery> m_renderTime;
friend class WaylandQPainterBackend;
};
class WaylandQPainterCursorLayer : public OutputLayer
{
Q_OBJECT
public:
WaylandQPainterCursorLayer(WaylandOutput *output, WaylandQPainterBackend *backend);
~WaylandQPainterCursorLayer() override;
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
private:
WaylandQPainterBackend *m_backend;
std::unique_ptr<QPainterSwapchain> m_swapchain;
std::shared_ptr<QPainterSwapchainSlot> m_back;
std::unique_ptr<CpuRenderTimeQuery> m_renderTime;
};
class WaylandQPainterBackend : public QPainterBackend
{
Q_OBJECT
public:
explicit WaylandQPainterBackend(WaylandBackend *b);
~WaylandQPainterBackend() override;
GraphicsBufferAllocator *graphicsBufferAllocator() const;
bool present(Output *output, const std::shared_ptr<OutputFrame> &frame) override;
OutputLayer *primaryLayer(Output *output) override;
OutputLayer *cursorLayer(Output *output) override;
private:
void createOutput(Output *waylandOutput);
struct Layers
{
std::unique_ptr<WaylandQPainterPrimaryLayer> primaryLayer;
std::unique_ptr<WaylandQPainterCursorLayer> cursorLayer;
};
WaylandBackend *m_backend;
std::unique_ptr<GraphicsBufferAllocator> m_allocator;
std::map<Output *, Layers> m_outputs;
};
} // namespace Wayland
} // namespace KWin