summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Klokkhammer Helsing <johan.helsing@qt.io>2018-09-24 13:04:51 +0200
committerJohan Helsing <johan.helsing@qt.io>2018-10-26 11:05:00 +0000
commit88a0246a46c30e08e9730d16cf8739773447d058 (patch)
treef9d20ff21e77031b7ddcb33394aa0e35de491933
parentba53384387dcfd2606a513b114eff488d3fdb940 (diff)
Client: Don't attach buffers to unexposed windows
QBackingStore::flush is sometimes called with an unxeposed window, in that case, don't attach the buffer to the wl_surface immediately, as that causes protocol errors with xdg_shell. Flushed buffers are instead stored until we get the first configure event. [ChangeLog][QPA plugin][xdg-shell] Fixed a bug where buffers were sometimes attached and committed before the first configure event, causing protocol errors. Fixes: QTBUG-71345 Change-Id: If9409d97bd25f6b13940c56141920a664c349c8e Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io> Reviewed-by: David Edmundson <davidedmundson@kde.org>
-rw-r--r--src/client/qwaylandshmbackingstore.cpp3
-rw-r--r--src/client/qwaylandwindow.cpp22
-rw-r--r--src/client/qwaylandwindow_p.h4
-rw-r--r--src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp19
-rw-r--r--src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6_p.h2
-rw-r--r--src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp19
-rw-r--r--src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h2
-rw-r--r--tests/auto/client/shared/mocksurface.cpp12
-rw-r--r--tests/auto/client/shared/mocksurface.h7
-rw-r--r--tests/auto/client/shared/mockxdgshellv6.cpp14
-rw-r--r--tests/auto/client/shared/mockxdgshellv6.h6
-rw-r--r--tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp35
12 files changed, 126 insertions, 19 deletions
diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp
index ecb03c0d..3fe2ce80 100644
--- a/src/client/qwaylandshmbackingstore.cpp
+++ b/src/client/qwaylandshmbackingstore.cpp
@@ -234,8 +234,7 @@ void QWaylandShmBackingStore::flush(QWindow *window, const QRegion &region, cons
mFrontBuffer = mBackBuffer;
QMargins margins = windowDecorationMargins();
-
- waylandWindow()->commit(mFrontBuffer, region.translated(margins.left(), margins.top()));
+ waylandWindow()->safeCommit(mFrontBuffer, region.translated(margins.left(), margins.top()));
}
void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &)
diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp
index 52bee6ae..1801c2a4 100644
--- a/src/client/qwaylandwindow.cpp
+++ b/src/client/qwaylandwindow.cpp
@@ -248,6 +248,7 @@ void QWaylandWindow::reset(bool sendDestroyEvent)
}
mMask = QRegion();
+ mQueuedBuffer = nullptr;
}
QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface)
@@ -566,8 +567,29 @@ void QWaylandWindow::damage(const QRect &rect)
damage(rect.x(), rect.y(), rect.width(), rect.height());
}
+void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage)
+{
+ if (isExposed()) {
+ commit(buffer, damage);
+ } else {
+ mQueuedBuffer = buffer;
+ mQueuedBufferDamage = damage;
+ }
+}
+
+void QWaylandWindow::handleExpose(const QRegion &region)
+{
+ QWindowSystemInterface::handleExposeEvent(window(), region);
+ if (mQueuedBuffer) {
+ commit(mQueuedBuffer, mQueuedBufferDamage);
+ mQueuedBuffer = nullptr;
+ mQueuedBufferDamage = QRegion();
+ }
+}
+
void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage)
{
+ Q_ASSERT(isExposed());
if (buffer->committed()) {
qCDebug(lcWaylandBackingstore) << "Buffer already committed, ignoring.";
return;
diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h
index d11ed871..ad24b735 100644
--- a/src/client/qwaylandwindow_p.h
+++ b/src/client/qwaylandwindow_p.h
@@ -116,6 +116,8 @@ public:
using QtWayland::wl_surface::damage;
void damage(const QRect &rect);
+ void safeCommit(QWaylandBuffer *buffer, const QRegion &damage);
+ void handleExpose(const QRegion &region);
void commit(QWaylandBuffer *buffer, const QRegion &damage);
void waitForFrameSync();
@@ -231,6 +233,8 @@ protected:
Qt::WindowStates mLastReportedWindowStates = Qt::WindowNoState;
QWaylandShmBackingStore *mBackingStore = nullptr;
+ QWaylandBuffer *mQueuedBuffer = nullptr;
+ QRegion mQueuedBufferDamage;
private slots:
void handleScreenRemoved(QScreen *qScreen);
diff --git a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp
index 447e8fb6..3d3af692 100644
--- a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp
+++ b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp
@@ -258,9 +258,14 @@ void QWaylandXdgSurfaceV6::setAppId(const QString &appId)
m_toplevel->set_app_id(appId);
}
+bool QWaylandXdgSurfaceV6::isExposed() const
+{
+ return m_configured || m_pendingConfigureSerial;
+}
+
bool QWaylandXdgSurfaceV6::handleExpose(const QRegion &region)
{
- if (!m_configured && !region.isEmpty()) {
+ if (!isExposed() && !region.isEmpty()) {
m_exposeRegion = region;
return true;
}
@@ -333,10 +338,18 @@ void QWaylandXdgSurfaceV6::setPopup(QWaylandWindow *parent, QWaylandInputDevice
void QWaylandXdgSurfaceV6::zxdg_surface_v6_configure(uint32_t serial)
{
- m_window->applyConfigureWhenPossible();
m_pendingConfigureSerial = serial;
+ if (!m_configured) {
+ // We have to do the initial applyConfigure() immediately, since that is the expose.
+ applyConfigure();
+ } else {
+ // Later configures are probably resizes, so we have to queue them up for a time when we
+ // are not painting to the window.
+ m_window->applyConfigureWhenPossible();
+ }
+
if (!m_exposeRegion.isEmpty()) {
- QWindowSystemInterface::handleExposeEvent(m_window->window(), m_exposeRegion);
+ m_window->handleExpose(m_exposeRegion);
m_exposeRegion = QRegion();
}
}
diff --git a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6_p.h b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6_p.h
index 38b711f8..874dba01 100644
--- a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6_p.h
+++ b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6_p.h
@@ -85,7 +85,7 @@ public:
void setTitle(const QString &title) override;
void setAppId(const QString &appId) override;
- bool isExposed() const override { return m_configured; }
+ bool isExposed() const override;
bool handleExpose(const QRegion &) override;
bool handlesActiveState() const { return m_toplevel; }
void applyConfigure() override;
diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp
index 0756e4d0..c723192c 100644
--- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp
+++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp
@@ -292,9 +292,14 @@ void QWaylandXdgSurface::setWindowFlags(Qt::WindowFlags flags)
m_toplevel->requestWindowFlags(flags);
}
+bool QWaylandXdgSurface::isExposed() const
+{
+ return m_configured || m_pendingConfigureSerial;
+}
+
bool QWaylandXdgSurface::handleExpose(const QRegion &region)
{
- if (!m_configured && !region.isEmpty()) {
+ if (!isExposed() && !region.isEmpty()) {
m_exposeRegion = region;
return true;
}
@@ -367,10 +372,18 @@ void QWaylandXdgSurface::setPopup(QWaylandWindow *parent, QWaylandInputDevice *d
void QWaylandXdgSurface::xdg_surface_configure(uint32_t serial)
{
- m_window->applyConfigureWhenPossible();
m_pendingConfigureSerial = serial;
+ if (!m_configured) {
+ // We have to do the initial applyConfigure() immediately, since that is the expose.
+ applyConfigure();
+ } else {
+ // Later configures are probably resizes, so we have to queue them up for a time when we
+ // are not painting to the window.
+ m_window->applyConfigureWhenPossible();
+ }
+
if (!m_exposeRegion.isEmpty()) {
- QWindowSystemInterface::handleExposeEvent(m_window->window(), m_exposeRegion);
+ m_window->handleExpose(m_exposeRegion);
m_exposeRegion = QRegion();
}
}
diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h
index 45d7d4b0..5e97a34b 100644
--- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h
+++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h
@@ -89,7 +89,7 @@ public:
void setAppId(const QString &appId) override;
void setWindowFlags(Qt::WindowFlags flags) override;
- bool isExposed() const override { return m_configured; }
+ bool isExposed() const override;
bool handleExpose(const QRegion &) override;
bool handlesActiveState() const { return m_toplevel; }
void applyConfigure() override;
diff --git a/tests/auto/client/shared/mocksurface.cpp b/tests/auto/client/shared/mocksurface.cpp
index c0d51618..84dcda6b 100644
--- a/tests/auto/client/shared/mocksurface.cpp
+++ b/tests/auto/client/shared/mocksurface.cpp
@@ -74,7 +74,7 @@ void Compositor::sendShellSurfaceConfigure(void *data, const QList<QVariant> &pa
auto statesBytes = QByteArray::fromRawData(reinterpret_cast<const char *>(states.data()),
states.size() * static_cast<int>(sizeof(uint)));
toplevel->send_configure(size.width(), size.height(), statesBytes);
- toplevel->xdgSurface()->send_configure(compositor->nextSerial());
+ toplevel->xdgSurface()->sendConfigure(compositor->nextSerial());
} else if (auto wlShellSurface = surface->wlShellSurface()) {
const uint edges = 0;
wlShellSurface->send_configure(edges, size.width(), size.height());
@@ -123,13 +123,17 @@ void Surface::surface_destroy(Resource *resource)
if (m_wlShellSurface) // on wl-shell the shell surface is automatically destroyed with the surface
wl_resource_destroy(m_wlShellSurface->resource()->handle);
Q_ASSERT(!m_wlShellSurface);
- Q_ASSERT(!m_xdgToplevelV6);
+ Q_ASSERT(!m_xdgSurfaceV6);
wl_resource_destroy(resource->handle);
}
-void Surface::surface_attach(Resource *resource,
- struct wl_resource *buffer, int x, int y)
+void Surface::surface_attach(Resource *resource, struct wl_resource *buffer, int x, int y)
{
+ if (m_xdgSurfaceV6) {
+ // It's a protocol error to attach a buffer to an xdgSurface that's not configured
+ Q_ASSERT(xdgSurfaceV6()->configureSent());
+ }
+
Q_UNUSED(resource);
Q_UNUSED(x);
Q_UNUSED(y);
diff --git a/tests/auto/client/shared/mocksurface.h b/tests/auto/client/shared/mocksurface.h
index 3b0f01fd..949dc23d 100644
--- a/tests/auto/client/shared/mocksurface.h
+++ b/tests/auto/client/shared/mocksurface.h
@@ -50,7 +50,8 @@ public:
static Surface *fromResource(struct ::wl_resource *resource);
void map();
bool isMapped() const;
- XdgToplevelV6 *xdgToplevelV6() const { return m_xdgToplevelV6; }
+ XdgSurfaceV6 *xdgSurfaceV6() const { return m_xdgSurfaceV6; }
+ XdgToplevelV6 *xdgToplevelV6() const { return m_xdgSurfaceV6 ? m_xdgSurfaceV6->toplevel() : nullptr; }
WlShellSurface *wlShellSurface() const { return m_wlShellSurface; }
QSharedPointer<MockSurface> mockSurface() const { return m_mockSurface; }
@@ -69,7 +70,7 @@ protected:
void surface_commit(Resource *resource) override;
private:
wl_resource *m_buffer = nullptr;
- XdgToplevelV6 *m_xdgToplevelV6 = nullptr;
+ XdgSurfaceV6 *m_xdgSurfaceV6 = nullptr;
WlShellSurface *m_wlShellSurface = nullptr;
Compositor *m_compositor = nullptr;
@@ -77,7 +78,7 @@ private:
QList<wl_resource *> m_frameCallbackList;
bool m_mapped = false;
- friend class XdgToplevelV6;
+ friend class XdgSurfaceV6;
friend class WlShellSurface;
};
diff --git a/tests/auto/client/shared/mockxdgshellv6.cpp b/tests/auto/client/shared/mockxdgshellv6.cpp
index 5dc8da59..05eff74a 100644
--- a/tests/auto/client/shared/mockxdgshellv6.cpp
+++ b/tests/auto/client/shared/mockxdgshellv6.cpp
@@ -49,6 +49,18 @@ XdgSurfaceV6::XdgSurfaceV6(XdgShellV6 *shell, Surface *surface, wl_client *clien
, m_surface(surface)
, m_shell(shell)
{
+ m_surface->m_xdgSurfaceV6 = this;
+}
+
+XdgSurfaceV6::~XdgSurfaceV6()
+{
+ m_surface->m_xdgSurfaceV6 = nullptr;
+}
+
+void XdgSurfaceV6::sendConfigure(uint32_t serial)
+{
+ send_configure(serial);
+ m_configureSent = true;
}
void XdgSurfaceV6::zxdg_surface_v6_get_toplevel(QtWaylandServer::zxdg_surface_v6::Resource *resource, uint32_t id)
@@ -78,7 +90,6 @@ XdgToplevelV6::XdgToplevelV6(XdgSurfaceV6 *xdgSurface, wl_client *client, uint32
, m_mockToplevel(new MockXdgToplevelV6(this))
{
auto *surface = m_xdgSurface->surface();
- surface->m_xdgToplevelV6 = this;
m_xdgSurface->shell()->addToplevel(this);
surface->map();
}
@@ -86,7 +97,6 @@ XdgToplevelV6::XdgToplevelV6(XdgSurfaceV6 *xdgSurface, wl_client *client, uint32
XdgToplevelV6::~XdgToplevelV6()
{
m_xdgSurface->shell()->removeToplevel(this);
- m_xdgSurface->surface()->m_xdgToplevelV6 = nullptr;
m_mockToplevel->m_toplevel = nullptr;
}
diff --git a/tests/auto/client/shared/mockxdgshellv6.h b/tests/auto/client/shared/mockxdgshellv6.h
index bd5e1306..a238fa56 100644
--- a/tests/auto/client/shared/mockxdgshellv6.h
+++ b/tests/auto/client/shared/mockxdgshellv6.h
@@ -46,8 +46,13 @@ class XdgSurfaceV6 : public QtWaylandServer::zxdg_surface_v6
{
public:
XdgSurfaceV6(XdgShellV6 *shell, Surface *surface, wl_client *client, uint32_t id);
+ ~XdgSurfaceV6() override;
XdgShellV6 *shell() const { return m_shell; }
Surface *surface() const { return m_surface; }
+ XdgToplevelV6 *toplevel() const { return m_toplevel; }
+
+ void sendConfigure(uint32_t serial);
+ bool configureSent() const { return m_configureSent; }
protected:
void zxdg_surface_v6_destroy_resource(Resource *) override { delete this; }
@@ -59,6 +64,7 @@ private:
Surface *m_surface = nullptr;
XdgToplevelV6 *m_toplevel = nullptr;
XdgShellV6 *m_shell = nullptr;
+ bool m_configureSent = false;
friend class XdgToplevelV6;
};
diff --git a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
index 91679cb5..3fd8153e 100644
--- a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
+++ b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
@@ -101,6 +101,7 @@ private slots:
void windowStateChangedEvents();
void windowGeometrySimple();
void windowGeometryFixed();
+ void flushUnconfiguredXdgSurface();
private:
MockCompositor *m_compositor = nullptr;
@@ -360,6 +361,40 @@ void tst_WaylandClientXdgShellV6::windowGeometryFixed()
QCOMPARE(geometrySpy.takeFirst().at(0).toRect().size(), initialWindowGeometry.size());
}
+void tst_WaylandClientXdgShellV6::flushUnconfiguredXdgSurface()
+{
+ TestWindow window;
+ window.show();
+
+ QSharedPointer<MockSurface> surface;
+ QTRY_VERIFY(surface = m_compositor->surface());
+
+ // Paint and flush some magenta
+ QBackingStore backingStore(&window);
+ QRect rect(QPoint(), window.size());
+ backingStore.resize(rect.size());
+ backingStore.beginPaint(rect);
+ QColor color = Qt::magenta;
+ QPainter p(backingStore.paintDevice());
+ p.fillRect(rect, color);
+ p.end();
+ backingStore.endPaint();
+ backingStore.flush(rect);
+
+ // We're not allowed to send buffer on this surface since it isn't yet configured.
+ // So, from the compositor's point of view there should be no buffer data yet.
+ m_compositor->processWaylandEvents();
+ QVERIFY(surface->image.isNull());
+ QVERIFY(!window.isExposed());
+
+ // Finally sending the configure should trigger an attach and commit with the
+ // right buffer.
+ m_compositor->sendShellSurfaceConfigure(surface);
+ QTRY_COMPARE(surface->image.size(), window.frameGeometry().size());
+ QTRY_COMPARE(surface->image.pixel(window.frameMargins().left(), window.frameMargins().top()), color.rgba());
+ QVERIFY(window.isExposed());
+}
+
int main(int argc, char **argv)
{
setenv("XDG_RUNTIME_DIR", ".", 1);