backends/wayland: Introduce cursor layers
It's a necessary step to let kwin repaint the cursor from Compositor. Unfortunately, it also means that we need to add more (temporary) code to paint the cursor in backends.
This commit is contained in:
parent
1c43571fe1
commit
dbd574ec05
6 changed files with 368 additions and 91 deletions
|
@ -26,6 +26,7 @@
|
||||||
#include <kwinglutils.h>
|
#include <kwinglutils.h>
|
||||||
|
|
||||||
// KDE
|
// KDE
|
||||||
|
#include <KWayland/Client/shm_pool.h>
|
||||||
#include <KWayland/Client/surface.h>
|
#include <KWayland/Client/surface.h>
|
||||||
|
|
||||||
// Qt
|
// Qt
|
||||||
|
@ -61,13 +62,13 @@ static QVector<EGLint> regionToRects(const QRegion ®ion, Output *output)
|
||||||
return rects;
|
return rects;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaylandEglOutput::WaylandEglOutput(WaylandOutput *output, WaylandEglBackend *backend)
|
WaylandEglPrimaryLayer::WaylandEglPrimaryLayer(WaylandOutput *output, WaylandEglBackend *backend)
|
||||||
: m_waylandOutput(output)
|
: m_waylandOutput(output)
|
||||||
, m_backend(backend)
|
, m_backend(backend)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandEglOutput::init()
|
bool WaylandEglPrimaryLayer::init()
|
||||||
{
|
{
|
||||||
auto surface = m_waylandOutput->surface();
|
auto surface = m_waylandOutput->surface();
|
||||||
const QSize nativeSize = m_waylandOutput->geometry().size() * m_waylandOutput->scale();
|
const QSize nativeSize = m_waylandOutput->geometry().size() * m_waylandOutput->scale();
|
||||||
|
@ -94,35 +95,23 @@ bool WaylandEglOutput::init()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaylandEglOutput::~WaylandEglOutput()
|
WaylandEglPrimaryLayer::~WaylandEglPrimaryLayer()
|
||||||
{
|
{
|
||||||
wl_egl_window_destroy(m_overlay);
|
wl_egl_window_destroy(m_overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFramebuffer *WaylandEglOutput::fbo() const
|
GLFramebuffer *WaylandEglPrimaryLayer::fbo() const
|
||||||
{
|
{
|
||||||
return m_fbo.get();
|
return m_fbo.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandEglOutput::makeContextCurrent() const
|
std::optional<OutputLayerBeginFrameInfo> WaylandEglPrimaryLayer::beginFrame()
|
||||||
{
|
{
|
||||||
if (m_eglSurface == EGL_NO_SURFACE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (eglMakeCurrent(m_backend->eglDisplay(), m_eglSurface, m_eglSurface, m_backend->context()) == EGL_FALSE) {
|
if (eglMakeCurrent(m_backend->eglDisplay(), m_eglSurface, m_eglSurface, m_backend->context()) == EGL_FALSE) {
|
||||||
qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed";
|
qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed";
|
||||||
return false;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
EGLint error = eglGetError();
|
|
||||||
if (error != EGL_SUCCESS) {
|
|
||||||
qCWarning(KWIN_WAYLAND_BACKEND) << "Error occurred while creating context " << error;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<OutputLayerBeginFrameInfo> WaylandEglOutput::beginFrame()
|
|
||||||
{
|
|
||||||
const QSize nativeSize = m_waylandOutput->pixelSize();
|
const QSize nativeSize = m_waylandOutput->pixelSize();
|
||||||
if (!m_fbo || m_fbo->size() != nativeSize) {
|
if (!m_fbo || m_fbo->size() != nativeSize) {
|
||||||
m_fbo = std::make_unique<GLFramebuffer>(0, nativeSize);
|
m_fbo = std::make_unique<GLFramebuffer>(0, nativeSize);
|
||||||
|
@ -130,9 +119,6 @@ std::optional<OutputLayerBeginFrameInfo> WaylandEglOutput::beginFrame()
|
||||||
wl_egl_window_resize(m_overlay, nativeSize.width(), nativeSize.height(), 0, 0);
|
wl_egl_window_resize(m_overlay, nativeSize.width(), nativeSize.height(), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
eglWaitNative(EGL_CORE_NATIVE_ENGINE);
|
|
||||||
makeContextCurrent();
|
|
||||||
|
|
||||||
QRegion repair;
|
QRegion repair;
|
||||||
if (m_backend->supportsBufferAge()) {
|
if (m_backend->supportsBufferAge()) {
|
||||||
repair = m_damageJournal.accumulate(m_bufferAge, infiniteRegion());
|
repair = m_damageJournal.accumulate(m_bufferAge, infiniteRegion());
|
||||||
|
@ -145,14 +131,14 @@ std::optional<OutputLayerBeginFrameInfo> WaylandEglOutput::beginFrame()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandEglOutput::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
|
bool WaylandEglPrimaryLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
|
||||||
{
|
{
|
||||||
m_damageJournal.add(damagedRegion);
|
m_damageJournal.add(damagedRegion);
|
||||||
GLFramebuffer::popFramebuffer();
|
GLFramebuffer::popFramebuffer();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaylandEglOutput::aboutToStartPainting(const QRegion &damage)
|
void WaylandEglPrimaryLayer::aboutToStartPainting(const QRegion &damage)
|
||||||
{
|
{
|
||||||
if (m_bufferAge > 0 && !damage.isEmpty() && m_backend->supportsPartialUpdate()) {
|
if (m_bufferAge > 0 && !damage.isEmpty() && m_backend->supportsPartialUpdate()) {
|
||||||
QVector<EGLint> rects = regionToRects(damage, m_waylandOutput);
|
QVector<EGLint> rects = regionToRects(damage, m_waylandOutput);
|
||||||
|
@ -164,7 +150,7 @@ void WaylandEglOutput::aboutToStartPainting(const QRegion &damage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaylandEglOutput::present()
|
void WaylandEglPrimaryLayer::present()
|
||||||
{
|
{
|
||||||
m_waylandOutput->surface()->setupFrameCallback();
|
m_waylandOutput->surface()->setupFrameCallback();
|
||||||
m_waylandOutput->surface()->setScale(std::ceil(m_waylandOutput->scale()));
|
m_waylandOutput->surface()->setScale(std::ceil(m_waylandOutput->scale()));
|
||||||
|
@ -187,6 +173,81 @@ void WaylandEglOutput::present()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaylandEglCursorLayer::WaylandEglCursorLayer(WaylandOutput *output, WaylandEglBackend *backend)
|
||||||
|
: m_output(output)
|
||||||
|
, m_backend(backend)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandEglCursorLayer::~WaylandEglCursorLayer()
|
||||||
|
{
|
||||||
|
eglMakeCurrent(m_backend->eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, m_backend->context());
|
||||||
|
m_framebuffer.reset();
|
||||||
|
m_texture.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal WaylandEglCursorLayer::scale() const
|
||||||
|
{
|
||||||
|
return m_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglCursorLayer::setScale(qreal scale)
|
||||||
|
{
|
||||||
|
m_scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint WaylandEglCursorLayer::hotspot() const
|
||||||
|
{
|
||||||
|
return m_hotspot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglCursorLayer::setHotspot(const QPoint &hotspot)
|
||||||
|
{
|
||||||
|
m_hotspot = hotspot;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize WaylandEglCursorLayer::size() const
|
||||||
|
{
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglCursorLayer::setSize(const QSize &size)
|
||||||
|
{
|
||||||
|
m_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<OutputLayerBeginFrameInfo> WaylandEglCursorLayer::beginFrame()
|
||||||
|
{
|
||||||
|
if (eglMakeCurrent(m_backend->eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, m_backend->context()) == EGL_FALSE) {
|
||||||
|
qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSize bufferSize = m_size.expandedTo(QSize(64, 64));
|
||||||
|
if (!m_texture || m_texture->size() != bufferSize) {
|
||||||
|
m_texture = std::make_unique<GLTexture>(GL_RGBA8, bufferSize);
|
||||||
|
m_framebuffer = std::make_unique<GLFramebuffer>(m_texture.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFramebuffer::pushFramebuffer(m_framebuffer.get());
|
||||||
|
return OutputLayerBeginFrameInfo{
|
||||||
|
.renderTarget = RenderTarget(m_framebuffer.get()),
|
||||||
|
.repaint = infiniteRegion(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaylandEglCursorLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
|
||||||
|
{
|
||||||
|
GLFramebuffer::popFramebuffer();
|
||||||
|
|
||||||
|
// Technically, we could pass a linux-dmabuf buffer, but host kwin does not support that atm.
|
||||||
|
const QImage image = m_texture->toImage().mirrored(false, true);
|
||||||
|
KWayland::Client::Buffer::Ptr buffer = m_output->backend()->display()->shmPool()->createBuffer(image);
|
||||||
|
m_output->cursor()->update(buffer, m_scale, m_hotspot);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
WaylandEglBackend::WaylandEglBackend(WaylandBackend *b)
|
WaylandEglBackend::WaylandEglBackend(WaylandBackend *b)
|
||||||
: AbstractEglBackend()
|
: AbstractEglBackend()
|
||||||
, m_backend(b)
|
, m_backend(b)
|
||||||
|
@ -196,13 +257,7 @@ WaylandEglBackend::WaylandEglBackend(WaylandBackend *b)
|
||||||
|
|
||||||
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandEglBackend::createEglWaylandOutput);
|
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandEglBackend::createEglWaylandOutput);
|
||||||
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *output) {
|
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *output) {
|
||||||
auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [output](const auto &o) {
|
m_outputs.erase(output);
|
||||||
return o->m_waylandOutput == output;
|
|
||||||
});
|
|
||||||
if (it == m_outputs.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_outputs.erase(it);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
b->setEglBackend(this);
|
b->setEglBackend(this);
|
||||||
|
@ -220,11 +275,15 @@ void WaylandEglBackend::cleanupSurfaces()
|
||||||
|
|
||||||
bool WaylandEglBackend::createEglWaylandOutput(Output *waylandOutput)
|
bool WaylandEglBackend::createEglWaylandOutput(Output *waylandOutput)
|
||||||
{
|
{
|
||||||
const auto output = std::make_shared<WaylandEglOutput>(static_cast<WaylandOutput *>(waylandOutput), this);
|
auto cursorLayer = std::make_unique<WaylandEglCursorLayer>(static_cast<WaylandOutput *>(waylandOutput), this);
|
||||||
if (!output->init()) {
|
auto primaryLayer = std::make_unique<WaylandEglPrimaryLayer>(static_cast<WaylandOutput *>(waylandOutput), this);
|
||||||
|
if (!primaryLayer->init()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_outputs.insert(waylandOutput, output);
|
m_outputs[waylandOutput] = Layers{
|
||||||
|
.primaryLayer = std::move(primaryLayer),
|
||||||
|
.cursorLayer = std::move(cursorLayer),
|
||||||
|
};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,13 +352,12 @@ bool WaylandEglBackend::initRenderingContext()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_outputs.isEmpty()) {
|
if (m_outputs.empty()) {
|
||||||
qCCritical(KWIN_WAYLAND_BACKEND) << "Create Window Surfaces failed";
|
qCCritical(KWIN_WAYLAND_BACKEND) << "Create Window Surfaces failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &firstOutput = m_outputs.first();
|
return makeCurrent();
|
||||||
return firstOutput->makeContextCurrent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandEglBackend::initBufferConfigs()
|
bool WaylandEglBackend::initBufferConfigs()
|
||||||
|
@ -340,7 +398,7 @@ bool WaylandEglBackend::initBufferConfigs()
|
||||||
std::shared_ptr<KWin::GLTexture> WaylandEglBackend::textureForOutput(KWin::Output *output) const
|
std::shared_ptr<KWin::GLTexture> WaylandEglBackend::textureForOutput(KWin::Output *output) const
|
||||||
{
|
{
|
||||||
auto texture = std::make_unique<GLTexture>(GL_RGBA8, output->pixelSize());
|
auto texture = std::make_unique<GLTexture>(GL_RGBA8, output->pixelSize());
|
||||||
GLFramebuffer::pushFramebuffer(m_outputs[output]->fbo());
|
GLFramebuffer::pushFramebuffer(m_outputs.at(output).primaryLayer->fbo());
|
||||||
GLFramebuffer renderTarget(texture.get());
|
GLFramebuffer renderTarget(texture.get());
|
||||||
renderTarget.blitFromFramebuffer(QRect(0, texture->height(), texture->width(), -texture->height()));
|
renderTarget.blitFromFramebuffer(QRect(0, texture->height(), texture->width(), -texture->height()));
|
||||||
GLFramebuffer::popFramebuffer();
|
GLFramebuffer::popFramebuffer();
|
||||||
|
@ -359,12 +417,17 @@ std::unique_ptr<SurfaceTexture> WaylandEglBackend::createSurfaceTextureWayland(S
|
||||||
|
|
||||||
void WaylandEglBackend::present(Output *output)
|
void WaylandEglBackend::present(Output *output)
|
||||||
{
|
{
|
||||||
m_outputs[output]->present();
|
m_outputs[output].primaryLayer->present();
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputLayer *WaylandEglBackend::primaryLayer(Output *output)
|
OutputLayer *WaylandEglBackend::primaryLayer(Output *output)
|
||||||
{
|
{
|
||||||
return m_outputs[output].get();
|
return m_outputs[output].primaryLayer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandEglCursorLayer *WaylandEglBackend::cursorLayer(Output *output)
|
||||||
|
{
|
||||||
|
return m_outputs[output].cursorLayer.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <wayland-egl.h>
|
#include <wayland-egl.h>
|
||||||
|
|
||||||
|
#include <KWayland/Client/buffer.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class QTemporaryFile;
|
class QTemporaryFile;
|
||||||
|
@ -34,16 +36,15 @@ class WaylandBackend;
|
||||||
class WaylandOutput;
|
class WaylandOutput;
|
||||||
class WaylandEglBackend;
|
class WaylandEglBackend;
|
||||||
|
|
||||||
class WaylandEglOutput : public OutputLayer
|
class WaylandEglPrimaryLayer : public OutputLayer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WaylandEglOutput(WaylandOutput *output, WaylandEglBackend *backend);
|
WaylandEglPrimaryLayer(WaylandOutput *output, WaylandEglBackend *backend);
|
||||||
~WaylandEglOutput() override;
|
~WaylandEglPrimaryLayer() override;
|
||||||
|
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
GLFramebuffer *fbo() const;
|
GLFramebuffer *fbo() const;
|
||||||
bool makeContextCurrent() const;
|
|
||||||
void present();
|
void present();
|
||||||
|
|
||||||
std::optional<OutputLayerBeginFrameInfo> beginFrame() override;
|
std::optional<OutputLayerBeginFrameInfo> beginFrame() override;
|
||||||
|
@ -62,6 +63,36 @@ private:
|
||||||
friend class WaylandEglBackend;
|
friend class WaylandEglBackend;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WaylandEglCursorLayer : public OutputLayer
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
WaylandEglCursorLayer(WaylandOutput *output, WaylandEglBackend *backend);
|
||||||
|
~WaylandEglCursorLayer() override;
|
||||||
|
|
||||||
|
qreal scale() const;
|
||||||
|
void setScale(qreal scale);
|
||||||
|
|
||||||
|
QPoint hotspot() const;
|
||||||
|
void setHotspot(const QPoint &hotspot);
|
||||||
|
|
||||||
|
QSize size() const;
|
||||||
|
void setSize(const QSize &size);
|
||||||
|
|
||||||
|
std::optional<OutputLayerBeginFrameInfo> beginFrame() override;
|
||||||
|
bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
WaylandOutput *m_output;
|
||||||
|
WaylandEglBackend *m_backend;
|
||||||
|
std::unique_ptr<GLFramebuffer> m_framebuffer;
|
||||||
|
std::unique_ptr<GLTexture> m_texture;
|
||||||
|
QPoint m_hotspot;
|
||||||
|
QSize m_size;
|
||||||
|
qreal m_scale = 1.0;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief OpenGL Backend using Egl on a Wayland surface.
|
* @brief OpenGL Backend using Egl on a Wayland surface.
|
||||||
*
|
*
|
||||||
|
@ -87,6 +118,7 @@ public:
|
||||||
void init() override;
|
void init() override;
|
||||||
void present(Output *output) override;
|
void present(Output *output) override;
|
||||||
OutputLayer *primaryLayer(Output *output) override;
|
OutputLayer *primaryLayer(Output *output) override;
|
||||||
|
WaylandEglCursorLayer *cursorLayer(Output *output);
|
||||||
|
|
||||||
bool havePlatformBase() const
|
bool havePlatformBase() const
|
||||||
{
|
{
|
||||||
|
@ -105,10 +137,16 @@ private:
|
||||||
|
|
||||||
void cleanupSurfaces() override;
|
void cleanupSurfaces() override;
|
||||||
|
|
||||||
void presentOnSurface(WaylandEglOutput *output, const QRegion &damagedRegion);
|
void presentOnSurface(WaylandEglPrimaryLayer *output, const QRegion &damagedRegion);
|
||||||
|
|
||||||
|
struct Layers
|
||||||
|
{
|
||||||
|
std::unique_ptr<WaylandEglPrimaryLayer> primaryLayer;
|
||||||
|
std::unique_ptr<WaylandEglCursorLayer> cursorLayer;
|
||||||
|
};
|
||||||
|
|
||||||
WaylandBackend *m_backend;
|
WaylandBackend *m_backend;
|
||||||
QMap<Output *, std::shared_ptr<WaylandEglOutput>> m_outputs;
|
std::map<Output *, Layers> m_outputs;
|
||||||
bool m_havePlatformBase;
|
bool m_havePlatformBase;
|
||||||
friend class EglWaylandTexture;
|
friend class EglWaylandTexture;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,10 +8,16 @@
|
||||||
*/
|
*/
|
||||||
#include "wayland_output.h"
|
#include "wayland_output.h"
|
||||||
#include "core/renderloop_p.h"
|
#include "core/renderloop_p.h"
|
||||||
|
#include "composite.h"
|
||||||
#include "wayland_backend.h"
|
#include "wayland_backend.h"
|
||||||
#include "wayland_display.h"
|
#include "wayland_display.h"
|
||||||
|
#include "wayland_egl_backend.h"
|
||||||
|
#include "wayland_qpainter_backend.h"
|
||||||
#include "wayland_server.h"
|
#include "wayland_server.h"
|
||||||
|
|
||||||
|
#include "kwingltexture.h"
|
||||||
|
#include "kwinglutils.h"
|
||||||
|
|
||||||
#include <KWayland/Client/compositor.h>
|
#include <KWayland/Client/compositor.h>
|
||||||
#include <KWayland/Client/pointer.h>
|
#include <KWayland/Client/pointer.h>
|
||||||
#include <KWayland/Client/pointerconstraints.h>
|
#include <KWayland/Client/pointerconstraints.h>
|
||||||
|
@ -21,6 +27,8 @@
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace KWin
|
namespace KWin
|
||||||
|
@ -73,11 +81,13 @@ void WaylandCursor::disable()
|
||||||
|
|
||||||
void WaylandCursor::update(KWayland::Client::Buffer::Ptr buffer, qreal scale, const QPoint &hotspot)
|
void WaylandCursor::update(KWayland::Client::Buffer::Ptr buffer, qreal scale, const QPoint &hotspot)
|
||||||
{
|
{
|
||||||
m_buffer = buffer;
|
if (m_buffer != buffer || m_scale != scale || m_hotspot != hotspot) {
|
||||||
m_scale = scale;
|
m_buffer = buffer;
|
||||||
m_hotspot = hotspot;
|
m_scale = scale;
|
||||||
|
m_hotspot = hotspot;
|
||||||
|
|
||||||
sync();
|
sync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaylandCursor::sync()
|
void WaylandCursor::sync()
|
||||||
|
@ -157,6 +167,11 @@ WaylandCursor *WaylandOutput::cursor() const
|
||||||
return m_cursor.get();
|
return m_cursor.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaylandBackend *WaylandOutput::backend() const
|
||||||
|
{
|
||||||
|
return m_backend;
|
||||||
|
}
|
||||||
|
|
||||||
RenderLoop *WaylandOutput::renderLoop() const
|
RenderLoop *WaylandOutput::renderLoop() const
|
||||||
{
|
{
|
||||||
return m_renderLoop.get();
|
return m_renderLoop.get();
|
||||||
|
@ -164,12 +179,17 @@ RenderLoop *WaylandOutput::renderLoop() const
|
||||||
|
|
||||||
bool WaylandOutput::setCursor(const QImage &image, const QPoint &hotspot)
|
bool WaylandOutput::setCursor(const QImage &image, const QPoint &hotspot)
|
||||||
{
|
{
|
||||||
KWayland::Client::Buffer::Ptr buffer;
|
if (m_hasPointerLock) {
|
||||||
if (!image.isNull()) {
|
return false;
|
||||||
buffer = m_backend->display()->shmPool()->createBuffer(image);
|
|
||||||
}
|
}
|
||||||
m_cursor->update(buffer, image.devicePixelRatio(), hotspot);
|
|
||||||
return !m_hasPointerLock;
|
if (WaylandEglBackend *backend = qobject_cast<WaylandEglBackend *>(Compositor::self()->backend())) {
|
||||||
|
renderCursorOpengl(backend, image, hotspot);
|
||||||
|
} else if (WaylandQPainterBackend *backend = qobject_cast<WaylandQPainterBackend *>(Compositor::self()->backend())) {
|
||||||
|
renderCursorQPainter(backend, image, hotspot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandOutput::moveCursor(const QPoint &position)
|
bool WaylandOutput::moveCursor(const QPoint &position)
|
||||||
|
@ -178,6 +198,68 @@ bool WaylandOutput::moveCursor(const QPoint &position)
|
||||||
return !m_hasPointerLock;
|
return !m_hasPointerLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WaylandOutput::renderCursorOpengl(WaylandEglBackend *backend, const QImage &image, const QPoint &hotspot)
|
||||||
|
{
|
||||||
|
WaylandEglCursorLayer *cursorLayer = backend->cursorLayer(this);
|
||||||
|
cursorLayer->setSize(image.size());
|
||||||
|
cursorLayer->setScale(image.devicePixelRatio());
|
||||||
|
cursorLayer->setHotspot(hotspot);
|
||||||
|
|
||||||
|
std::optional<OutputLayerBeginFrameInfo> beginInfo = cursorLayer->beginFrame();
|
||||||
|
if (!beginInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRect cursorRect(QPoint(0, 0), image.size() / image.devicePixelRatio());
|
||||||
|
|
||||||
|
QMatrix4x4 mvp;
|
||||||
|
mvp.ortho(QRect(QPoint(), beginInfo->renderTarget.size()));
|
||||||
|
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
GLTexture texture(image);
|
||||||
|
texture.bind();
|
||||||
|
ShaderBinder binder(ShaderTrait::MapTexture);
|
||||||
|
binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
|
||||||
|
texture.render(cursorRect, beginInfo->renderTarget.devicePixelRatio());
|
||||||
|
texture.unbind();
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
|
cursorLayer->endFrame(infiniteRegion(), infiniteRegion());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandOutput::renderCursorQPainter(WaylandQPainterBackend *backend, const QImage &image, const QPoint &hotspot)
|
||||||
|
{
|
||||||
|
WaylandQPainterCursorLayer *cursorLayer = backend->cursorLayer(this);
|
||||||
|
cursorLayer->setSize(image.size());
|
||||||
|
cursorLayer->setScale(image.devicePixelRatio());
|
||||||
|
cursorLayer->setHotspot(hotspot);
|
||||||
|
|
||||||
|
std::optional<OutputLayerBeginFrameInfo> beginInfo = cursorLayer->beginFrame();
|
||||||
|
if (!beginInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRect cursorRect(QPoint(0, 0), image.size() / image.devicePixelRatio());
|
||||||
|
|
||||||
|
QImage *c = std::get<QImage *>(beginInfo->renderTarget.nativeHandle());
|
||||||
|
c->setDevicePixelRatio(scale());
|
||||||
|
c->fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter p;
|
||||||
|
p.begin(c);
|
||||||
|
p.setWorldTransform(logicalToNativeMatrix(cursorRect, 1, transform()).toTransform());
|
||||||
|
p.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
|
p.drawImage(QPoint(0, 0), image);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
cursorLayer->endFrame(infiniteRegion(), infiniteRegion());
|
||||||
|
}
|
||||||
|
|
||||||
void WaylandOutput::init(const QSize &pixelSize, qreal scale)
|
void WaylandOutput::init(const QSize &pixelSize, qreal scale)
|
||||||
{
|
{
|
||||||
m_renderLoop->setRefreshRate(s_refreshRate);
|
m_renderLoop->setRefreshRate(s_refreshRate);
|
||||||
|
|
|
@ -32,6 +32,8 @@ namespace KWin
|
||||||
namespace Wayland
|
namespace Wayland
|
||||||
{
|
{
|
||||||
class WaylandBackend;
|
class WaylandBackend;
|
||||||
|
class WaylandEglBackend;
|
||||||
|
class WaylandQPainterBackend;
|
||||||
|
|
||||||
class WaylandCursor
|
class WaylandCursor
|
||||||
{
|
{
|
||||||
|
@ -74,6 +76,7 @@ public:
|
||||||
bool isReady() const;
|
bool isReady() const;
|
||||||
KWayland::Client::Surface *surface() const;
|
KWayland::Client::Surface *surface() const;
|
||||||
WaylandCursor *cursor() const;
|
WaylandCursor *cursor() const;
|
||||||
|
WaylandBackend *backend() const;
|
||||||
|
|
||||||
void lockPointer(KWayland::Client::Pointer *pointer, bool lock);
|
void lockPointer(KWayland::Client::Pointer *pointer, bool lock);
|
||||||
void resize(const QSize &pixelSize);
|
void resize(const QSize &pixelSize);
|
||||||
|
@ -84,6 +87,8 @@ public:
|
||||||
private:
|
private:
|
||||||
void handleConfigure(const QSize &size, KWayland::Client::XdgShellSurface::States states, quint32 serial);
|
void handleConfigure(const QSize &size, KWayland::Client::XdgShellSurface::States states, quint32 serial);
|
||||||
void updateWindowTitle();
|
void updateWindowTitle();
|
||||||
|
void renderCursorOpengl(WaylandEglBackend *backend, const QImage &image, const QPoint &hotspot);
|
||||||
|
void renderCursorQPainter(WaylandQPainterBackend *backend, const QImage &image, const QPoint &hotspot);
|
||||||
|
|
||||||
std::unique_ptr<RenderLoop> m_renderLoop;
|
std::unique_ptr<RenderLoop> m_renderLoop;
|
||||||
std::unique_ptr<KWayland::Client::Surface> m_surface;
|
std::unique_ptr<KWayland::Client::Surface> m_surface;
|
||||||
|
|
|
@ -36,26 +36,19 @@ WaylandQPainterBufferSlot::~WaylandQPainterBufferSlot()
|
||||||
buffer->setUsed(false);
|
buffer->setUsed(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
WaylandQPainterOutput::WaylandQPainterOutput(WaylandOutput *output)
|
WaylandQPainterPrimaryLayer::WaylandQPainterPrimaryLayer(WaylandOutput *output)
|
||||||
: m_waylandOutput(output)
|
: m_waylandOutput(output)
|
||||||
|
, m_pool(output->backend()->display()->shmPool())
|
||||||
{
|
{
|
||||||
|
connect(m_pool, &KWayland::Client::ShmPool::poolResized, this, &WaylandQPainterPrimaryLayer::remapBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
WaylandQPainterOutput::~WaylandQPainterOutput()
|
WaylandQPainterPrimaryLayer::~WaylandQPainterPrimaryLayer()
|
||||||
{
|
{
|
||||||
m_slots.clear();
|
m_slots.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandQPainterOutput::init(KWayland::Client::ShmPool *pool)
|
void WaylandQPainterPrimaryLayer::remapBuffer()
|
||||||
{
|
|
||||||
m_pool = pool;
|
|
||||||
|
|
||||||
connect(pool, &KWayland::Client::ShmPool::poolResized, this, &WaylandQPainterOutput::remapBuffer);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WaylandQPainterOutput::remapBuffer()
|
|
||||||
{
|
{
|
||||||
qCDebug(KWIN_WAYLAND_BACKEND) << "Remapped back buffer of surface" << m_waylandOutput->surface();
|
qCDebug(KWIN_WAYLAND_BACKEND) << "Remapped back buffer of surface" << m_waylandOutput->surface();
|
||||||
|
|
||||||
|
@ -65,7 +58,7 @@ void WaylandQPainterOutput::remapBuffer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaylandQPainterOutput::present()
|
void WaylandQPainterPrimaryLayer::present()
|
||||||
{
|
{
|
||||||
for (const auto &slot : m_slots) {
|
for (const auto &slot : m_slots) {
|
||||||
if (slot.get() == m_back) {
|
if (slot.get() == m_back) {
|
||||||
|
@ -82,12 +75,12 @@ void WaylandQPainterOutput::present()
|
||||||
s->commit();
|
s->commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
WaylandQPainterBufferSlot *WaylandQPainterOutput::back() const
|
WaylandQPainterBufferSlot *WaylandQPainterPrimaryLayer::back() const
|
||||||
{
|
{
|
||||||
return m_back;
|
return m_back;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaylandQPainterBufferSlot *WaylandQPainterOutput::acquire()
|
WaylandQPainterBufferSlot *WaylandQPainterPrimaryLayer::acquire()
|
||||||
{
|
{
|
||||||
const QSize nativeSize(m_waylandOutput->pixelSize());
|
const QSize nativeSize(m_waylandOutput->pixelSize());
|
||||||
if (m_swapchainSize != nativeSize) {
|
if (m_swapchainSize != nativeSize) {
|
||||||
|
@ -116,12 +109,12 @@ WaylandQPainterBufferSlot *WaylandQPainterOutput::acquire()
|
||||||
return m_back;
|
return m_back;
|
||||||
}
|
}
|
||||||
|
|
||||||
QRegion WaylandQPainterOutput::accumulateDamage(int bufferAge) const
|
QRegion WaylandQPainterPrimaryLayer::accumulateDamage(int bufferAge) const
|
||||||
{
|
{
|
||||||
return m_damageJournal.accumulate(bufferAge, infiniteRegion());
|
return m_damageJournal.accumulate(bufferAge, infiniteRegion());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<OutputLayerBeginFrameInfo> WaylandQPainterOutput::beginFrame()
|
std::optional<OutputLayerBeginFrameInfo> WaylandQPainterPrimaryLayer::beginFrame()
|
||||||
{
|
{
|
||||||
WaylandQPainterBufferSlot *slot = acquire();
|
WaylandQPainterBufferSlot *slot = acquire();
|
||||||
return OutputLayerBeginFrameInfo{
|
return OutputLayerBeginFrameInfo{
|
||||||
|
@ -130,12 +123,71 @@ std::optional<OutputLayerBeginFrameInfo> WaylandQPainterOutput::beginFrame()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaylandQPainterOutput::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
|
bool WaylandQPainterPrimaryLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
|
||||||
{
|
{
|
||||||
m_damageJournal.add(damagedRegion);
|
m_damageJournal.add(damagedRegion);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaylandQPainterCursorLayer::WaylandQPainterCursorLayer(WaylandOutput *output)
|
||||||
|
: m_output(output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandQPainterCursorLayer::~WaylandQPainterCursorLayer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal WaylandQPainterCursorLayer::scale() const
|
||||||
|
{
|
||||||
|
return m_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandQPainterCursorLayer::setScale(qreal scale)
|
||||||
|
{
|
||||||
|
m_scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint WaylandQPainterCursorLayer::hotspot() const
|
||||||
|
{
|
||||||
|
return m_hotspot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandQPainterCursorLayer::setHotspot(const QPoint &hotspot)
|
||||||
|
{
|
||||||
|
m_hotspot = hotspot;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize WaylandQPainterCursorLayer::size() const
|
||||||
|
{
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandQPainterCursorLayer::setSize(const QSize &size)
|
||||||
|
{
|
||||||
|
m_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<OutputLayerBeginFrameInfo> WaylandQPainterCursorLayer::beginFrame()
|
||||||
|
{
|
||||||
|
const QSize bufferSize = m_size.expandedTo(QSize(64, 64));
|
||||||
|
if (m_backingStore.size() != bufferSize) {
|
||||||
|
m_backingStore = QImage(bufferSize, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OutputLayerBeginFrameInfo{
|
||||||
|
.renderTarget = RenderTarget(&m_backingStore),
|
||||||
|
.repaint = infiniteRegion(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaylandQPainterCursorLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
|
||||||
|
{
|
||||||
|
KWayland::Client::Buffer::Ptr buffer = m_output->backend()->display()->shmPool()->createBuffer(m_backingStore);
|
||||||
|
m_output->cursor()->update(buffer, m_scale, m_hotspot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b)
|
WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b)
|
||||||
: QPainterBackend()
|
: QPainterBackend()
|
||||||
, m_backend(b)
|
, m_backend(b)
|
||||||
|
@ -147,13 +199,7 @@ WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b)
|
||||||
}
|
}
|
||||||
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandQPainterBackend::createOutput);
|
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandQPainterBackend::createOutput);
|
||||||
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *waylandOutput) {
|
connect(m_backend, &WaylandBackend::outputRemoved, this, [this](Output *waylandOutput) {
|
||||||
auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [waylandOutput](const auto &output) {
|
m_outputs.erase(waylandOutput);
|
||||||
return output->m_waylandOutput == waylandOutput;
|
|
||||||
});
|
|
||||||
if (it == m_outputs.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_outputs.erase(it);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,19 +209,26 @@ WaylandQPainterBackend::~WaylandQPainterBackend()
|
||||||
|
|
||||||
void WaylandQPainterBackend::createOutput(Output *waylandOutput)
|
void WaylandQPainterBackend::createOutput(Output *waylandOutput)
|
||||||
{
|
{
|
||||||
const auto output = std::make_shared<WaylandQPainterOutput>(static_cast<WaylandOutput *>(waylandOutput));
|
m_outputs[waylandOutput] = Layers{
|
||||||
output->init(m_backend->display()->shmPool());
|
.primaryLayer = std::make_unique<WaylandQPainterPrimaryLayer>(static_cast<WaylandOutput *>(waylandOutput)),
|
||||||
m_outputs.insert(waylandOutput, output);
|
.cursorLayer = std::make_unique<WaylandQPainterCursorLayer>(static_cast<WaylandOutput *>(waylandOutput)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaylandQPainterBackend::present(Output *output)
|
void WaylandQPainterBackend::present(Output *output)
|
||||||
{
|
{
|
||||||
m_outputs[output]->present();
|
m_outputs[output].primaryLayer->present();
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputLayer *WaylandQPainterBackend::primaryLayer(Output *output)
|
OutputLayer *WaylandQPainterBackend::primaryLayer(Output *output)
|
||||||
{
|
{
|
||||||
return m_outputs[output].get();
|
return m_outputs[output].primaryLayer.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaylandQPainterCursorLayer *WaylandQPainterBackend::cursorLayer(Output *output)
|
||||||
|
{
|
||||||
|
return m_outputs[output].cursorLayer.get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
#include "qpainterbackend.h"
|
#include "qpainterbackend.h"
|
||||||
#include "utils/damagejournal.h"
|
#include "utils/damagejournal.h"
|
||||||
|
|
||||||
|
#include <KWayland/Client/buffer.h>
|
||||||
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QWeakPointer>
|
#include <QWeakPointer>
|
||||||
|
@ -46,16 +48,15 @@ public:
|
||||||
int age = 0;
|
int age = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WaylandQPainterOutput : public OutputLayer
|
class WaylandQPainterPrimaryLayer : public OutputLayer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WaylandQPainterOutput(WaylandOutput *output);
|
WaylandQPainterPrimaryLayer(WaylandOutput *output);
|
||||||
~WaylandQPainterOutput() override;
|
~WaylandQPainterPrimaryLayer() override;
|
||||||
|
|
||||||
std::optional<OutputLayerBeginFrameInfo> beginFrame() override;
|
std::optional<OutputLayerBeginFrameInfo> beginFrame() override;
|
||||||
bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override;
|
bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override;
|
||||||
|
|
||||||
bool init(KWayland::Client::ShmPool *pool);
|
|
||||||
void remapBuffer();
|
void remapBuffer();
|
||||||
|
|
||||||
WaylandQPainterBufferSlot *back() const;
|
WaylandQPainterBufferSlot *back() const;
|
||||||
|
@ -77,6 +78,34 @@ private:
|
||||||
friend class WaylandQPainterBackend;
|
friend class WaylandQPainterBackend;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WaylandQPainterCursorLayer : public OutputLayer
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WaylandQPainterCursorLayer(WaylandOutput *output);
|
||||||
|
~WaylandQPainterCursorLayer() override;
|
||||||
|
|
||||||
|
qreal scale() const;
|
||||||
|
void setScale(qreal scale);
|
||||||
|
|
||||||
|
QPoint hotspot() const;
|
||||||
|
void setHotspot(const QPoint &hotspot);
|
||||||
|
|
||||||
|
QSize size() const;
|
||||||
|
void setSize(const QSize &size);
|
||||||
|
|
||||||
|
std::optional<OutputLayerBeginFrameInfo> beginFrame() override;
|
||||||
|
bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
WaylandOutput *m_output;
|
||||||
|
QImage m_backingStore;
|
||||||
|
QPoint m_hotspot;
|
||||||
|
QSize m_size;
|
||||||
|
qreal m_scale = 1.0;
|
||||||
|
};
|
||||||
|
|
||||||
class WaylandQPainterBackend : public QPainterBackend
|
class WaylandQPainterBackend : public QPainterBackend
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -86,13 +115,20 @@ public:
|
||||||
|
|
||||||
void present(Output *output) override;
|
void present(Output *output) override;
|
||||||
OutputLayer *primaryLayer(Output *output) override;
|
OutputLayer *primaryLayer(Output *output) override;
|
||||||
|
WaylandQPainterCursorLayer *cursorLayer(Output *output);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createOutput(Output *waylandOutput);
|
void createOutput(Output *waylandOutput);
|
||||||
void frameRendered();
|
void frameRendered();
|
||||||
|
|
||||||
|
struct Layers
|
||||||
|
{
|
||||||
|
std::unique_ptr<WaylandQPainterPrimaryLayer> primaryLayer;
|
||||||
|
std::unique_ptr<WaylandQPainterCursorLayer> cursorLayer;
|
||||||
|
};
|
||||||
|
|
||||||
WaylandBackend *m_backend;
|
WaylandBackend *m_backend;
|
||||||
QMap<Output *, std::shared_ptr<WaylandQPainterOutput>> m_outputs;
|
std::map<Output *, Layers> m_outputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Wayland
|
} // namespace Wayland
|
||||||
|
|
Loading…
Reference in a new issue