diff --git a/plugins/platforms/wayland/CMakeLists.txt b/plugins/platforms/wayland/CMakeLists.txt --- a/plugins/platforms/wayland/CMakeLists.txt +++ b/plugins/platforms/wayland/CMakeLists.txt @@ -2,6 +2,7 @@ logging.cpp scene_qpainter_wayland_backend.cpp wayland_backend.cpp + wayland_output.cpp ) if(HAVE_WAYLAND_EGL) diff --git a/plugins/platforms/wayland/egl_wayland_backend.h b/plugins/platforms/wayland/egl_wayland_backend.h --- a/plugins/platforms/wayland/egl_wayland_backend.h +++ b/plugins/platforms/wayland/egl_wayland_backend.h @@ -2,7 +2,8 @@ KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2013 Martin Gräßlin +Copyright 2013 Martin Gräßlin +Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,9 +31,34 @@ namespace KWin { -namespace Wayland { - class WaylandBackend; -} +namespace Wayland +{ +class WaylandBackend; +class WaylandOutput; +class EglWaylandBackend; + +class EglWaylandOutput : public QObject +{ + Q_OBJECT +public: + EglWaylandOutput(WaylandOutput *output, QObject *parent = nullptr); + ~EglWaylandOutput() = default; + + bool init(EglWaylandBackend *backend); + void updateSize(const QSize &size); + +private: + WaylandOutput *m_waylandOutput; + wl_egl_window *m_overlay = nullptr; + EGLSurface m_eglSurface = EGL_NO_SURFACE; + int m_bufferAge = 0; + /** + * @brief The damage history for the past 10 frames. + */ + QVector m_damageHistory; + + friend class EglWaylandBackend; +}; /** * @brief OpenGL Backend using Egl on a Wayland surface. @@ -50,29 +76,38 @@ { Q_OBJECT public: - EglWaylandBackend(Wayland::WaylandBackend *b); + EglWaylandBackend(WaylandBackend *b); virtual ~EglWaylandBackend(); - virtual void screenGeometryChanged(const QSize &size); - virtual SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; - virtual QRegion prepareRenderingFrame(); - virtual void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion); + void screenGeometryChanged(const QSize &size) override; + SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; + QRegion prepareRenderingFrame() override; + QRegion prepareRenderingForScreen(int screenId) override; + void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; + void endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) override; virtual bool usesOverlayWindow() const override; + bool perScreenRendering() const override; void init() override; -protected: - virtual void present(); - -private Q_SLOTS: - void overlaySizeChanged(const QSize &size); + bool havePlatformBase() const { + return m_havePlatformBase; + } private: bool initializeEgl(); bool initBufferConfigs(); bool initRenderingContext(); - bool makeContextCurrent(); - int m_bufferAge; - Wayland::WaylandBackend *m_wayland; - wl_egl_window *m_overlay; + + bool createEglWaylandOutput(WaylandOutput *output); + + void cleanupSurfaces() override; + void cleanupOutput(EglWaylandOutput *output); + + bool makeContextCurrent(EglWaylandOutput *output); + void present() override; + void presentOnSurface(EglWaylandOutput *output); + + WaylandBackend *m_backend; + QVector m_outputs; bool m_havePlatformBase; friend class EglWaylandTexture; }; @@ -90,6 +125,7 @@ EglWaylandTexture(SceneOpenGLTexture *texture, EglWaylandBackend *backend); }; -} // namespace +} +} -#endif // KWIN_EGL_ON_X_BACKEND_H +#endif diff --git a/plugins/platforms/wayland/egl_wayland_backend.cpp b/plugins/platforms/wayland/egl_wayland_backend.cpp --- a/plugins/platforms/wayland/egl_wayland_backend.cpp +++ b/plugins/platforms/wayland/egl_wayland_backend.cpp @@ -1,9 +1,9 @@ - /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2013 Martin Gräßlin +Copyright 2019 Roman Gilg +Copyright 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,57 +19,140 @@ along with this program. If not, see . *********************************************************************/ #define WL_EGL_PLATFORM 1 + #include "egl_wayland_backend.h" -// kwin + +#include "wayland_backend.h" +#include "wayland_output.h" + #include "composite.h" #include "logging.h" #include "options.h" -#include "wayland_backend.h" + #include "wayland_server.h" -#include +#include "screens.h" + // kwin libs #include + // KDE +#include #include #include + // Qt #include namespace KWin { +namespace Wayland +{ + +EglWaylandOutput::EglWaylandOutput(WaylandOutput *output, QObject *parent) + : QObject(parent) + , m_waylandOutput(output) +{ +} + +bool EglWaylandOutput::init(EglWaylandBackend *backend) +{ + auto surface = m_waylandOutput->surface(); + const QSize &size = m_waylandOutput->geometry().size(); + auto overlay = wl_egl_window_create(*surface, size.width(), size.height()); + if (!overlay) { + qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Egl window failed"; + return false; + } + m_overlay = overlay; + + EGLSurface eglSurface = EGL_NO_SURFACE; + if (backend->havePlatformBase()) { + eglSurface = eglCreatePlatformWindowSurfaceEXT(backend->eglDisplay(), backend->config(), (void *) overlay, nullptr); + } else { + eglSurface = eglCreateWindowSurface(backend->eglDisplay(), backend->config(), overlay, nullptr); + } + if (eglSurface == EGL_NO_SURFACE) { + qCCritical(KWIN_WAYLAND_BACKEND) << "Create Window Surface failed"; + return false; + } + m_eglSurface = eglSurface; + + connect(m_waylandOutput, &WaylandOutput::sizeChanged, this, &EglWaylandOutput::updateSize); + + return true; +} + +void EglWaylandOutput::updateSize(const QSize &size) +{ + wl_egl_window_resize(m_overlay, size.width(), size.height(), 0, 0); +} -EglWaylandBackend::EglWaylandBackend(Wayland::WaylandBackend *b) +EglWaylandBackend::EglWaylandBackend(WaylandBackend *b) : AbstractEglBackend() - , m_bufferAge(0) - , m_wayland(b) - , m_overlay(NULL) + , m_backend(b) { - if (!m_wayland) { + if (!m_backend) { setFailed("Wayland Backend has not been created"); return; } - qCDebug(KWIN_WAYLAND_BACKEND) << "Connected to Wayland display?" << (m_wayland->display() ? "yes" : "no" ); - if (!m_wayland->display()) { + qCDebug(KWIN_WAYLAND_BACKEND) << "Connected to Wayland display?" << (m_backend->display() ? "yes" : "no" ); + if (!m_backend->display()) { setFailed("Could not connect to Wayland compositor"); return; } - connect(m_wayland, SIGNAL(shellSurfaceSizeChanged(QSize)), SLOT(overlaySizeChanged(QSize))); + // Egl is always direct rendering setIsDirectRendering(true); + + connect(m_backend, &WaylandBackend::outputAdded, this, &EglWaylandBackend::createEglWaylandOutput); + connect(m_backend, &WaylandBackend::outputRemoved, this, + [this] (WaylandOutput *output) { + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [output] (const EglWaylandOutput *o) { + return o->m_waylandOutput == output; + } + ); + if (it == m_outputs.end()) { + return; + } + cleanupOutput(*it); + m_outputs.erase(it); + } + ); } EglWaylandBackend::~EglWaylandBackend() { cleanup(); - if (m_overlay) { - wl_egl_window_destroy(m_overlay); +} + +void EglWaylandBackend::cleanupSurfaces() +{ + for (auto o : m_outputs) { + cleanupOutput(o); + } + m_outputs.clear(); +} + +bool EglWaylandBackend::createEglWaylandOutput(WaylandOutput *waylandOutput) +{ + auto *output = new EglWaylandOutput(waylandOutput, this); + if (output->init(this)) { + return false; } + m_outputs << output; + return true; +} + +void EglWaylandBackend::cleanupOutput(EglWaylandOutput *output) +{ + wl_egl_window_destroy(output->m_overlay); } bool EglWaylandBackend::initializeEgl() { initClientExtensions(); - EGLDisplay display = m_wayland->sceneEglDisplay(); + EGLDisplay display = m_backend->sceneEglDisplay(); // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. @@ -80,9 +163,9 @@ if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_wayland"))) return false; - display = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, m_wayland->display(), nullptr); + display = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, m_backend->display(), nullptr); } else { - display = eglGetDisplay(m_wayland->display()); + display = eglGetDisplay(m_backend->display()); } } @@ -116,37 +199,37 @@ return false; } - if (!m_wayland->surface()) { - return false; - } + auto waylandOutputs = m_backend->waylandOutputs(); - const QSize &size = m_wayland->shellSurfaceSize(); - auto s = m_wayland->surface(); - connect(s, &KWayland::Client::Surface::frameRendered, Compositor::self(), &Compositor::bufferSwapComplete); - m_overlay = wl_egl_window_create(*s, size.width(), size.height()); - if (!m_overlay) { - qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Egl window failed"; + // we only allow to start with at least one output + if (waylandOutputs.isEmpty()) { return false; } - EGLSurface surface = EGL_NO_SURFACE; - if (m_havePlatformBase) - surface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *) m_overlay, nullptr); - else - surface = eglCreateWindowSurface(eglDisplay(), config(), m_overlay, nullptr); + for (auto *out : waylandOutputs) { + if (!createEglWaylandOutput(out)) { + return false; + } + } - if (surface == EGL_NO_SURFACE) { - qCCritical(KWIN_WAYLAND_BACKEND) << "Create Window Surface failed"; + if (m_outputs.isEmpty()) { + qCCritical(KWIN_WAYLAND_BACKEND) << "Create Window Surfaces failed"; return false; } - setSurface(surface); - return makeContextCurrent(); + auto *firstOutput = m_outputs.first(); + // set our first surface as the one for the abstract backend, just to make it happy + setSurface(firstOutput->m_eglSurface); + return makeContextCurrent(firstOutput); } -bool EglWaylandBackend::makeContextCurrent() +bool EglWaylandBackend::makeContextCurrent(EglWaylandOutput *output) { - if (eglMakeCurrent(eglDisplay(), surface(), surface(), context()) == EGL_FALSE) { + const EGLSurface eglSurface = output->m_eglSurface; + if (eglSurface == EGL_NO_SURFACE) { + return false; + } + if (eglMakeCurrent(eglDisplay(), eglSurface, eglSurface, context()) == EGL_FALSE) { qCCritical(KWIN_WAYLAND_BACKEND) << "Make Context Current failed"; return false; } @@ -156,6 +239,14 @@ qCWarning(KWIN_WAYLAND_BACKEND) << "Error occurred while creating context " << error; return false; } + + const QRect &v = output->m_waylandOutput->geometry(); + + qreal scale = output->m_waylandOutput->scale(); + + const QSize overall = screens()->size(); + glViewport(-v.x() * scale, (v.height() - overall.height() + v.y()) * scale, + overall.width() * scale, overall.height() * scale); return true; } @@ -189,18 +280,24 @@ void EglWaylandBackend::present() { - m_wayland->surface()->setupFrameCallback(); + for (auto *output: qAsConst(m_outputs)) { + makeContextCurrent(output); + presentOnSurface(output); + } +} + +void EglWaylandBackend::presentOnSurface(EglWaylandOutput *output) +{ + output->m_waylandOutput->surface()->setupFrameCallback(); Compositor::self()->aboutToSwapBuffers(); if (supportsBufferAge()) { - eglSwapBuffers(eglDisplay(), surface()); - eglQuerySurface(eglDisplay(), surface(), EGL_BUFFER_AGE_EXT, &m_bufferAge); - setLastDamage(QRegion()); - return; + eglSwapBuffers(eglDisplay(), output->m_eglSurface); + eglQuerySurface(eglDisplay(), output->m_eglSurface, EGL_BUFFER_AGE_EXT, &output->m_bufferAge); } else { - eglSwapBuffers(eglDisplay(), surface()); - setLastDamage(QRegion()); + eglSwapBuffers(eglDisplay(), output->m_eglSurface); } + } void EglWaylandBackend::screenGeometryChanged(const QSize &size) @@ -210,7 +307,9 @@ // TODO: base implementation in OpenGLBackend // The back buffer contents are now undefined - m_bufferAge = 0; + for (auto *output : qAsConst(m_outputs)) { + output->m_bufferAge = 0; + } } SceneOpenGLTexturePrivate *EglWaylandBackend::createBackendTexture(SceneOpenGLTexture *texture) @@ -220,71 +319,92 @@ QRegion EglWaylandBackend::prepareRenderingFrame() { - if (!lastDamage().isEmpty()) - present(); - QRegion repaint; - if (supportsBufferAge()) - repaint = accumulatedDamageHistory(m_bufferAge); eglWaitNative(EGL_CORE_NATIVE_ENGINE); startRenderTimer(); - return repaint; + return QRegion(); +} + +QRegion EglWaylandBackend::prepareRenderingForScreen(int screenId) +{ + auto *output = m_outputs.at(screenId); + makeContextCurrent(output); + if (supportsBufferAge()) { + QRegion region; + + // Note: An age of zero means the buffer contents are undefined + if (output->m_bufferAge > 0 && output->m_bufferAge <= output->m_damageHistory.count()) { + for (int i = 0; i < output->m_bufferAge - 1; i++) + region |= output->m_damageHistory[i]; + } else { + region = output->m_waylandOutput->geometry(); + } + + return region; + } + return QRegion(); } void EglWaylandBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { - if (damagedRegion.isEmpty()) { - setLastDamage(QRegion()); + Q_UNUSED(renderedRegion) + Q_UNUSED(damagedRegion) +} + +void EglWaylandBackend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion) +{ + EglWaylandOutput *output = m_outputs[screenId]; + if (damagedRegion.intersected(output->m_waylandOutput->geometry()).isEmpty() && screenId == 0) { // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. - if (!renderedRegion.isEmpty()) + if (!renderedRegion.intersected(output->m_waylandOutput->geometry()).isEmpty()) { glFlush(); + } - m_bufferAge = 1; + for (auto *o : qAsConst(m_outputs)) { + o->m_bufferAge = 1; + } return; } + presentOnSurface(output); - setLastDamage(renderedRegion); + // Save the damaged region to history + // Note: damage history is only collected for the first screen. See EglGbmBackend + // for mor information regarding this limitation. + if (supportsBufferAge() && screenId == 0) { + if (output->m_damageHistory.count() > 10) { + output->m_damageHistory.removeLast(); + } - if (!blocksForRetrace()) { - // This also sets lastDamage to empty which prevents the frame from - // being posted again when prepareRenderingFrame() is called. - present(); - } else { - // Make sure that the GPU begins processing the command stream - // now and not the next time prepareRenderingFrame() is called. - glFlush(); + output->m_damageHistory.prepend(damagedRegion.intersected(output->m_waylandOutput->geometry())); } - - // Save the damaged region to history - if (supportsBufferAge()) - addToDamageHistory(damagedRegion); } -void EglWaylandBackend::overlaySizeChanged(const QSize &size) +bool EglWaylandBackend::usesOverlayWindow() const { - wl_egl_window_resize(m_overlay, size.width(), size.height(), 0, 0); + return false; } -bool EglWaylandBackend::usesOverlayWindow() const +bool EglWaylandBackend::perScreenRendering() const { - return false; + return true; } /************************************************ * EglTexture ************************************************/ -EglWaylandTexture::EglWaylandTexture(KWin::SceneOpenGLTexture *texture, KWin::EglWaylandBackend *backend) +EglWaylandTexture::EglWaylandTexture(KWin::SceneOpenGLTexture *texture, KWin::Wayland::EglWaylandBackend *backend) : AbstractEglTexture(texture, backend) { } EglWaylandTexture::~EglWaylandTexture() = default; -} // namespace +} +} diff --git a/plugins/platforms/wayland/scene_qpainter_wayland_backend.h b/plugins/platforms/wayland/scene_qpainter_wayland_backend.h --- a/plugins/platforms/wayland/scene_qpainter_wayland_backend.h +++ b/plugins/platforms/wayland/scene_qpainter_wayland_backend.h @@ -2,7 +2,8 @@ KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2013, 2015 Martin Gräßlin +Copyright 2019 Roman Gilg +Copyright 2013, 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,6 +31,7 @@ { namespace Client { +class ShmPool; class Buffer; } } @@ -39,30 +41,62 @@ namespace Wayland { class WaylandBackend; -} +class WaylandOutput; +class WaylandQPainterBackend; + +class WaylandQPainterOutput : public QObject +{ + Q_OBJECT +public: + WaylandQPainterOutput(WaylandOutput *output, QObject *parent = nullptr); + ~WaylandQPainterOutput(); + + bool init(KWayland::Client::ShmPool *pool); + void updateSize(const QSize &size); + void remapBuffer(); + + void prepareRenderingFrame(); + void present(const QRegion &damage); + +private: + WaylandOutput *m_waylandOutput; + KWayland::Client::ShmPool *m_pool; + + QWeakPointer m_buffer; + QImage m_backBuffer; + + friend class WaylandQPainterBackend; +}; class WaylandQPainterBackend : public QObject, public QPainterBackend { Q_OBJECT public: - explicit WaylandQPainterBackend(Wayland::WaylandBackend *b); + explicit WaylandQPainterBackend(WaylandBackend *b); virtual ~WaylandQPainterBackend(); - virtual void present(int mask, const QRegion& damage) override; virtual bool usesOverlayWindow() const override; - virtual void screenGeometryChanged(const QSize &size) override; + virtual QImage *buffer() override; + QImage *bufferForScreen(int screenId) override; + + virtual void present(int mask, const QRegion& damage) override; virtual void prepareRenderingFrame() override; + virtual bool needsFullRepaint() const override; -private Q_SLOTS: - void remapBuffer(); + bool perScreenRendering() const override; + private: - Wayland::WaylandBackend *m_backend; + void createOutput(WaylandOutput *waylandOutput); + void frameRendered(); + + WaylandBackend *m_backend; bool m_needsFullRepaint; - QImage m_backBuffer; - QWeakPointer m_buffer; + + QVector m_outputs; }; +} } #endif diff --git a/plugins/platforms/wayland/scene_qpainter_wayland_backend.cpp b/plugins/platforms/wayland/scene_qpainter_wayland_backend.cpp --- a/plugins/platforms/wayland/scene_qpainter_wayland_backend.cpp +++ b/plugins/platforms/wayland/scene_qpainter_wayland_backend.cpp @@ -2,7 +2,8 @@ KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2013, 2015 Martin Gräßlin +Copyright 2019 Roman Gilg +Copyright 2013, 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,57 +19,60 @@ along with this program. If not, see . *********************************************************************/ #include "scene_qpainter_wayland_backend.h" +#include "wayland_backend.h" +#include "wayland_output.h" + #include "composite.h" #include "logging.h" -#include "wayland_backend.h" + #include #include #include namespace KWin { +namespace Wayland +{ -WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b) - : QPainterBackend() - , m_backend(b) - , m_needsFullRepaint(true) - , m_backBuffer(QImage(QSize(), QImage::Format_RGB32)) - , m_buffer() +WaylandQPainterOutput::WaylandQPainterOutput(WaylandOutput *output, QObject *parent) + : QObject(parent) + , m_waylandOutput(output) { - connect(b->shmPool(), SIGNAL(poolResized()), SLOT(remapBuffer())); - connect(b, &Wayland::WaylandBackend::shellSurfaceSizeChanged, - this, &WaylandQPainterBackend::screenGeometryChanged); - connect(b->surface(), &KWayland::Client::Surface::frameRendered, - Compositor::self(), &Compositor::bufferSwapComplete); } -WaylandQPainterBackend::~WaylandQPainterBackend() +WaylandQPainterOutput::~WaylandQPainterOutput() { if (m_buffer) { m_buffer.toStrongRef()->setUsed(false); } } -bool WaylandQPainterBackend::usesOverlayWindow() const +bool WaylandQPainterOutput::init(KWayland::Client::ShmPool *pool) { - return false; + m_pool = pool; + m_backBuffer = QImage(QSize(), QImage::Format_RGB32); + + connect(pool, &KWayland::Client::ShmPool::poolResized, this, &WaylandQPainterOutput::remapBuffer); + connect(m_waylandOutput, &WaylandOutput::sizeChanged, this, &WaylandQPainterOutput::updateSize); + + return true; } -void WaylandQPainterBackend::present(int mask, const QRegion &damage) +void WaylandQPainterOutput::remapBuffer() { - Q_UNUSED(mask) - if (m_backBuffer.isNull()) { + if (!m_buffer) { return; } - Compositor::self()->aboutToSwapBuffers(); - m_needsFullRepaint = false; - auto s = m_backend->surface(); - s->attachBuffer(m_buffer); - s->damage(damage); - s->commit(); + auto b = m_buffer.toStrongRef(); + if (!b->isUsed()){ + return; + } + const QSize size = m_backBuffer.size(); + m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_RGB32); + qCDebug(KWIN_WAYLAND_BACKEND) << "Remapped back buffer of surface" << m_waylandOutput->surface(); } -void WaylandQPainterBackend::screenGeometryChanged(const QSize &size) +void WaylandQPainterOutput::updateSize(const QSize &size) { Q_UNUSED(size) if (!m_buffer) { @@ -78,12 +82,15 @@ m_buffer.clear(); } -QImage *WaylandQPainterBackend::buffer() +void WaylandQPainterOutput::present(const QRegion &damage) { - return &m_backBuffer; + auto s = m_waylandOutput->surface(); + s->attachBuffer(m_buffer); + s->damage(damage); + s->commit(); } -void WaylandQPainterBackend::prepareRenderingFrame() +void WaylandQPainterOutput::prepareRenderingFrame() { if (m_buffer) { auto b = m_buffer.toStrongRef(); @@ -97,38 +104,107 @@ } } m_buffer.clear(); - const QSize size(m_backend->shellSurfaceSize()); - m_buffer = m_backend->shmPool()->getBuffer(size, size.width() * 4); + + const QSize size(m_waylandOutput->geometry().size()); + + m_buffer = m_pool->getBuffer(size, size.width() * 4); if (!m_buffer) { qCDebug(KWIN_WAYLAND_BACKEND) << "Did not get a new Buffer from Shm Pool"; m_backBuffer = QImage(); return; } + auto b = m_buffer.toStrongRef(); b->setUsed(true); + m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_RGB32); m_backBuffer.fill(Qt::transparent); - m_needsFullRepaint = true; - qCDebug(KWIN_WAYLAND_BACKEND) << "Created a new back buffer"; +// qCDebug(KWIN_WAYLAND_BACKEND) << "Created a new back buffer for output surface" << m_waylandOutput->surface(); } -void WaylandQPainterBackend::remapBuffer() +WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b) + : QPainterBackend() + , m_backend(b) + , m_needsFullRepaint(true) { - if (!m_buffer) { - return; + + const auto waylandOutputs = m_backend->waylandOutputs(); + for (auto *output: waylandOutputs) { + createOutput(output); } - auto b = m_buffer.toStrongRef(); - if (!b->isUsed()){ - return; + connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandQPainterBackend::createOutput); + connect(m_backend, &WaylandBackend::outputRemoved, this, + [this] (WaylandOutput *waylandOutput) { + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [waylandOutput] (WaylandQPainterOutput *output) { + return output->m_waylandOutput == waylandOutput; + } + ); + if (it == m_outputs.end()) { + return; + } + delete *it; + m_outputs.erase(it); + } + ); +} + +WaylandQPainterBackend::~WaylandQPainterBackend() +{ +} + +bool WaylandQPainterBackend::usesOverlayWindow() const +{ + return false; +} + +bool WaylandQPainterBackend::perScreenRendering() const +{ + return true; +} + +void WaylandQPainterBackend::createOutput(WaylandOutput *waylandOutput) +{ + auto *output = new WaylandQPainterOutput(waylandOutput, this); + output->init(m_backend->shmPool()); + m_outputs << output; +} + +void WaylandQPainterBackend::present(int mask, const QRegion &damage) +{ + Q_UNUSED(mask) + + Compositor::self()->aboutToSwapBuffers(); + m_needsFullRepaint = false; + + for (auto *output : m_outputs) { + output->present(damage); } - const QSize size = m_backBuffer.size(); - m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_RGB32); - qCDebug(KWIN_WAYLAND_BACKEND) << "Remapped our back buffer"; +} + +QImage *WaylandQPainterBackend::buffer() +{ + return bufferForScreen(0); +} + +QImage *WaylandQPainterBackend::bufferForScreen(int screenId) +{ + auto *output = m_outputs[screenId]; + return &output->m_backBuffer; +} + +void WaylandQPainterBackend::prepareRenderingFrame() +{ + for (auto *output : m_outputs) { + output->prepareRenderingFrame(); + } + m_needsFullRepaint = true; } bool WaylandQPainterBackend::needsFullRepaint() const { return m_needsFullRepaint; } } +} diff --git a/plugins/platforms/wayland/wayland_backend.h b/plugins/platforms/wayland/wayland_backend.h --- a/plugins/platforms/wayland/wayland_backend.h +++ b/plugins/platforms/wayland/wayland_backend.h @@ -2,7 +2,8 @@ KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2013 Martin Gräßlin +Copyright 2019 Roman Gilg +Copyright 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -43,7 +44,6 @@ class Buffer; class ShmPool; class Compositor; -class ConfinedPointer; class ConnectionThread; class EventQueue; class Keyboard; @@ -53,13 +53,15 @@ class PointerSwipeGesture; class PointerPinchGesture; class Registry; +class RelativePointer; +class RelativePointerManager; class Seat; class Shell; -class ShellSurface; +class SubCompositor; +class SubSurface; class Surface; class Touch; class XdgShell; -class XdgShellSurface; } } @@ -72,21 +74,68 @@ class WaylandBackend; class WaylandSeat; +class WaylandOutput; + +class WaylandCursor : public QObject +{ + Q_OBJECT +public: + explicit WaylandCursor(WaylandBackend *backend); + virtual ~WaylandCursor(); + + virtual void init(); + virtual void move(const QPointF &globalPosition) { + Q_UNUSED(globalPosition) + } + + void installImage(); + +protected: + void resetSurface(); + virtual void doInstallImage(wl_buffer *image, const QSize &size); + void drawSurface(wl_buffer *image, const QSize &size); + + KWayland::Client::Surface *surface() const { + return m_surface; + } + WaylandBackend *backend() const { + return m_backend; + } + +private: + WaylandBackend *m_backend; + KWayland::Client::Pointer *m_pointer; + KWayland::Client::Surface *m_surface = nullptr; +}; + +class WaylandSubSurfaceCursor : public WaylandCursor +{ + Q_OBJECT +public: + explicit WaylandSubSurfaceCursor(WaylandBackend *backend); + virtual ~WaylandSubSurfaceCursor(); + + void init() override; + + void move(const QPointF &globalPosition) override; + +private: + void changeOutput(WaylandOutput *output); + void doInstallImage(wl_buffer *image, const QSize &size) override; + void createSubSurface(); + + QPointF absoluteToRelativePosition(const QPointF &position); + WaylandOutput *m_output = nullptr; + KWayland::Client::SubSurface *m_subSurface = nullptr; +}; class WaylandSeat : public QObject { Q_OBJECT public: WaylandSeat(wl_seat *seat, WaylandBackend *backend); virtual ~WaylandSeat(); - void installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotspot); - void installCursorImage(const QImage &image, const QPoint &hotspot); - void setInstallCursor(bool install); - bool isInstallCursor() const { - return m_installCursor; - } - KWayland::Client::Pointer *pointer() const { return m_pointer; } @@ -101,25 +150,26 @@ void destroyKeyboard(); void destroyTouch(); void setupPointerGestures(); + KWayland::Client::Seat *m_seat; KWayland::Client::Pointer *m_pointer; KWayland::Client::Keyboard *m_keyboard; KWayland::Client::Touch *m_touch; - KWayland::Client::Surface *m_cursor; KWayland::Client::PointerGestures *m_gesturesInterface = nullptr; KWayland::Client::PointerPinchGesture *m_pinchGesture = nullptr; KWayland::Client::PointerSwipeGesture *m_swipeGesture = nullptr; + uint32_t m_enteredSerial; + WaylandBackend *m_backend; - bool m_installCursor; }; /** - * @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 surface and its shell mapping. - **/ +* @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 Platform { Q_OBJECT @@ -131,51 +181,77 @@ void init() override; wl_display *display(); KWayland::Client::Compositor *compositor(); + KWayland::Client::SubCompositor *subCompositor(); KWayland::Client::ShmPool *shmPool(); - KWayland::Client::Surface *surface() const; - QSize shellSurfaceSize() const; - Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QPainterBackend *createQPainterBackend() override; - QSize screenSize() const override { - return shellSurfaceSize(); + void flush(); + + WaylandSeat *seat() const { + return m_seat; + } + KWayland::Client::PointerConstraints *pointerConstraints() const { + return m_pointerConstraints; } - void flush(); + void pointerMotionRelativeToOutput(const QPointF &position, quint32 time); - void togglePointerConfinement(); + bool supportsPointerLock(); + void togglePointerLock(); + bool pointerIsLocked(); QVector supportedCompositors() const override; + void checkBufferSwap(); + + WaylandOutput* getOutputAt(const QPointF globalPosition); + Outputs outputs() const override; + Outputs enabledOutputs() const override; + QVector waylandOutputs() const { + return m_outputs; + } + Q_SIGNALS: - void shellSurfaceSizeChanged(const QSize &size); + void outputAdded(WaylandOutput *output); + void outputRemoved(WaylandOutput *output); + void systemCompositorDied(); void connectionFailed(); + + void pointerLockSupportedChanged(); + void pointerLockChanged(bool locked); + private: void initConnection(); - void createSurface(); - template - void setupSurface(T *surface); - void updateWindowTitle(); + void createOutputs(); + + void updateScreenSize(WaylandOutput *output); + void relativeMotionHandler(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp); + wl_display *m_display; KWayland::Client::EventQueue *m_eventQueue; KWayland::Client::Registry *m_registry; KWayland::Client::Compositor *m_compositor; + KWayland::Client::SubCompositor *m_subCompositor; KWayland::Client::Shell *m_shell; - KWayland::Client::Surface *m_surface; - KWayland::Client::ShellSurface *m_shellSurface; KWayland::Client::XdgShell *m_xdgShell = nullptr; - KWayland::Client::XdgShellSurface *m_xdgShellSurface = nullptr; - QScopedPointer m_seat; KWayland::Client::ShmPool *m_shm; KWayland::Client::ConnectionThread *m_connectionThreadObject; + + WaylandSeat *m_seat = nullptr; + KWayland::Client::RelativePointer *m_relativePointer = nullptr; + KWayland::Client::RelativePointerManager *m_relativePointerManager = nullptr; KWayland::Client::PointerConstraints *m_pointerConstraints = nullptr; - KWayland::Client::ConfinedPointer *m_pointerConfinement = nullptr; + QThread *m_connectionThread; - bool m_isPointerConfined = false; + QVector m_outputs; + + WaylandCursor *m_waylandCursor = nullptr; + + bool m_pointerLockRequested = false; }; inline @@ -191,15 +267,15 @@ } inline -KWayland::Client::ShmPool* WaylandBackend::shmPool() +KWayland::Client::SubCompositor *WaylandBackend::subCompositor() { - return m_shm; + return m_subCompositor; } inline -KWayland::Client::Surface *WaylandBackend::surface() const +KWayland::Client::ShmPool* WaylandBackend::shmPool() { - return m_surface; + return m_shm; } } // namespace Wayland diff --git a/plugins/platforms/wayland/wayland_backend.cpp b/plugins/platforms/wayland/wayland_backend.cpp --- a/plugins/platforms/wayland/wayland_backend.cpp +++ b/plugins/platforms/wayland/wayland_backend.cpp @@ -2,7 +2,8 @@ KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2013 Martin Gräßlin +Copyright 2019 Roman Gilg +Copyright 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,68 +18,204 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -// own #include "wayland_backend.h" -#include -// KWin -#include "cursor.h" + +#if HAVE_WAYLAND_EGL +#include "egl_wayland_backend.h" +#endif #include "logging.h" -#include "main.h" #include "scene_qpainter_wayland_backend.h" +#include "wayland_output.h" + +#include "composite.h" +#include "cursor.h" +#include "input.h" +#include "main.h" +#include "outputscreens.h" +#include "pointer_input.h" #include "screens.h" -#include "wayland_server.h" #include "wayland_cursor_theme.h" -#if HAVE_WAYLAND_EGL -#include "egl_wayland_backend.h" -#endif +#include "wayland_server.h" + +#include + #include #include #include #include #include #include #include #include -#include #include +#include #include #include #include #include +#include +#include #include #include #include -#include -#include + #include -#include -#include -// Qt #include #include -// Wayland -#include #include +#include namespace KWin { namespace Wayland { using namespace KWayland::Client; +WaylandCursor::WaylandCursor(WaylandBackend *backend) + : QObject(backend) + , m_backend(backend) +{ + resetSurface(); +} + +void WaylandCursor::resetSurface() +{ + delete m_surface; + m_surface = backend()->compositor()->createSurface(this); +} + +void WaylandCursor::init() +{ + installImage(); +} + +WaylandCursor::~WaylandCursor() +{ + delete m_surface; +} + +void WaylandCursor::installImage() +{ + const QImage image = m_backend->softwareCursor(); + if (image.isNull() || image.size().isEmpty()) { + doInstallImage(nullptr, QSize()); + return; + } + wl_buffer *imageBuffer = *(m_backend->shmPool()->createBuffer(image).data()); + doInstallImage(imageBuffer, image.size()); +} + +void WaylandCursor::doInstallImage(wl_buffer *image, const QSize &size) +{ + auto *pointer = m_backend->seat()->pointer(); + if (!pointer || !pointer->isValid()) { + return; + } + pointer->setCursor(m_surface, image ? m_backend->softwareCursorHotspot() : QPoint()); + drawSurface(image, size); +} + +void WaylandCursor::drawSurface(wl_buffer *image, const QSize &size) +{ + m_surface->attachBuffer(image); + m_surface->damage(QRect(QPoint(0,0), size)); + m_surface->commit(Surface::CommitFlag::None); + m_backend->flush(); +} + +WaylandSubSurfaceCursor::WaylandSubSurfaceCursor(WaylandBackend *backend) + : WaylandCursor(backend) +{ +} + +void WaylandSubSurfaceCursor::init() +{ + if (auto *pointer = backend()->seat()->pointer()) { + pointer->hideCursor(); + } +} + +WaylandSubSurfaceCursor::~WaylandSubSurfaceCursor() +{ + delete m_subSurface; +} + +void WaylandSubSurfaceCursor::changeOutput(WaylandOutput *output) +{ + delete m_subSurface; + m_subSurface = nullptr; + m_output = output; + if (!output) { + return; + } + createSubSurface(); + surface()->commit(); +} + +void WaylandSubSurfaceCursor::createSubSurface() +{ + if (m_subSurface) { + return; + } + if (!m_output) { + return; + } + resetSurface(); + m_subSurface = backend()->subCompositor()->createSubSurface(surface(), m_output->surface(), this); + m_subSurface->setMode(SubSurface::Mode::Desynchronized); +} + +void WaylandSubSurfaceCursor::doInstallImage(wl_buffer *image, const QSize &size) +{ + if (!image) { + delete m_subSurface; + m_subSurface = nullptr; + return; + } + createSubSurface(); + // cursor position might have changed due to different cursor hot spot + move(input()->pointer()->pos()); + drawSurface(image, size); +} + +QPointF WaylandSubSurfaceCursor::absoluteToRelativePosition(const QPointF &position) +{ + auto ret = position - m_output->geometry().topLeft() - backend()->softwareCursorHotspot(); + return ret; +} + +void WaylandSubSurfaceCursor::move(const QPointF &globalPosition) +{ + auto *output = backend()->getOutputAt(globalPosition.toPoint()); + if (!m_output || (output && m_output != output)) { + changeOutput(output); + if (!m_output) { + // cursor might be off the grid + return; + } + installImage(); + return; + } + if (!m_subSurface) { + return; + } + // place the sub-surface relative to the output it is on and factor in the hotspot + const auto relativePosition = globalPosition.toPoint() - backend()->softwareCursorHotspot() - m_output->geometry().topLeft(); + m_subSurface->setPosition(relativePosition); + Compositor::self()->addRepaintFull(); +} + WaylandSeat::WaylandSeat(wl_seat *seat, WaylandBackend *backend) : QObject(NULL) , m_seat(new Seat(this)) , m_pointer(NULL) , m_keyboard(NULL) , m_touch(nullptr) - , m_cursor(NULL) , m_enteredSerial(0) , m_backend(backend) - , m_installCursor(false) { m_seat->setup(seat); connect(m_seat, &Seat::hasKeyboardChanged, this, @@ -90,7 +227,7 @@ switch (state) { case Keyboard::KeyState::Pressed: if (key == KEY_RIGHTCTRL) { - m_backend->togglePointerConfinement(); + m_backend->togglePointerLock(); } m_backend->keyboardKeyPressed(key, time); break; @@ -123,17 +260,14 @@ m_pointer = m_seat->createPointer(this); setupPointerGestures(); connect(m_pointer, &Pointer::entered, this, - [this](quint32 serial) { + [this](quint32 serial, const QPointF &relativeToSurface) { + Q_UNUSED(relativeToSurface) m_enteredSerial = serial; - if (!m_installCursor) { - // explicitly hide cursor - m_pointer->hideCursor(); - } } ); connect(m_pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) { - m_backend->pointerMotion(relativeToSurface, time); + m_backend->pointerMotionRelativeToOutput(relativeToSurface, time); } ); connect(m_pointer, &Pointer::buttonStateChanged, this, @@ -212,6 +346,16 @@ } } +void WaylandBackend::pointerMotionRelativeToOutput(const QPointF &position, quint32 time) +{ + auto outputIt = std::find_if(m_outputs.begin(), m_outputs.end(), [this](WaylandOutput *wo) { + return wo->surface() == m_seat->pointer()->enteredSurface(); + }); + Q_ASSERT(outputIt != m_outputs.end()); + const QPointF outputPosition = (*outputIt)->geometry().topLeft() + position; + Platform::pointerMotion(outputPosition, time); +} + WaylandSeat::~WaylandSeat() { destroyPointer(); @@ -241,42 +385,6 @@ m_touch = nullptr; } -void WaylandSeat::installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotSpot) -{ - if (!m_installCursor) { - return; - } - if (!m_pointer || !m_pointer->isValid()) { - return; - } - if (!m_cursor) { - m_cursor = m_backend->compositor()->createSurface(this); - } - if (!m_cursor || !m_cursor->isValid()) { - return; - } - m_pointer->setCursor(m_cursor, hotSpot); - m_cursor->attachBuffer(image); - m_cursor->damage(QRect(QPoint(0,0), size)); - m_cursor->commit(Surface::CommitFlag::None); - m_backend->flush(); -} - -void WaylandSeat::installCursorImage(const QImage &image, const QPoint &hotSpot) -{ - if (image.isNull()) { - installCursorImage(nullptr, QSize(), QPoint()); - return; - } - installCursorImage(*(m_backend->shmPool()->createBuffer(image).data()), image.size(), hotSpot); -} - -void WaylandSeat::setInstallCursor(bool install) -{ - // TODO: remove, add? - m_installCursor = install; -} - void WaylandSeat::setupPointerGestures() { if (!m_pointer || !m_gesturesInterface) { @@ -338,39 +446,32 @@ , m_eventQueue(new EventQueue(this)) , m_registry(new Registry(this)) , m_compositor(new KWayland::Client::Compositor(this)) + , m_subCompositor(new KWayland::Client::SubCompositor(this)) , m_shell(new Shell(this)) - , m_surface(nullptr) - , m_shellSurface(NULL) - , m_seat() , m_shm(new ShmPool(this)) , m_connectionThreadObject(new ConnectionThread(nullptr)) , m_connectionThread(nullptr) { connect(this, &WaylandBackend::connectionFailed, this, &WaylandBackend::initFailed); - connect(this, &WaylandBackend::shellSurfaceSizeChanged, this, &WaylandBackend::screenSizeChanged); } WaylandBackend::~WaylandBackend() { if (m_pointerConstraints) { m_pointerConstraints->release(); } - if (m_xdgShellSurface) { - m_xdgShellSurface->release(); - } - if (m_shellSurface) { - m_shellSurface->release(); - } - if (m_surface) { - m_surface->release(); - } + delete m_waylandCursor; + + qDeleteAll(m_outputs); + if (m_xdgShell) { m_xdgShell->release(); } m_shell->release(); + m_subCompositor->release(); m_compositor->release(); m_registry->release(); - m_seat.reset(); + delete m_seat; m_shm->release(); m_eventQueue->release(); @@ -388,6 +489,11 @@ m_compositor->setup(m_registry->bindCompositor(name, 1)); } ); + connect(m_registry, &Registry::subCompositorAnnounced, this, + [this](quint32 name) { + m_subCompositor->setup(m_registry->bindSubCompositor(name, 1)); + } + ); connect(m_registry, &Registry::shellAnnounced, this, [this](quint32 name) { m_shell->setup(m_registry->bindShell(name, 1)); @@ -398,24 +504,37 @@ if (Application::usesLibinput()) { return; } - m_seat.reset(new WaylandSeat(m_registry->bindSeat(name, 2), this)); + m_seat = new WaylandSeat(m_registry->bindSeat(name, 2), this); } ); connect(m_registry, &Registry::shmAnnounced, this, [this](quint32 name) { m_shm->setup(m_registry->bindShm(name, 1)); } ); + connect(m_registry, &Registry::relativePointerManagerUnstableV1Announced, this, + [this](quint32 name, quint32 version) { + if (m_relativePointerManager) { + return; + } + m_relativePointerManager = m_registry->createRelativePointerManager(name, version, this); + if (m_pointerConstraints) { + emit pointerLockSupportedChanged(); + } + } + ); connect(m_registry, &Registry::pointerConstraintsUnstableV1Announced, this, [this](quint32 name, quint32 version) { if (m_pointerConstraints) { return; } m_pointerConstraints = m_registry->createPointerConstraints(name, version, this); - updateWindowTitle(); + if (m_relativePointerManager) { + emit pointerLockSupportedChanged(); + } } ); - connect(m_registry, &Registry::interfacesAnnounced, this, &WaylandBackend::createSurface); + connect(m_registry, &Registry::interfacesAnnounced, this, &WaylandBackend::createOutputs); connect(m_registry, &Registry::interfacesAnnounced, this, [this] { if (!m_seat) { @@ -425,25 +544,57 @@ if (gi.name == 0) { return; } - auto gesturesInterface = m_registry->createPointerGestures(gi.name, gi.version, m_seat.data()); + auto gesturesInterface = m_registry->createPointerGestures(gi.name, gi.version, m_seat); m_seat->installGesturesInterface(gesturesInterface); + + m_waylandCursor = new WaylandCursor(this); } ); if (!deviceIdentifier().isEmpty()) { m_connectionThreadObject->setSocketName(deviceIdentifier()); } connect(this, &WaylandBackend::cursorChanged, this, [this] { - if (m_seat.isNull() || !m_seat->isInstallCursor()) { + if (!m_seat) { return; } - m_seat->installCursorImage(softwareCursor(), softwareCursorHotspot()); + m_waylandCursor->installImage(); markCursorAsRendered(); } ); + connect(this, &WaylandBackend::pointerLockChanged, this, [this](bool locked) { + delete m_waylandCursor; + if (locked) { + Q_ASSERT(!m_relativePointer); + m_waylandCursor = new WaylandSubSurfaceCursor(this); + m_waylandCursor->move(input()->pointer()->pos()); + m_relativePointer = m_relativePointerManager->createRelativePointer(m_seat->pointer(), this); + if (!m_relativePointer->isValid()) { + return; + } + connect(m_relativePointer, &RelativePointer::relativeMotion, + this, &WaylandBackend::relativeMotionHandler); + } else { + delete m_relativePointer; + m_relativePointer = nullptr; + m_waylandCursor = new WaylandCursor(this); + } + m_waylandCursor->init(); + }); initConnection(); } +void WaylandBackend::relativeMotionHandler(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp) +{ + Q_UNUSED(deltaNonAccelerated) + Q_ASSERT(m_waylandCursor); + + const auto oldGlobalPos = input()->pointer()->pos(); + const QPointF newPos = oldGlobalPos + QPointF(delta.width(), delta.height()); + m_waylandCursor->move(newPos); + Platform::pointerMotion(newPos, timestamp); +} + void WaylandBackend::initConnection() { connect(m_connectionThreadObject, &ConnectionThread::connected, this, @@ -461,29 +612,19 @@ [this]() { setReady(false); emit systemCompositorDied(); - m_seat.reset(); + delete m_seat; m_shm->destroy(); - if (m_xdgShellSurface) { - m_xdgShellSurface->destroy(); - delete m_xdgShellSurface; - m_xdgShellSurface = nullptr; - } - if (m_shellSurface) { - m_shellSurface->destroy(); - delete m_shellSurface; - m_shellSurface = nullptr; - } - if (m_surface) { - m_surface->destroy(); - delete m_surface; - m_surface = nullptr; - } + + qDeleteAll(m_outputs); + m_outputs.clear(); + if (m_shell) { m_shell->destroy(); } if (m_xdgShell) { m_xdgShell->destroy(); } + m_subCompositor->destroy(); m_compositor->destroy(); m_registry->destroy(); m_eventQueue->destroy(); @@ -501,71 +642,90 @@ m_connectionThreadObject->initConnection(); } -void WaylandBackend::createSurface() +void WaylandBackend::updateScreenSize(WaylandOutput *output) +{ + auto it = std::find(m_outputs.begin(), m_outputs.end(), output); + + int nextLogicalPosition = output->geometry().topRight().x(); + while (++it != m_outputs.end()) { + const QRect geo = (*it)->geometry(); + (*it)->setGeometry(QPoint(nextLogicalPosition, 0), geo.size()); + nextLogicalPosition = geo.topRight().x(); + } +} + +void WaylandBackend::createOutputs() { - m_surface = m_compositor->createSurface(this); - if (!m_surface || !m_surface->isValid()) { - qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; - return; - } using namespace KWayland::Client; - auto iface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); - if (iface.name != 0) { - auto manager = m_registry->createServerSideDecorationManager(iface.name, iface.version, this); - auto decoration = manager->create(m_surface, this); - connect(decoration, &ServerSideDecoration::modeChanged, this, - [this, decoration] { - if (decoration->mode() != ServerSideDecoration::Mode::Server) { - decoration->requestMode(ServerSideDecoration::Mode::Server); - } - } - ); - } - if (m_seat) { - m_seat->setInstallCursor(true); - } - // check for xdg shell - auto xdgIface = m_registry->interface(Registry::Interface::XdgShellUnstableV6); + + const auto ssdManagerIface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); + ServerSideDecorationManager *ssdManager = ssdManagerIface.name == 0 ? nullptr : + m_registry->createServerSideDecorationManager(ssdManagerIface.name, ssdManagerIface.version, this); + + + const auto xdgIface = m_registry->interface(Registry::Interface::XdgShellUnstableV6); if (xdgIface.name != 0) { m_xdgShell = m_registry->createXdgShell(xdgIface.name, xdgIface.version, this); + } + + // 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 int pixelWidth = initialWindowSize().width() * initialOutputScale() + 0.5; + const int pixelHeight = initialWindowSize().height() * initialOutputScale() + 0.5; + const int logicalWidth = initialWindowSize().width(); + + int logicalWidthSum = 0; + for (int i = 0; i < initialOutputCount(); i++) { + auto surface = m_compositor->createSurface(this); + if (!surface || !surface->isValid()) { + qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; + return; + } + + if (ssdManager) { + auto decoration = ssdManager->create(surface, this); + connect(decoration, &ServerSideDecoration::modeChanged, this, + [this, decoration] { + if (decoration->mode() != ServerSideDecoration::Mode::Server) { + decoration->requestMode(ServerSideDecoration::Mode::Server); + } + } + ); + } + + WaylandOutput *waylandOutput = nullptr; + if (m_xdgShell && m_xdgShell->isValid()) { - m_xdgShellSurface = m_xdgShell->createSurface(m_surface, this); - connect(m_xdgShellSurface, &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit); - setupSurface(m_xdgShellSurface); + waylandOutput = new XdgShellOutput(surface, m_xdgShell, this, i+1); + } else if (m_shell->isValid()) { + waylandOutput = new ShellOutput(surface, m_shell, this); + } + + if (!waylandOutput) { + qCCritical(KWIN_WAYLAND_BACKEND) << "Binding to all shell interfaces failed for output" << i; return; } - } - if (m_shell->isValid()) { - m_shellSurface = m_shell->createSurface(m_surface, this); - setupSurface(m_shellSurface); - m_shellSurface->setToplevel(); - } -} -template -void WaylandBackend::setupSurface(T *surface) -{ - connect(surface, &T::sizeChanged, this, &WaylandBackend::shellSurfaceSizeChanged); - surface->setSize(initialWindowSize()); - updateWindowTitle(); - setReady(true); - emit screensQueried(); -} + waylandOutput->setScale(initialOutputScale()); + waylandOutput->setGeometry(QPoint(logicalWidthSum, 0), QSize(pixelWidth, pixelHeight)); -QSize WaylandBackend::shellSurfaceSize() const -{ - if (m_shellSurface) { - return m_shellSurface->size(); - } - if (m_xdgShellSurface) { - return m_xdgShellSurface->size(); + connect(waylandOutput, &WaylandOutput::sizeChanged, this, [this, waylandOutput](const QSize &size) { + Q_UNUSED(size) + updateScreenSize(waylandOutput); + Compositor::self()->addRepaintFull(); + }); + connect(waylandOutput, &WaylandOutput::frameRendered, this, &WaylandBackend::checkBufferSwap); + + logicalWidthSum += logicalWidth; + m_outputs << waylandOutput; } - return QSize(); + setReady(true); + emit screensQueried(); } Screens *WaylandBackend::createScreens(QObject *parent) { - return new BasicScreens(this, parent); + return new OutputScreens(this, parent); } OpenGLBackend *WaylandBackend::createOpenGLBackend() @@ -582,75 +742,85 @@ return new WaylandQPainterBackend(this); } +void WaylandBackend::checkBufferSwap() +{ + const bool allRendered = std::all_of(m_outputs.begin(), m_outputs.end(), [](WaylandOutput *o) { + return o->rendered(); + }); + if (!allRendered) { + // need to wait more + // TODO: what if one does not need to be rendered (no damage)? + return; + } + + for (auto *output : m_outputs) { + if (!output->rendered()) { + return; + } + } + Compositor::self()->bufferSwapComplete(); + + for (auto *output : m_outputs) { + output->resetRendered(); + } +} + void WaylandBackend::flush() { if (m_connectionThreadObject) { m_connectionThreadObject->flush(); } } -void WaylandBackend::togglePointerConfinement() +WaylandOutput* WaylandBackend::getOutputAt(const QPointF globalPosition) +{ + const auto pos = globalPosition.toPoint(); + auto checkPosition = [pos](WaylandOutput *output) { + return output->geometry().contains(pos); + }; + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), checkPosition); + return it == m_outputs.end() ? nullptr : *it; +} + +bool WaylandBackend::supportsPointerLock() +{ + return m_pointerConstraints && m_relativePointerManager; +} + +void WaylandBackend::togglePointerLock() { if (!m_pointerConstraints) { return; } - if (!m_seat) { + if (!m_relativePointerManager) { return; } - auto p = m_seat->pointer(); - if (!p) { + if (!m_seat) { return; } - if (!m_surface) { + auto pointer = m_seat->pointer(); + if (!pointer) { return; } - if (m_pointerConfinement && m_isPointerConfined) { - delete m_pointerConfinement; - m_pointerConfinement = nullptr; - m_isPointerConfined = false; - updateWindowTitle(); - flush(); - return; - } else if (m_pointerConfinement) { + if (m_outputs.isEmpty()) { return; } - m_pointerConfinement = m_pointerConstraints->confinePointer(m_surface, p, nullptr, PointerConstraints::LifeTime::Persistent, this); - connect(m_pointerConfinement, &ConfinedPointer::confined, this, - [this] { - m_isPointerConfined = true; - updateWindowTitle(); - } - ); - connect(m_pointerConfinement, &ConfinedPointer::unconfined, this, - [this] { - m_isPointerConfined = false; - updateWindowTitle(); - } - ); - updateWindowTitle(); + + for (auto output : m_outputs) { + output->lockPointer(m_seat->pointer(), !m_pointerLockRequested); + } + m_pointerLockRequested = !m_pointerLockRequested; flush(); } -void WaylandBackend::updateWindowTitle() +bool WaylandBackend::pointerIsLocked() { - if (!m_xdgShellSurface) { - return; - } - QString grab; - if (m_isPointerConfined) { - grab = i18n("Press right control to ungrab pointer"); - } else { - if (!m_pointerConfinement && m_pointerConstraints) { - grab = i18n("Press right control key to grab pointer"); + for (auto *output : m_outputs) { + if (output->pointerIsLocked()) { + return true; } } - const QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument", - "KDE Wayland Compositor (%1)", waylandServer()->display()->socketName()); - if (grab.isEmpty()) { - m_xdgShellSurface->setTitle(title); - } else { - m_xdgShellSurface->setTitle(title + QStringLiteral(" - ") + grab); - } + return false; } QVector WaylandBackend::supportedCompositors() const @@ -665,6 +835,16 @@ #endif } +Outputs WaylandBackend::outputs() const +{ + return m_outputs; +} + +Outputs WaylandBackend::enabledOutputs() const +{ + // all outputs are enabled + return m_outputs; +} } diff --git a/plugins/platforms/wayland/wayland_output.h b/plugins/platforms/wayland/wayland_output.h new file mode 100644 --- /dev/null +++ b/plugins/platforms/wayland/wayland_output.h @@ -0,0 +1,130 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_WAYLAND_OUTPUT_H +#define KWIN_WAYLAND_OUTPUT_H + +#include "abstract_output.h" + +#include + +#include + +namespace KWayland +{ +namespace Client +{ +class Surface; + +class Shell; +class ShellSurface; + +class Pointer; +class LockedPointer; +} +} + +namespace KWin +{ +namespace Wayland +{ +class WaylandBackend; + +class WaylandOutput : public AbstractOutput +{ + Q_OBJECT +public: + explicit WaylandOutput(KWayland::Client::Surface *surface, QObject *parent = nullptr); + ~WaylandOutput(); + + virtual void lockPointer(KWayland::Client::Pointer *pointer, bool lock) { + Q_UNUSED(pointer) + Q_UNUSED(lock) + } + + virtual bool pointerIsLocked() { return false; } + + QSize pixelSize() const override; + + /** + * @brief defines the geometry of the output + * @param logicalPosition top left position of the output in compositor space + * @param pixelSize output size as seen from the outside + */ + void setGeometry(const QPoint &logicalPosition, const QSize &pixelSize); + + KWayland::Client::Surface* surface() const { + return m_surface; + } + + bool rendered() const { + return m_rendered; + } + void resetRendered() { + m_rendered = false; + } + +Q_SIGNALS: + void sizeChanged(const QSize &size); + void frameRendered(); + +private: + KWayland::Client::Surface *m_surface; + + QSize m_pixelSize; + bool m_rendered = false; +}; + +class ShellOutput : public WaylandOutput +{ +public: + ShellOutput(KWayland::Client::Surface *surface, + KWayland::Client::Shell *shell, + WaylandBackend *backend); + ~ShellOutput(); + +private: + KWayland::Client::ShellSurface *m_shellSurface = nullptr; +}; + +class XdgShellOutput : public WaylandOutput +{ +public: + XdgShellOutput(KWayland::Client::Surface *surface, + KWayland::Client::XdgShell *xdgShell, + WaylandBackend *backend, int number); + ~XdgShellOutput(); + + void lockPointer(KWayland::Client::Pointer *pointer, bool lock) override; + +private: + void handleConfigure(const QSize &size, KWayland::Client::XdgShellSurface::States states, quint32 serial); + void updateWindowTitle(); + + KWayland::Client::XdgShellSurface *m_xdgShellSurface = nullptr; + WaylandBackend *m_backend; + int m_number; + KWayland::Client::LockedPointer *m_pointerLock = nullptr; + bool m_hasPointerLock = false; +}; + +} +} + +#endif diff --git a/plugins/platforms/wayland/wayland_output.cpp b/plugins/platforms/wayland/wayland_output.cpp new file mode 100644 --- /dev/null +++ b/plugins/platforms/wayland/wayland_output.cpp @@ -0,0 +1,185 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#include "wayland_output.h" +#include "wayland_backend.h" + +#include "wayland_server.h" + +#include +#include +#include + +#include + +#include + +namespace KWin +{ +namespace Wayland +{ + +using namespace KWayland::Client; + +WaylandOutput::WaylandOutput(Surface *surface, QObject *parent) + : AbstractOutput(parent), + m_surface(surface) +{ + connect(surface, &Surface::frameRendered, [this] { + m_rendered = true; + emit frameRendered(); + }); +} + +WaylandOutput::~WaylandOutput() +{ + m_surface->destroy(); + delete m_surface; +} + +QSize WaylandOutput::pixelSize() const +{ + return m_pixelSize; +} + +void WaylandOutput::setGeometry(const QPoint &logicalPosition, const QSize &pixelSize) +{ + m_pixelSize = pixelSize; + setRawPhysicalSize(pixelSize); + setGlobalPos(logicalPosition); +} + +ShellOutput::ShellOutput(Surface *surface, Shell *shell, WaylandBackend *backend) + : WaylandOutput(surface, backend) +{ + auto shellSurface = shell->createSurface(surface, this); + shellSurface->setToplevel(); +} + +ShellOutput::~ShellOutput() +{ + m_shellSurface->destroy(); + delete m_shellSurface; +} + +XdgShellOutput::XdgShellOutput(Surface *surface, XdgShell *xdgShell, WaylandBackend *backend, int number) + : WaylandOutput(surface, backend) + , m_backend(backend) + , m_number(number) +{ + m_xdgShellSurface = xdgShell->createSurface(surface, this); + updateWindowTitle(); + + connect(m_xdgShellSurface, &XdgShellSurface::configureRequested, this, &XdgShellOutput::handleConfigure); + connect(m_xdgShellSurface, &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit); + + connect(backend, &WaylandBackend::pointerLockSupportedChanged, this, &XdgShellOutput::updateWindowTitle); + connect(backend, &WaylandBackend::pointerLockChanged, this, [this](bool locked) { + if (locked) { + if (!m_hasPointerLock) { + // some other output has locked the pointer + // this surface can stop trying to lock the pointer + lockPointer(nullptr, false); + // set it true for the other surface + m_hasPointerLock = true; + } + } else { + // just try unlocking + lockPointer(nullptr, false); + } + updateWindowTitle(); + }); +} + +XdgShellOutput::~XdgShellOutput() +{ + m_xdgShellSurface->destroy(); + delete m_xdgShellSurface; +} + +void XdgShellOutput::handleConfigure(const QSize &size, XdgShellSurface::States states, quint32 serial) +{ + Q_UNUSED(states); + if (size.width() == 0 || size.height() == 0) { + return; + } + setGeometry(geometry().topLeft(), size); + m_xdgShellSurface->ackConfigure(serial); + emit sizeChanged(size); +} + +void XdgShellOutput::updateWindowTitle() +{ + QString grab; + if (m_hasPointerLock) { + grab = i18n("Press right control to ungrab pointer"); + } else if (m_backend->pointerConstraints()) { + grab = i18n("Press right control key to grab pointer"); + } + const QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument", + "KDE Wayland Compositor #%1 (%2)", m_number, waylandServer()->display()->socketName()); + + if (grab.isEmpty()) { + m_xdgShellSurface->setTitle(title); + } else { + m_xdgShellSurface->setTitle(title + QStringLiteral(" — ") + grab); + } +} + +void XdgShellOutput::lockPointer(Pointer *pointer, bool lock) +{ + if (!lock) { + const bool surfaceWasLocked = m_pointerLock && m_hasPointerLock; + delete m_pointerLock; + m_pointerLock = nullptr; + m_hasPointerLock = false; + if (surfaceWasLocked) { + emit m_backend->pointerLockChanged(false); + } + return; + } + + Q_ASSERT(!m_pointerLock); + m_pointerLock = m_backend->pointerConstraints()->lockPointer(surface(), pointer, nullptr, + PointerConstraints::LifeTime::OneShot, + this); + if (!m_pointerLock->isValid()) { + delete m_pointerLock; + m_pointerLock = nullptr; + return; + } + connect(m_pointerLock, &LockedPointer::locked, this, + [this] { + m_hasPointerLock = true; + emit m_backend->pointerLockChanged(true); + } + ); + connect(m_pointerLock, &LockedPointer::unlocked, this, + [this] { + delete m_pointerLock; + m_pointerLock = nullptr; + m_hasPointerLock = false; + emit m_backend->pointerLockChanged(false); + } + ); +} + +} +}