From 71f68537466cf6f42981a1eb4929ac56c9ef12fe Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 5 May 2021 20:49:26 +0300 Subject: [PATCH 01/57] Client: Announce an output after receiving more complete state Output initialization is not atomic, meaning that the compositor may process a wl_output bind request in one event loop cycle, and the xdg_output_manager.get_xdg_output in another event loop cycle. This means that xdg_output properties may arrive in another wl_output done frame. Prior to xdg-output v3, that wasn't an issue because the compositor is required to send an xdg_output.done event after sending xdg_output properties. Starting with v3, the compositor may choose not to send an xdg_output.done event after sending xdg_output properties. Therefore, as is, QtWayland may announce an output with bad logical geometry or even worse without name assigned by the compositor. Unfortunately, that breaks applications such as plasmashell. Plasma uses output names as a criterion to determine what kind of contents should be displayed on a particular output. In order to fix the initialization sequence, this change makes every QWaylandScreen track processed events. After all required events have been received, the screen can be announced to the rest of Qt. Change-Id: If5da747edd7af277ec1364cbea105c6994f47402 Reviewed-by: David Edmundson (cherry picked from commit 69ea480f2e53ad4a5bbca78cde044eb8d4c48896) Original Ticket: https://codereview.qt-project.org/c/qt/qtwayland/+/347774 CCBUG: 435124 --- src/client/qwaylandscreen.cpp | 32 +++++++++++++++++++++++--------- src/client/qwaylandscreen_p.h | 10 ++++++++-- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/client/qwaylandscreen.cpp b/src/client/qwaylandscreen.cpp index 6cb337de..7c2d9be3 100644 --- a/src/client/qwaylandscreen.cpp +++ b/src/client/qwaylandscreen.cpp @@ -72,7 +72,7 @@ QWaylandScreen::QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uin qCWarning(lcQpaWayland) << "wl_output done event not supported by compositor," << "QScreen may not work correctly"; mWaylandDisplay->forceRoundTrip(); // Give the compositor a chance to send geometry etc. - mOutputDone = true; // Fake the done event + mProcessedEvents |= OutputDoneEvent; // Fake the done event maybeInitialize(); } } @@ -83,14 +83,25 @@ QWaylandScreen::~QWaylandScreen() zxdg_output_v1::destroy(); } +uint QWaylandScreen::requiredEvents() const +{ + uint ret = OutputDoneEvent; + + if (mWaylandDisplay->xdgOutputManager()) { + ret |= XdgOutputNameEvent; + + if (mWaylandDisplay->xdgOutputManager()->version() < 3) + ret |= XdgOutputDoneEvent; + } + return ret; +} + void QWaylandScreen::maybeInitialize() { Q_ASSERT(!mInitialized); - if (!mOutputDone) - return; - - if (mWaylandDisplay->xdgOutputManager() && !mXdgOutputDone) + const uint requiredEvents = this->requiredEvents(); + if ((mProcessedEvents & requiredEvents) != requiredEvents) return; mInitialized = true; @@ -276,9 +287,8 @@ void QWaylandScreen::output_scale(int32_t factor) void QWaylandScreen::output_done() { - mOutputDone = true; - if (zxdg_output_v1::isInitialized() && mWaylandDisplay->xdgOutputManager()->version() >= 3) - mXdgOutputDone = true; + mProcessedEvents |= OutputDoneEvent; + if (mInitialized) { updateOutputProperties(); if (zxdg_output_v1::isInitialized()) @@ -339,7 +349,7 @@ void QWaylandScreen::zxdg_output_v1_done() if (Q_UNLIKELY(mWaylandDisplay->xdgOutputManager()->version() >= 3)) qWarning(lcQpaWayland) << "zxdg_output_v1.done received on version 3 or newer, this is most likely a bug in the compositor"; - mXdgOutputDone = true; + mProcessedEvents |= XdgOutputDoneEvent; if (mInitialized) updateXdgOutputProperties(); else @@ -348,7 +358,11 @@ void QWaylandScreen::zxdg_output_v1_done() void QWaylandScreen::zxdg_output_v1_name(const QString &name) { + if (Q_UNLIKELY(mInitialized)) + qWarning(lcQpaWayland) << "zxdg_output_v1.name received after output has been initialized, this is most likely a bug in the compositor"; + mOutputName = name; + mProcessedEvents |= XdgOutputNameEvent; } void QWaylandScreen::updateXdgOutputProperties() diff --git a/src/client/qwaylandscreen_p.h b/src/client/qwaylandscreen_p.h index df1c94f2..050cfdc0 100644 --- a/src/client/qwaylandscreen_p.h +++ b/src/client/qwaylandscreen_p.h @@ -116,6 +116,13 @@ public: static QWaylandScreen *fromWlOutput(::wl_output *output); private: + enum Event : uint { + XdgOutputDoneEvent = 0x1, + OutputDoneEvent = 0x2, + XdgOutputNameEvent = 0x4, + }; + uint requiredEvents() const; + void output_mode(uint32_t flags, int width, int height, int refresh) override; void output_geometry(int32_t x, int32_t y, int32_t width, int32_t height, @@ -148,8 +155,7 @@ private: QSize mPhysicalSize; QString mOutputName; Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; - bool mOutputDone = false; - bool mXdgOutputDone = false; + uint mProcessedEvents = 0; bool mInitialized = false; #if QT_CONFIG(cursor) -- 2.49.0 From 32bacc3b758a25a856e26f01f5d054d6ef2c0652 Mon Sep 17 00:00:00 2001 From: Jaeyoon Jung Date: Mon, 15 Feb 2021 08:31:06 +0900 Subject: [PATCH 02/57] Fix issue with repeated window size changes Check if the new window size is different from the size requested previously before calling wl_egl_window_resize. It addresses the issue where repeated setGeometry calls between two sizes might not work as expected. The problem occurs when wl_egl_window_get_attached_size does not get the same size that was requested by the previous setGeometry call. If the returned size happened to match the new size instead, we would mistakenly skip the resize. Change-Id: Iafe4a91cc707f854b9099b6109b6be1423d7bd29 Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 14d066c61025e548227ccd8d655e80ffa31fa15e) --- .../client/wayland-egl/qwaylandeglwindow.cpp | 4 +++- .../client/wayland-egl/qwaylandeglwindow.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp index e00c28c3..64f7caeb 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp +++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp @@ -122,14 +122,16 @@ void QWaylandEglWindow::updateSurface(bool create) if (!disableResizeCheck) { wl_egl_window_get_attached_size(m_waylandEglWindow, ¤t_width, ¤t_height); } - if (disableResizeCheck || (current_width != sizeWithMargins.width() || current_height != sizeWithMargins.height())) { + if (disableResizeCheck || (current_width != sizeWithMargins.width() || current_height != sizeWithMargins.height()) || m_requestedSize != sizeWithMargins) { wl_egl_window_resize(m_waylandEglWindow, sizeWithMargins.width(), sizeWithMargins.height(), mOffset.x(), mOffset.y()); + m_requestedSize = sizeWithMargins; mOffset = QPoint(); m_resize = true; } } else if (create && wlSurface()) { m_waylandEglWindow = wl_egl_window_create(wlSurface(), sizeWithMargins.width(), sizeWithMargins.height()); + m_requestedSize = sizeWithMargins; } if (!m_eglSurface && m_waylandEglWindow && create) { diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h index 2fccbcea..ad1e5ee9 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h +++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h @@ -85,6 +85,7 @@ private: mutable QOpenGLFramebufferObject *m_contentFBO = nullptr; QSurfaceFormat m_format; + QSize m_requestedSize; }; } -- 2.49.0 From 98df701a25a9493c454699398482a4873f6e2cb9 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Tue, 9 Feb 2021 16:09:21 +0000 Subject: [PATCH 03/57] Client: Connect drags being accepted to updating the source drag icon Currently in a multi-process drag and drop when the other client accepts a given mimetype for dropping it calls accept, which is received by the client, but the drag cursor is never updated. Instead the drag cursor was updated in the data_device_enter events which only works if we are operating within one process. The code existed to handle this existed but both the targetChanged signal and the dragSourceTargetChanged were unused. Change-Id: I443f31f1b2ef72d4b5eadaf7115f97544dac883a Reviewed-by: Vlad Zahorodnii Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 08e478448a97a440d5a968a5d797f0d7302140c2) --- src/client/qwaylanddatadevice.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/qwaylanddatadevice.cpp b/src/client/qwaylanddatadevice.cpp index 4d2459d1..375f13fb 100644 --- a/src/client/qwaylanddatadevice.cpp +++ b/src/client/qwaylanddatadevice.cpp @@ -130,6 +130,7 @@ bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData)); connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled); + connect(m_dragSource.data(), &QWaylandDataSource::targetChanged, this, &QWaylandDataDevice::dragSourceTargetChanged); start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->currentInputDevice()->serial()); return true; -- 2.49.0 From bd2d78e034ffab276333d954eb391cd2d06438c8 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Fri, 14 May 2021 13:23:24 +0100 Subject: [PATCH 04/57] Client: Disconnect registry listener on destruction If a display outlives a QWaylandClientExtension and a new global is announced we end up delivering an event to a now deleted extension which will crash. Change-Id: Idc0de40be61a2f7627ab4963e1fe29b22fbf3f04 (cherry picked from commit c4ba37cd2f8cb81b9438b56ac604fc2f3e45083c) --- src/client/global/qwaylandclientextension.cpp | 7 +++++++ src/client/global/qwaylandclientextension.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/client/global/qwaylandclientextension.cpp b/src/client/global/qwaylandclientextension.cpp index 966096a8..2dc61b77 100644 --- a/src/client/global/qwaylandclientextension.cpp +++ b/src/client/global/qwaylandclientextension.cpp @@ -88,6 +88,13 @@ QWaylandClientExtension::QWaylandClientExtension(const int ver) QMetaObject::invokeMethod(this, "addRegistryListener", Qt::QueuedConnection); } +QWaylandClientExtension::~QWaylandClientExtension() +{ + Q_D(QWaylandClientExtension); + if (d->registered && !QCoreApplication::closingDown()) + d->waylandIntegration->display()->removeListener(&QWaylandClientExtensionPrivate::handleRegistryGlobal, this); +} + QtWaylandClient::QWaylandIntegration *QWaylandClientExtension::integration() const { Q_D(const QWaylandClientExtension); diff --git a/src/client/global/qwaylandclientextension.h b/src/client/global/qwaylandclientextension.h index 98272e57..5bd28398 100644 --- a/src/client/global/qwaylandclientextension.h +++ b/src/client/global/qwaylandclientextension.h @@ -63,6 +63,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandClientExtension : public QObject Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) public: QWaylandClientExtension(const int version); + ~QWaylandClientExtension(); QtWaylandClient::QWaylandIntegration *integration() const; int version() const; -- 2.49.0 From 8329054a32746bc3b7d47d17cb75164aa4833f1a Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Mon, 3 May 2021 23:01:53 +0100 Subject: [PATCH 05/57] Client: Set XdgShell size hints before the first commit propagateSizeHints is only called in QWindow we have platform window and minimumSizeHint is then sent. We also need to send existing hints when we create the shell window. Sending them when we apply configure is too late, we need these hints available for the compositor to correctly configure the window. Change-Id: I6cbb294b11db06ecd87535fa4816bb8ad34a29c6 Reviewed-by: Vlad Zahorodnii Reviewed-by: Aleix Pol Gonzalez (cherry picked from commit d6e074d0d35221b0fac14c94fc79c98363f2f6c3) --- src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp | 3 +-- tests/auto/client/xdgshell/tst_xdgshell.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index 77ae62d6..45519ae5 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -106,8 +106,6 @@ void QWaylandXdgSurface::Toplevel::applyConfigure() m_xdgSurface->m_window->resizeFromApplyConfigure(m_pending.size); } - m_xdgSurface->setSizeHints(); - m_applied = m_pending; qCDebug(lcQpaWayland) << "Applied pending xdg_toplevel configure event:" << m_applied.size << m_applied.states; } @@ -270,6 +268,7 @@ QWaylandXdgSurface::QWaylandXdgSurface(QWaylandXdgShell *shell, ::xdg_surface *s m_toplevel->set_parent(parentXdgSurface->m_toplevel->object()); } } + setSizeHints(); } QWaylandXdgSurface::~QWaylandXdgSurface() diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp index 1c23728b..1423d647 100644 --- a/tests/auto/client/xdgshell/tst_xdgshell.cpp +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -505,7 +505,7 @@ void tst_xdgshell::minMaxSize() window.show(); QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); - exec([=] { xdgToplevel()->sendCompleteConfigure(); }); + // we don't roundtrip with our configuration the initial commit should be correct QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, QSize(100, 100)); QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, QSize(1000, 1000)); -- 2.49.0 From 7a34952c2e7a32d0b6f5ec43468226296213e666 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Mon, 14 Jun 2021 12:45:37 +0100 Subject: [PATCH 06/57] Fix build 1b5e43a593e917610e6245f7a272ac081c508ba4 relied on a patch that we can't backport. This adds that extra internal boolean backporting just the tiny part of d6ac8cf6. --- src/client/global/qwaylandclientextension.cpp | 5 ++++- src/client/global/qwaylandclientextension_p.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/global/qwaylandclientextension.cpp b/src/client/global/qwaylandclientextension.cpp index 2dc61b77..36609c08 100644 --- a/src/client/global/qwaylandclientextension.cpp +++ b/src/client/global/qwaylandclientextension.cpp @@ -74,7 +74,10 @@ void QWaylandClientExtensionPrivate::handleRegistryGlobal(void *data, ::wl_regis void QWaylandClientExtension::addRegistryListener() { Q_D(QWaylandClientExtension); - d->waylandIntegration->display()->addRegistryListener(&QWaylandClientExtensionPrivate::handleRegistryGlobal, this); + if (!d->registered) { + d->waylandIntegration->display()->addRegistryListener(&QWaylandClientExtensionPrivate::handleRegistryGlobal, this); + d->registered = true; + } } QWaylandClientExtension::QWaylandClientExtension(const int ver) diff --git a/src/client/global/qwaylandclientextension_p.h b/src/client/global/qwaylandclientextension_p.h index 69cc46a0..9091efbe 100644 --- a/src/client/global/qwaylandclientextension_p.h +++ b/src/client/global/qwaylandclientextension_p.h @@ -68,6 +68,7 @@ public: QtWaylandClient::QWaylandIntegration *waylandIntegration = nullptr; int version = -1; bool active = false; + bool registered = false; }; class Q_WAYLAND_CLIENT_EXPORT QWaylandClientExtensionTemplatePrivate : public QWaylandClientExtensionPrivate -- 2.49.0 From 7a6744480cb1ff20d1bd7e08985f257f256fea6c Mon Sep 17 00:00:00 2001 From: Zhang Liang Date: Mon, 1 Feb 2021 19:29:43 +0800 Subject: [PATCH 07/57] Fix: remove listener Add the operation for removing the listener form listener list Change-Id: Ief2ff1303b607eee499543303fe80e51f8f10cc5 Reviewed-by: David Edmundson (cherry picked from commit 16760280fd04cf70255bab16d9acecad254fdd8f) --- src/client/qwaylanddisplay.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 8a6d5db1..16f8ca1a 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -456,9 +456,10 @@ void QWaylandDisplay::addRegistryListener(RegistryListener listener, void *data) void QWaylandDisplay::removeListener(RegistryListener listener, void *data) { - std::remove_if(mRegistryListeners.begin(), mRegistryListeners.end(), [=](Listener l){ + auto iter = std::remove_if(mRegistryListeners.begin(), mRegistryListeners.end(), [=](Listener l){ return (l.listener == listener && l.data == data); }); + mRegistryListeners.erase(iter, mRegistryListeners.end()); } uint32_t QWaylandDisplay::currentTimeMillisec() -- 2.49.0 From c056a86ae6b48a23f1a677bdb298b3e0e00b2e58 Mon Sep 17 00:00:00 2001 From: David Redondo Date: Wed, 26 May 2021 14:49:40 +0200 Subject: [PATCH 08/57] Hook up queryKeyboardModifers Can be useful when upon enter a modifiers event is received but no key event so no QKeyEvent is generated. Fixes: QTBUG-62786 Change-Id: I30b57fc78ce6d54d8f644ca95ba40e7e26eb24ed Reviewed-by: Marco Martin Reviewed-by: David Edmundson (cherry picked from commit 4fa2baba8181ade4958a94e9531ec4f6919438a9) --- src/client/qwaylandintegration.cpp | 8 ++++++++ src/client/qwaylandintegration_p.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index d257e2e3..cd8569b1 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -262,6 +262,14 @@ QWaylandDisplay *QWaylandIntegration::display() const return mDisplay.data(); } +Qt::KeyboardModifiers QWaylandIntegration::queryKeyboardModifiers() const +{ + if (auto *seat = mDisplay->currentInputDevice()) { + return seat->modifiers(); + } + return Qt::NoModifier; +} + QList QWaylandIntegration::possibleKeys(const QKeyEvent *event) const { if (auto *seat = mDisplay->currentInputDevice()) diff --git a/src/client/qwaylandintegration_p.h b/src/client/qwaylandintegration_p.h index ff70ae25..73b80658 100644 --- a/src/client/qwaylandintegration_p.h +++ b/src/client/qwaylandintegration_p.h @@ -107,6 +107,8 @@ public: QWaylandDisplay *display() const; + Qt::KeyboardModifiers queryKeyboardModifiers() const override; + QList possibleKeys(const QKeyEvent *event) const override; QStringList themeNames() const override; -- 2.49.0 From 73636c9dd2af7410e988fb63649262b669036f8b Mon Sep 17 00:00:00 2001 From: Jan Blackquill Date: Tue, 24 Aug 2021 14:36:34 -0400 Subject: [PATCH 09/57] Correctly detect if image format is supported by QImageWriter The code queries potential image formats by stripping a mimetype of its 'image/' prefix and making the rest of the mimetype capitalised, such as 'image/png' -> 'PNG'. The problem is that this is then searched for in QImageWriter::supportedImageFormats() by simple equality. The method returns a list of lowercase byte arrays, not uppercase. As the codepath can never match due to checking for an uppercase word in an array of lowercase words, this means that images are effectively always sent as BMP format, even if they should be sent in other formats, such as PNG or JPEG. A simple inspection with GDB (or a qDebug) reveals this: ``` (gdb) p QImageWriter::supportedImageFormats() $31 = {"bmp" = {...}, "bw" = {...}, "cur" = {...}, "eps" = {...}, "epsf" = {...}, "epsi" = {...}, "icns" = {...}, "ico" = {...}, "jp2" = {...}, "jpeg" = {...}, "jpg" = {...}, "pbm" = {...}, "pcx" = {...}, "pgm" = {...}, "pic" = {...}, "png" = {...}, "ppm" = {...}, "rgb" = {...}, "rgba" = {...}, "sgi" = {...}, "tga" = {...}, "tif" = {...}, "tiff" = {...}, "wbmp" = {...}, "webp" = {...}, "xbm" = {...}, "xpm" = {...}} ``` ``` (gdb) p QImageWriter::supportedImageFormats().contains("PNG") $32 = false ``` ``` (gdb) p QImageWriter::supportedImageFormats().contains("png") $33 = true ``` The fix for this is simple: lowercase the remainder of the mimetype, instead of uppercasing it, and we can start hitting the codepath that's supposed to write non-BMP formats. Change-Id: Id3e9b730b7edcabcb2f1b04d8ef0a4c1fb9c9159 Reviewed-by: David Edmundson Reviewed-by: Qt CI Bot (cherry picked from commit 6072c1dc87e185f30c014f764737ac97b906640f) --- src/shared/qwaylandmimehelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/qwaylandmimehelper.cpp b/src/shared/qwaylandmimehelper.cpp index c5266ab3..e2fe1928 100644 --- a/src/shared/qwaylandmimehelper.cpp +++ b/src/shared/qwaylandmimehelper.cpp @@ -60,7 +60,7 @@ QByteArray QWaylandMimeHelper::getByteArray(QMimeData *mimeData, const QString & buf.open(QIODevice::ReadWrite); QByteArray fmt = "BMP"; if (mimeType.startsWith(QLatin1String("image/"))) { - QByteArray imgFmt = mimeType.mid(6).toUpper().toLatin1(); + QByteArray imgFmt = mimeType.mid(6).toLower().toLatin1(); if (QImageWriter::supportedImageFormats().contains(imgFmt)) fmt = imgFmt; } -- 2.49.0 From 0e054eab8f64ea3ad58225967b3374df380bcf8f Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 27 May 2021 19:55:04 -0300 Subject: [PATCH 10/57] Client: Don't always recreate frame callbacks The main QWaylandWindow method that is executed when handling updates is QWaylandWindow::handleUpdate(). This method always, unconditionally queues a frame callback, regardless of whether any other one is already queued. On some circumstances, e.g. when a window is hidden or completely obscured by other windows, it stops receiving frame callbacks from the compositor. However, QWaylandWindow would continue to request for them, which eventually fills up the Wayland socket, and causes the application to crash. This can be avoided by checking if the platform window is already waiting for a frame callback, before queueing another one. In QWaylandWindow::handleUpdate(), check if mWaitingForFrameCallback is true before queueing frame callbacks, and early return if that's the case. The XDG-shell test needed to be updated for this: The mock compositor is not responding to any frame callbacks, so the window will be unexposed, no longer get paint events and therefore not trigger any commit. This worked by accident before because we were issuing updates quickly enough to reset the timer before it had a chance to unexpose the window. The easiest fix is just to disable the dependency on frame callbacks in this test, since that is clearly not what it's testing. Task-number: QTBUG-81504 Change-Id: Ieacb05c7d5a5fcf662243d9177ebcc308cb9ca84 Reviewed-by: Qt CI Bot Reviewed-by: Georges Basile Stavracas Neto Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit cbc74ba6d7186457d8d07183272e952dee5f34f9) --- src/client/qwaylandwindow.cpp | 4 ++++ tests/auto/client/xdgshell/tst_xdgshell.cpp | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index d57094a7..9de284de 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -1199,6 +1199,10 @@ void QWaylandWindow::requestUpdate() void QWaylandWindow::handleUpdate() { qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread(); + + if (mWaitingForFrameCallback) + return; + // TODO: Should sync subsurfaces avoid requesting frame callbacks? QReadLocker lock(&mSurfaceLock); if (!mSurface) diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp index 1423d647..46f07c0a 100644 --- a/tests/auto/client/xdgshell/tst_xdgshell.cpp +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -138,6 +138,7 @@ void tst_xdgshell::configureSize() void tst_xdgshell::configureStates() { + QVERIFY(qputenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", "0")); QRasterWindow window; window.resize(64, 48); window.show(); @@ -186,6 +187,7 @@ void tst_xdgshell::configureStates() QCOMPARE(window.windowStates(), Qt::WindowNoState); QCOMPARE(window.frameGeometry().size(), windowedSize); // QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + QVERIFY(qunsetenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT")); } void tst_xdgshell::popup() -- 2.49.0 From f0753e3fa7a2a531ca03a51a9ad35a3df4ecba0c Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 27 May 2021 20:02:53 -0300 Subject: [PATCH 11/57] Client: Always destroy frame callback in the actual callback It's good hygiene to destroy all frame callbacks. Destroy the frame callback and cleanup the mFrameCallback class member in the callback itself. The callback destruction happens before calling handleFrameCallback() to avoid the theoretical case where another frame callback is queued by handleFrameCallback(), and then immediately destroyed in the callback handler. * asturmlechner 2021-09-27: Conflict resolved from non-backported commit in dev branch: 93058de8d7e7c2f320c22b3bd898aa06cf5babcd Change-Id: Ide6dc95e3402932c58bfc088a9d471fda821e9a1 Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 42cdc61a93cf2acb09936aebb5e431fdbc0a26c6) --- src/client/qwaylandwindow.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 9de284de..e0093013 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -635,9 +635,13 @@ void QWaylandWindow::commit() const wl_callback_listener QWaylandWindow::callbackListener = { [](void *data, wl_callback *callback, uint32_t time) { - Q_UNUSED(callback); Q_UNUSED(time); auto *window = static_cast(data); + + Q_ASSERT(callback == window->mFrameCallback); + wl_callback_destroy(callback); + window->mFrameCallback = nullptr; + window->handleFrameCallback(); } }; @@ -1208,11 +1212,6 @@ void QWaylandWindow::handleUpdate() if (!mSurface) return; - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - } - QMutexLocker locker(mFrameQueue.mutex); struct ::wl_surface *wrappedSurface = reinterpret_cast(wl_proxy_create_wrapper(mSurface->object())); wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mFrameQueue.queue); -- 2.49.0 From 8c2c86baa59e7e867ab62263df75749fe8b1401d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9ven=20Car?= Date: Wed, 18 Aug 2021 18:28:20 +0200 Subject: [PATCH 12/57] Wayland client: use wl_keyboard to determine active state Commit f497a5bb87270174b8e0106b7eca1992d44ff15d made QWaylandDisplay use the xdgshell's active state for QWindow::isActive(), instead of using wl_keyboard activate/deactivate events. That seems to have been a misunderstanding, since xdgshell activation is only supposed to be used to determine visual appearance, and there is an explicit warning not to assume it means focus. This commit reverts this logic back to listening to wl_keyboard. It adds a fallback when there is no wl_keyboard available to handle activated/deactivated events through xdg-shell, in order to fix QTBUG-53702. windowStates is handled so that we're not using the Xdg hint for anything with QWindowSystemInterface::handleWindowStateChanged or anything where we need to track only having one active. We are still exposing it for decorations, which is the only reason to use the Xdghint over keyboard focus - so you can keep the toplevel active whilst you show a popup. cherry-pick 40036a1b80e5234e6db7d5cbeff122aa7ee13e20 Change-Id: I4343d2ed9fb5b066cde95628ed0b4ccc84a424db Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/client/qwaylanddisplay.cpp | 19 +++++++++++-------- src/client/qwaylanddisplay_p.h | 1 + src/client/qwaylandwindow.cpp | 13 +++++++++++-- src/client/qwaylandwindow_p.h | 1 + .../qwaylandshellintegration_p.h | 7 +++---- .../qwaylandxdgshellv5integration.cpp | 7 ------- .../qwaylandxdgshellv5integration_p.h | 1 - .../qwaylandxdgshellv6integration.cpp | 14 -------------- .../qwaylandxdgshellv6integration_p.h | 1 - .../xdg-shell/qwaylandxdgshell.cpp | 16 +++++----------- .../xdg-shell/qwaylandxdgshellintegration.cpp | 14 -------------- .../xdg-shell/qwaylandxdgshellintegration_p.h | 1 - tests/auto/client/xdgshell/tst_xdgshell.cpp | 10 +++++++--- 13 files changed, 39 insertions(+), 66 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 16f8ca1a..d1ca0274 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -579,14 +579,10 @@ void QWaylandDisplay::handleKeyboardFocusChanged(QWaylandInputDevice *inputDevic if (mLastKeyboardFocus == keyboardFocus) return; - if (mWaylandIntegration->mShellIntegration) { - mWaylandIntegration->mShellIntegration->handleKeyboardFocusChanged(keyboardFocus, mLastKeyboardFocus); - } else { - if (keyboardFocus) - handleWindowActivated(keyboardFocus); - if (mLastKeyboardFocus) - handleWindowDeactivated(mLastKeyboardFocus); - } + if (keyboardFocus) + handleWindowActivated(keyboardFocus); + if (mLastKeyboardFocus) + handleWindowDeactivated(mLastKeyboardFocus); mLastKeyboardFocus = keyboardFocus; } @@ -631,6 +627,13 @@ QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const return mInputDevices.isEmpty() ? 0 : mInputDevices.first(); } +bool QWaylandDisplay::isKeyboardAvailable() const +{ + return std::any_of( + mInputDevices.constBegin(), mInputDevices.constEnd(), + [this](const QWaylandInputDevice *device) { return device->keyboard() != nullptr; }); +} + #if QT_CONFIG(cursor) QWaylandCursor *QWaylandDisplay::waylandCursor() diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index 1bad8b67..15104d65 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -219,6 +219,7 @@ public: void destroyFrameQueue(const FrameQueue &q); void dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout = -1); + bool isKeyboardAvailable() const; public slots: void blockingReadEvents(); void flushRequests(); diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index e0093013..bba43a54 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -96,7 +96,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) QWaylandWindow::~QWaylandWindow() { mDisplay->destroyFrameQueue(mFrameQueue); - mDisplay->handleWindowDestroyed(this); delete mWindowDecoration; @@ -266,6 +265,8 @@ void QWaylandWindow::reset() mMask = QRegion(); mQueuedBuffer = nullptr; + + mDisplay->handleWindowDestroyed(this); } QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface) @@ -1112,10 +1113,18 @@ bool QWaylandWindow::setMouseGrabEnabled(bool grab) return true; } +Qt::WindowStates QWaylandWindow::windowStates() const +{ + return mLastReportedWindowStates; +} + void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states) { createDecoration(); - QWindowSystemInterface::handleWindowStateChanged(window(), states, mLastReportedWindowStates); + Qt::WindowStates statesWithoutActive = states & ~Qt::WindowActive; + Qt::WindowStates lastStatesWithoutActive = mLastReportedWindowStates & ~Qt::WindowActive; + QWindowSystemInterface::handleWindowStateChanged(window(), statesWithoutActive, + lastStatesWithoutActive); mLastReportedWindowStates = states; } diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 01337cff..fb3ed606 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -148,6 +148,7 @@ public: void setWindowState(Qt::WindowStates states) override; void setWindowFlags(Qt::WindowFlags flags) override; void handleWindowStatesChanged(Qt::WindowStates states); + Qt::WindowStates windowStates() const; void raise() override; void lower() override; diff --git a/src/client/shellintegration/qwaylandshellintegration_p.h b/src/client/shellintegration/qwaylandshellintegration_p.h index ccad0048..4cc9b3b8 100644 --- a/src/client/shellintegration/qwaylandshellintegration_p.h +++ b/src/client/shellintegration/qwaylandshellintegration_p.h @@ -73,11 +73,10 @@ public: return true; } virtual QWaylandShellSurface *createShellSurface(QWaylandWindow *window) = 0; + // kept for binary compat with layer-shell-qt virtual void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) { - if (newFocus) - m_display->handleWindowActivated(newFocus); - if (oldFocus) - m_display->handleWindowDeactivated(oldFocus); + Q_UNUSED(newFocus); + Q_UNUSED(oldFocus); } virtual void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) { Q_UNUSED(resource); diff --git a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp index 4e25949f..cfc60939 100644 --- a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp +++ b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp @@ -85,13 +85,6 @@ QWaylandShellSurface *QWaylandXdgShellV5Integration::createShellSurface(QWayland return m_xdgShell->createXdgSurface(window); } -void QWaylandXdgShellV5Integration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) { - if (newFocus && qobject_cast(newFocus->shellSurface())) - m_display->handleWindowActivated(newFocus); - if (oldFocus && qobject_cast(oldFocus->shellSurface())) - m_display->handleWindowDeactivated(oldFocus); -} - } QT_END_NAMESPACE diff --git a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h index ce6bdb9e..aed88670 100644 --- a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h +++ b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h @@ -67,7 +67,6 @@ public: QWaylandXdgShellV5Integration() {} bool initialize(QWaylandDisplay *display) override; QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; - void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override; private: QScopedPointer m_xdgShell; diff --git a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp index 03164316..e8da8ba1 100644 --- a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp +++ b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp @@ -68,20 +68,6 @@ QWaylandShellSurface *QWaylandXdgShellV6Integration::createShellSurface(QWayland return m_xdgShell->getXdgSurface(window); } -void QWaylandXdgShellV6Integration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) -{ - if (newFocus) { - auto *xdgSurface = qobject_cast(newFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowActivated(newFocus); - } - if (oldFocus && qobject_cast(oldFocus->shellSurface())) { - auto *xdgSurface = qobject_cast(oldFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowDeactivated(oldFocus); - } -} - } QT_END_NAMESPACE diff --git a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h index 261f8cbb..c1bcd5c6 100644 --- a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h +++ b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h @@ -65,7 +65,6 @@ public: QWaylandXdgShellV6Integration() {} bool initialize(QWaylandDisplay *display) override; QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; - void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override; private: QScopedPointer m_xdgShell; diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index 45519ae5..e7bd5117 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -68,11 +68,6 @@ QWaylandXdgSurface::Toplevel::Toplevel(QWaylandXdgSurface *xdgSurface) QWaylandXdgSurface::Toplevel::~Toplevel() { - if (m_applied.states & Qt::WindowActive) { - QWaylandWindow *window = m_xdgSurface->window(); - window->display()->handleWindowDeactivated(window); - } - // The protocol spec requires that the decoration object is deleted before xdg_toplevel. delete m_decoration; m_decoration = nullptr; @@ -86,16 +81,15 @@ void QWaylandXdgSurface::Toplevel::applyConfigure() if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) m_normalSize = m_xdgSurface->m_window->windowFrameGeometry().size(); - if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive)) + if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive) + && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) m_xdgSurface->m_window->display()->handleWindowActivated(m_xdgSurface->m_window); - if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive)) + if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive) + && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) m_xdgSurface->m_window->display()->handleWindowDeactivated(m_xdgSurface->m_window); - // TODO: none of the other plugins send WindowActive either, but is it on purpose? - Qt::WindowStates statesWithoutActive = m_pending.states & ~Qt::WindowActive; - - m_xdgSurface->m_window->handleWindowStatesChanged(statesWithoutActive); + m_xdgSurface->m_window->handleWindowStatesChanged(m_pending.states); if (m_pending.size.isEmpty()) { // An empty size in the configure means it's up to the client to choose the size diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp index 8769d971..da0dd6a7 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp @@ -69,20 +69,6 @@ QWaylandShellSurface *QWaylandXdgShellIntegration::createShellSurface(QWaylandWi return m_xdgShell->getXdgSurface(window); } -void QWaylandXdgShellIntegration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) -{ - if (newFocus) { - auto *xdgSurface = qobject_cast(newFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowActivated(newFocus); - } - if (oldFocus && qobject_cast(oldFocus->shellSurface())) { - auto *xdgSurface = qobject_cast(oldFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowDeactivated(oldFocus); - } -} - } QT_END_NAMESPACE diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h index b6caa6c9..2f929f98 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h @@ -65,7 +65,6 @@ public: QWaylandXdgShellIntegration() {} bool initialize(QWaylandDisplay *display) override; QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; - void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override; private: QScopedPointer m_xdgShell; diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp index 46f07c0a..1da70ff2 100644 --- a/tests/auto/client/xdgshell/tst_xdgshell.cpp +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -31,6 +31,7 @@ #include #include #include +#include using namespace MockCompositor; @@ -155,9 +156,12 @@ void tst_xdgshell::configureStates() // Toplevel windows don't know their position on xdg-shell // QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled -// QEXPECT_FAIL("", "configure has already been acked, we shouldn't have to wait for isActive", Continue); -// QVERIFY(window.isActive()); - QTRY_VERIFY(window.isActive()); // Just make sure it eventually get's set correctly + // window.windowstate() is driven by keyboard focus, however for decorations we want to follow + // XDGShell this is internal to QtWayland so it is queried directly + auto waylandWindow = static_cast(window.handle()); + Q_ASSERT(waylandWindow); + QTRY_VERIFY(waylandWindow->windowStates().testFlag( + Qt::WindowActive)); // Just make sure it eventually get's set correctly const QSize screenSize(640, 480); const uint maximizedSerial = exec([&] { -- 2.49.0 From b69868cb4d7d7717d08a8294896acd4043292546 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Fri, 16 Jul 2021 13:00:03 +0200 Subject: [PATCH 13/57] Client: do not empty clipboard when a new popup/window is opened If we open a new popup or a window within the same app we have to avoid invalidating selection offer when losing focus, because it's still the same client who has the focus and we might not get a new selection offer by the compositor and therefore we would lose clipboard content. Fixes: QTBUG-93474 Change-Id: Ia2ef826c2967b1daf1cdeb085e8dae66d090dbcf Reviewed-by: Qt CI Bot Reviewed-by: David Edmundson Cherry-pick: 1e57ebd501cfc2255300392cd4565cd034efeed8 --- src/client/qwaylanddisplay.cpp | 13 +++++++++++++ src/client/qwaylandinputdevice.cpp | 8 -------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index d1ca0274..7560bf41 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -601,6 +601,19 @@ void QWaylandDisplay::handleWaylandSync() QWindow *activeWindow = mActiveWindows.empty() ? nullptr : mActiveWindows.last()->window(); if (activeWindow != QGuiApplication::focusWindow()) QWindowSystemInterface::handleWindowActivated(activeWindow); + + if (!activeWindow) { + if (lastInputDevice()) { +#if QT_CONFIG(clipboard) + if (auto *dataDevice = lastInputDevice()->dataDevice()) + dataDevice->invalidateSelectionOffer(); +#endif +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *device = lastInputDevice()->primarySelectionDevice()) + device->invalidateSelectionOffer(); +#endif + } + } } const wl_callback_listener QWaylandDisplay::syncCallbackListener = { diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 5994774f..5ec64faf 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -1303,14 +1303,6 @@ void QWaylandInputDevice::Keyboard::handleFocusDestroyed() void QWaylandInputDevice::Keyboard::handleFocusLost() { mFocus = nullptr; -#if QT_CONFIG(clipboard) - if (auto *dataDevice = mParent->dataDevice()) - dataDevice->invalidateSelectionOffer(); -#endif -#if QT_CONFIG(wayland_client_primary_selection) - if (auto *device = mParent->primarySelectionDevice()) - device->invalidateSelectionOffer(); -#endif mParent->mQDisplay->handleKeyboardFocusChanged(mParent); mRepeatTimer.stop(); } -- 2.49.0 From 77d8a85bedfd1f91970c8cf39709853776f80933 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Tue, 16 Feb 2021 09:51:47 +0000 Subject: [PATCH 14/57] Client: Implement DataDeviceV3 DataDeviceV2 fixes a leak of DataDevice resources. DataDeviceV3 brings multiple improvements: Action negotiation. The source announces which actions are supported, the target then announces which subset of those action the target supports and a preferred action. After negotiation both the source and target are notified of which action is to be performed. Drag sources are now notified when contents are dropped and when a client has finished with the drag and drop operation. A good test is the draggableicons example in QtBase. Change-Id: I55e9759ca5a2e4218d02d863144a64ade53ef764 Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 283a2d61d03315495a52d82f356e7cb5292f4bb4) --- src/client/qwaylanddatadevice.cpp | 84 ++++++++++++++----- src/client/qwaylanddatadevice_p.h | 8 +- src/client/qwaylanddatadevicemanager.cpp | 4 +- src/client/qwaylanddatadevicemanager_p.h | 2 +- src/client/qwaylanddataoffer.cpp | 25 ++++++ src/client/qwaylanddataoffer_p.h | 4 + src/client/qwaylanddatasource.cpp | 27 +++++- src/client/qwaylanddatasource_p.h | 10 ++- src/client/qwaylanddisplay.cpp | 2 +- src/client/qwaylanddnd.cpp | 24 +++--- src/client/qwaylanddnd_p.h | 7 +- .../client/datadevicev1/tst_datadevicev1.cpp | 2 +- 12 files changed, 153 insertions(+), 46 deletions(-) diff --git a/src/client/qwaylanddatadevice.cpp b/src/client/qwaylanddatadevice.cpp index 375f13fb..89e4e372 100644 --- a/src/client/qwaylanddatadevice.cpp +++ b/src/client/qwaylanddatadevice.cpp @@ -72,6 +72,8 @@ QWaylandDataDevice::QWaylandDataDevice(QWaylandDataDeviceManager *manager, QWayl QWaylandDataDevice::~QWaylandDataDevice() { + if (wl_data_device_get_version(object()) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) + release(); } QWaylandDataOffer *QWaylandDataDevice::selectionOffer() const @@ -110,7 +112,7 @@ QWaylandDataOffer *QWaylandDataDevice::dragOffer() const return m_dragOffer.data(); } -bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) +bool QWaylandDataDevice::startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon) { auto *seat = m_display->currentInputDevice(); auto *origin = seat->pointerFocus(); @@ -129,8 +131,28 @@ bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) mimeData->setData("application/x-qt-avoid-empty-placeholder", QByteArray("1")); m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData)); + + if (wl_data_device_get_version(object()) >= 3) + m_dragSource->set_actions(dropActionsToWl(supportedActions)); + connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled); - connect(m_dragSource.data(), &QWaylandDataSource::targetChanged, this, &QWaylandDataDevice::dragSourceTargetChanged); + connect(m_dragSource.data(), &QWaylandDataSource::dndResponseUpdated, this, [this](bool accepted, Qt::DropAction action) { + auto drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); + // in old versions drop action is not set, so we guess + if (wl_data_source_get_version(m_dragSource->object()) < 3) { + drag->setResponse(accepted); + } else { + QPlatformDropQtResponse response(accepted, action); + drag->setResponse(response); + } + }); + connect(m_dragSource.data(), &QWaylandDataSource::dndDropped, this, [](bool accepted, Qt::DropAction action) { + QPlatformDropQtResponse response(accepted, action); + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setDropResponse(response); + }); + connect(m_dragSource.data(), &QWaylandDataSource::finished, this, []() { + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(); + }); start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->currentInputDevice()->serial()); return true; @@ -159,7 +181,7 @@ void QWaylandDataDevice::data_device_drop() supportedActions = drag->supportedActions(); } else if (m_dragOffer) { dragData = m_dragOffer->mimeData(); - supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; + supportedActions = m_dragOffer->supportedActions(); } else { return; } @@ -169,7 +191,11 @@ void QWaylandDataDevice::data_device_drop() QGuiApplication::keyboardModifiers()); if (drag) { - static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(response); + auto drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); + drag->setDropResponse(response); + drag->finishDrag(); + } else if (m_dragOffer) { + m_dragOffer->finish(); } } @@ -193,7 +219,7 @@ void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, supportedActions = drag->supportedActions(); } else if (m_dragOffer) { dragData = m_dragOffer->mimeData(); - supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; + supportedActions = m_dragOffer->supportedActions(); } const QPlatformDragQtResponse &response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, @@ -204,11 +230,7 @@ void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); } - if (response.isAccepted()) { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, m_dragOffer->firstFormat().toUtf8().constData()); - } else { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, nullptr); - } + sendResponse(supportedActions, response); } void QWaylandDataDevice::data_device_leave() @@ -242,10 +264,10 @@ void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixe supportedActions = drag->supportedActions(); } else { dragData = m_dragOffer->mimeData(); - supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; + supportedActions = m_dragOffer->supportedActions(); } - QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, + const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers()); @@ -253,11 +275,7 @@ void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixe static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); } - if (response.isAccepted()) { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, m_dragOffer->firstFormat().toUtf8().constData()); - } else { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, nullptr); - } + sendResponse(supportedActions, response); } #endif // QT_CONFIG(draganddrop) @@ -287,11 +305,6 @@ void QWaylandDataDevice::dragSourceCancelled() m_dragSource.reset(); } -void QWaylandDataDevice::dragSourceTargetChanged(const QString &mimeType) -{ - static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->updateTarget(mimeType); -} - QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) const { QPoint pnt(wl_fixed_to_int(x), wl_fixed_to_int(y)); @@ -304,6 +317,33 @@ QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) con } return pnt; } + +void QWaylandDataDevice::sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response) +{ + if (response.isAccepted()) { + if (wl_data_device_get_version(object()) >= 3) + m_dragOffer->set_actions(dropActionsToWl(supportedActions), dropActionsToWl(response.acceptedAction())); + + m_dragOffer->accept(m_enterSerial, m_dragOffer->firstFormat()); + } else { + m_dragOffer->accept(m_enterSerial, QString()); + } +} + +int QWaylandDataDevice::dropActionsToWl(Qt::DropActions actions) +{ + + int wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (actions & Qt::CopyAction) + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (actions & (Qt::MoveAction | Qt::TargetMoveAction)) + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + // wayland does not support LinkAction at the time of writing + return wlActions; +} + + #endif // QT_CONFIG(draganddrop) } diff --git a/src/client/qwaylanddatadevice_p.h b/src/client/qwaylanddatadevice_p.h index 16c3ad28..801dcc2c 100644 --- a/src/client/qwaylanddatadevice_p.h +++ b/src/client/qwaylanddatadevice_p.h @@ -64,6 +64,7 @@ QT_REQUIRE_CONFIG(wayland_datadevice); QT_BEGIN_NAMESPACE class QMimeData; +class QPlatformDragQtResponse; class QWindow; namespace QtWaylandClient { @@ -89,7 +90,7 @@ public: #if QT_CONFIG(draganddrop) QWaylandDataOffer *dragOffer() const; - bool startDrag(QMimeData *mimeData, QWaylandWindow *icon); + bool startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon); void cancelDrag(); #endif @@ -109,13 +110,16 @@ private Q_SLOTS: #if QT_CONFIG(draganddrop) void dragSourceCancelled(); - void dragSourceTargetChanged(const QString &mimeType); #endif private: #if QT_CONFIG(draganddrop) QPoint calculateDragPosition(int x, int y, QWindow *wnd) const; #endif + void sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response); + + static int dropActionsToWl(Qt::DropActions dropActions); + QWaylandDisplay *m_display = nullptr; QWaylandInputDevice *m_inputDevice = nullptr; diff --git a/src/client/qwaylanddatadevicemanager.cpp b/src/client/qwaylanddatadevicemanager.cpp index 35d67307..6dc4f77f 100644 --- a/src/client/qwaylanddatadevicemanager.cpp +++ b/src/client/qwaylanddatadevicemanager.cpp @@ -50,8 +50,8 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { -QWaylandDataDeviceManager::QWaylandDataDeviceManager(QWaylandDisplay *display, uint32_t id) - : wl_data_device_manager(display->wl_registry(), id, 1) +QWaylandDataDeviceManager::QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id) + : wl_data_device_manager(display->wl_registry(), id, qMin(version, 3)) , m_display(display) { // Create transfer devices for all input devices. diff --git a/src/client/qwaylanddatadevicemanager_p.h b/src/client/qwaylanddatadevicemanager_p.h index bd05c0fb..510d9be4 100644 --- a/src/client/qwaylanddatadevicemanager_p.h +++ b/src/client/qwaylanddatadevicemanager_p.h @@ -68,7 +68,7 @@ class QWaylandInputDevice; class Q_WAYLAND_CLIENT_EXPORT QWaylandDataDeviceManager : public QtWayland::wl_data_device_manager { public: - QWaylandDataDeviceManager(QWaylandDisplay *display, uint32_t id); + QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id); ~QWaylandDataDeviceManager() override; QWaylandDataDevice *getDataDevice(QWaylandInputDevice *inputDevice); diff --git a/src/client/qwaylanddataoffer.cpp b/src/client/qwaylanddataoffer.cpp index 6046a9b5..09e29973 100644 --- a/src/client/qwaylanddataoffer.cpp +++ b/src/client/qwaylanddataoffer.cpp @@ -87,6 +87,15 @@ QMimeData *QWaylandDataOffer::mimeData() return m_mimeData.data(); } +Qt::DropActions QWaylandDataOffer::supportedActions() const +{ + if (wl_data_offer_get_version(const_cast<::wl_data_offer*>(object())) < 3) { + return Qt::MoveAction | Qt::CopyAction; + } + + return m_supportedActions; +} + void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd) { receive(mimeType, fd); @@ -98,6 +107,22 @@ void QWaylandDataOffer::data_offer_offer(const QString &mime_type) m_mimeData->appendFormat(mime_type); } +void QWaylandDataOffer::data_offer_action(uint32_t dnd_action) +{ + Q_UNUSED(dnd_action); + // This is the compositor telling the drag target what action it should perform + // It does not map nicely into Qt final drop semantics, other than pretending there is only one supported action? +} + +void QWaylandDataOffer::data_offer_source_actions(uint32_t source_actions) +{ + m_supportedActions = Qt::DropActions(); + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + m_supportedActions |= Qt::MoveAction; + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + m_supportedActions |= Qt::CopyAction; +} + QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer) : m_dataOffer(dataOffer) { diff --git a/src/client/qwaylanddataoffer_p.h b/src/client/qwaylanddataoffer_p.h index 9cf1483c..6f667398 100644 --- a/src/client/qwaylanddataoffer_p.h +++ b/src/client/qwaylanddataoffer_p.h @@ -82,6 +82,7 @@ public: explicit QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer); ~QWaylandDataOffer() override; QMimeData *mimeData() override; + Qt::DropActions supportedActions() const; QString firstFormat() const; @@ -89,10 +90,13 @@ public: protected: void data_offer_offer(const QString &mime_type) override; + void data_offer_source_actions(uint32_t source_actions) override; + void data_offer_action(uint32_t dnd_action) override; private: QWaylandDisplay *m_display = nullptr; QScopedPointer m_mimeData; + Qt::DropActions m_supportedActions; }; diff --git a/src/client/qwaylanddatasource.cpp b/src/client/qwaylanddatasource.cpp index c86c1416..321170a6 100644 --- a/src/client/qwaylanddatasource.cpp +++ b/src/client/qwaylanddatasource.cpp @@ -105,7 +105,32 @@ void QWaylandDataSource::data_source_send(const QString &mime_type, int32_t fd) void QWaylandDataSource::data_source_target(const QString &mime_type) { - Q_EMIT targetChanged(mime_type); + m_accepted = !mime_type.isEmpty(); + Q_EMIT dndResponseUpdated(m_accepted, m_dropAction); +} + +void QWaylandDataSource::data_source_action(uint32_t action) +{ + Qt::DropAction qtAction = Qt::IgnoreAction; + + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + qtAction = Qt::MoveAction; + else if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + qtAction = Qt::CopyAction; + + m_dropAction = qtAction; + Q_EMIT dndResponseUpdated(m_accepted, m_dropAction); +} + +void QWaylandDataSource::data_source_dnd_finished() +{ + Q_EMIT finished(); +} + +void QWaylandDataSource::data_source_dnd_drop_performed() +{ + + Q_EMIT dndDropped(m_accepted, m_dropAction); } } diff --git a/src/client/qwaylanddatasource_p.h b/src/client/qwaylanddatasource_p.h index 520b3165..089c5485 100644 --- a/src/client/qwaylanddatasource_p.h +++ b/src/client/qwaylanddatasource_p.h @@ -75,16 +75,24 @@ public: ~QWaylandDataSource() override; Q_SIGNALS: - void targetChanged(const QString &mime_type); void cancelled(); + void finished(); + + void dndResponseUpdated(bool accepted, Qt::DropAction action); + void dndDropped(bool accepted, Qt::DropAction action); protected: void data_source_cancelled() override; void data_source_send(const QString &mime_type, int32_t fd) override; void data_source_target(const QString &mime_type) override; + void data_source_dnd_drop_performed() override; + void data_source_dnd_finished() override; + void data_source_action(uint32_t action) override; private: QMimeData *m_mime_data = nullptr; + bool m_accepted = false; + Qt::DropAction m_dropAction = Qt::IgnoreAction; }; } diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 7560bf41..7f28d01c 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -356,7 +356,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin mInputDevices.append(inputDevice); #if QT_CONFIG(wayland_datadevice) } else if (interface == QStringLiteral("wl_data_device_manager")) { - mDndSelectionHandler.reset(new QWaylandDataDeviceManager(this, id)); + mDndSelectionHandler.reset(new QWaylandDataDeviceManager(this, version, id)); #endif } else if (interface == QStringLiteral("qt_surface_extension")) { mWindowExtension.reset(new QtWayland::qt_surface_extension(registry, id, 1)); diff --git a/src/client/qwaylanddnd.cpp b/src/client/qwaylanddnd.cpp index 6535aa16..97ee5b2e 100644 --- a/src/client/qwaylanddnd.cpp +++ b/src/client/qwaylanddnd.cpp @@ -66,7 +66,7 @@ void QWaylandDrag::startDrag() { QBasicDrag::startDrag(); QWaylandWindow *icon = static_cast(shapedPixmapWindow()->handle()); - if (m_display->currentInputDevice()->dataDevice()->startDrag(drag()->mimeData(), icon)) { + if (m_display->currentInputDevice()->dataDevice()->startDrag(drag()->mimeData(), drag()->supportedActions(), icon)) { icon->addAttachOffset(-drag()->hotSpot()); } else { // Cancelling immediately does not work, since the event loop for QDrag::exec is started @@ -103,31 +103,31 @@ void QWaylandDrag::endDrag() m_display->currentInputDevice()->handleEndDrag(); } -void QWaylandDrag::updateTarget(const QString &mimeType) +void QWaylandDrag::setResponse(bool accepted) { - setCanDrop(!mimeType.isEmpty()); - - if (canDrop()) { - updateCursor(defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers())); - } else { - updateCursor(Qt::IgnoreAction); - } + // This method is used for old DataDevices where the drag action is not communicated + Qt::DropAction action = defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers()); + setResponse(QPlatformDropQtResponse(accepted, action)); } -void QWaylandDrag::setResponse(const QPlatformDragQtResponse &response) +void QWaylandDrag::setResponse(const QPlatformDropQtResponse &response) { setCanDrop(response.isAccepted()); if (canDrop()) { - updateCursor(defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers())); + updateCursor(response.acceptedAction()); } else { updateCursor(Qt::IgnoreAction); } } -void QWaylandDrag::finishDrag(const QPlatformDropQtResponse &response) +void QWaylandDrag::setDropResponse(const QPlatformDropQtResponse &response) { setExecutedDropAction(response.acceptedAction()); +} + +void QWaylandDrag::finishDrag() +{ QKeyEvent event(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); eventFilter(shapedPixmapWindow(), &event); } diff --git a/src/client/qwaylanddnd_p.h b/src/client/qwaylanddnd_p.h index 474fe2ab..747f0190 100644 --- a/src/client/qwaylanddnd_p.h +++ b/src/client/qwaylanddnd_p.h @@ -71,9 +71,10 @@ public: QWaylandDrag(QWaylandDisplay *display); ~QWaylandDrag() override; - void updateTarget(const QString &mimeType); - void setResponse(const QPlatformDragQtResponse &response); - void finishDrag(const QPlatformDropQtResponse &response); + void setResponse(bool accepted); + void setResponse(const QPlatformDropQtResponse &response); + void setDropResponse(const QPlatformDropQtResponse &response); + void finishDrag(); protected: void startDrag() override; diff --git a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp index 1568b3b9..067410d0 100644 --- a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp +++ b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp @@ -35,7 +35,7 @@ using namespace MockCompositor; -constexpr int dataDeviceVersion = 1; +constexpr int dataDeviceVersion = 3; class DataDeviceCompositor : public DefaultCompositor { public: -- 2.49.0 From fd53a9ccbb0c5e3b8dc5b436c34958761cb4c5a4 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 18 Nov 2021 13:05:30 +0100 Subject: [PATCH 15/57] Client: Delay deletion of QDrag object until after we're done with it In certain cases, most notably when performing drag and drop operations with touch, the QDrag object gets deleted before data_source_send is executed. This then tries to access a deleted data_source, crashing the client. To avoid this, we indicate we want the QDrag object to stay around and then delete it in QWaylandDrag::finishDrag, which with data_device v3 is guaranteed to be called after everyone is done with the data source. Change-Id: I6a2f5a219f58d1b721a9fec33c57d26d2c522ec9 Reviewed-by: David Edmundson (cherry picked from commit 39e3290efa2dd40722fa3322284cae3b01ccedf4) --- src/client/qwaylanddnd.cpp | 11 +++++++++++ src/client/qwaylanddnd_p.h | 1 + 2 files changed, 12 insertions(+) diff --git a/src/client/qwaylanddnd.cpp b/src/client/qwaylanddnd.cpp index 97ee5b2e..7c53f5fa 100644 --- a/src/client/qwaylanddnd.cpp +++ b/src/client/qwaylanddnd.cpp @@ -80,6 +80,9 @@ void QWaylandDrag::cancel() QBasicDrag::cancel(); m_display->currentInputDevice()->dataDevice()->cancelDrag(); + + if (drag()) + drag()->deleteLater(); } void QWaylandDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) @@ -130,6 +133,14 @@ void QWaylandDrag::finishDrag() { QKeyEvent event(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); eventFilter(shapedPixmapWindow(), &event); + + if (drag()) + drag()->deleteLater(); +} + +bool QWaylandDrag::ownsDragObject() const +{ + return true; } } diff --git a/src/client/qwaylanddnd_p.h b/src/client/qwaylanddnd_p.h index 747f0190..46f629ac 100644 --- a/src/client/qwaylanddnd_p.h +++ b/src/client/qwaylanddnd_p.h @@ -83,6 +83,7 @@ protected: void drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override; void endDrag() override; + bool ownsDragObject() const override; private: QWaylandDisplay *m_display = nullptr; -- 2.49.0 From 757c941881e535178bf92ffc5c40f8ac7485b975 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Sun, 14 Nov 2021 13:54:19 +0000 Subject: [PATCH 16/57] Client: Avoid processing of events when showing windows The only time we want to dispatch events from the wayland socket is when the application is waiting for external events. Doing so at any other time will cause unpredictable behavior in client code. This caused a crash downstream where we had outputs get altered whilst itterating through outputs, which shouldn't happen. There is no benefit to flushing here, it won't make anything appear faster as we haven't attached the buffer yet. Change-Id: Ie13eae4012dab96a93d8810f468d1343402b8c28 Reviewed-by: Qt CI Bot Reviewed-by: Aleix Pol Gonzalez (cherry picked from commit 46ed85a80b28d519cf5887bbdce55d1bf57886c3) --- src/client/qwaylandwindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index bba43a54..41e56d24 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -437,7 +437,6 @@ void QWaylandWindow::setVisible(bool visible) if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) activePopups << this; initWindow(); - mDisplay->flushRequests(); setGeometry(windowGeometry()); // Don't flush the events here, or else the newly visible window may start drawing, but since -- 2.49.0 From 980225f489e7ec09068c5ea2a51c14692a986c67 Mon Sep 17 00:00:00 2001 From: Elvis Lee Date: Thu, 18 Feb 2021 15:45:49 +0900 Subject: [PATCH 17/57] Handle registry_global out of constructor Factory functions in QWaylandDisplay::registry_global() can be overridden. Later, other classes instantiated in the registry_global can support platform specific implementation with inheritance and some factory function. Change-Id: I92ce574e049b8c91587687cc7c30611f3dfdbe56 Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 3793a82038682db77966ea5daf8e75964e4250fe) --- src/client/qwaylanddisplay.cpp | 19 ++++++++++++------- src/client/qwaylanddisplay_p.h | 2 ++ src/client/qwaylandintegration.cpp | 3 +++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 7f28d01c..c2482030 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -160,13 +160,6 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) if (!mXkbContext) qCWarning(lcQpaWayland, "failed to create xkb context"); #endif - - forceRoundTrip(); - - if (!mWaitingScreens.isEmpty()) { - // Give wl_output.done and zxdg_output_v1.done events a chance to arrive - forceRoundTrip(); - } } QWaylandDisplay::~QWaylandDisplay(void) @@ -191,6 +184,18 @@ QWaylandDisplay::~QWaylandDisplay(void) wl_display_disconnect(mDisplay); } +// Steps which is called just after constructor. This separates registry_global() out of the constructor +// so that factory functions in integration can be overridden. +void QWaylandDisplay::initialize() +{ + forceRoundTrip(); + + if (!mWaitingScreens.isEmpty()) { + // Give wl_output.done and zxdg_output_v1.done events a chance to arrive + forceRoundTrip(); + } +} + void QWaylandDisplay::ensureScreen() { if (!mScreens.empty() || mPlaceholderScreen) diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index 15104d65..49820255 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -131,6 +131,8 @@ public: QWaylandDisplay(QWaylandIntegration *waylandIntegration); ~QWaylandDisplay(void) override; + void initialize(); + #if QT_CONFIG(xkbcommon) struct xkb_context *xkbContext() const { return mXkbContext.get(); } #endif diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index cd8569b1..8afecb31 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -200,6 +200,9 @@ void QWaylandIntegration::initialize() QSocketNotifier *sn = new QSocketNotifier(fd, QSocketNotifier::Read, mDisplay.data()); QObject::connect(sn, SIGNAL(activated(QSocketDescriptor)), mDisplay.data(), SLOT(flushRequests())); + // Call after eventDispatcher is fully connected, for QWaylandDisplay::forceRoundTrip() + mDisplay->initialize(); + // Qt does not support running with no screens mDisplay->ensureScreen(); } -- 2.49.0 From 21ddde34937f948deeb0e1e69068e291d1ec6556 Mon Sep 17 00:00:00 2001 From: Elvis Lee Date: Wed, 17 Mar 2021 16:31:10 +0900 Subject: [PATCH 18/57] Connect flushRequest after forceRoundTrip If flushRequest is connected with aboutToBlock, the flushRequest may consumes all events so that processEvents might be blocked in forceRoundTrip. Change-Id: I12b2c506e8442bf0e75f6ab6e418d3e1eea6d68c Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 654a54755138c520c3a41210d8078196e9a2c1bf) --- src/client/qwaylandintegration.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 8afecb31..661cea53 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -192,10 +192,6 @@ QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const void QWaylandIntegration::initialize() { - QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::eventDispatcher; - QObject::connect(dispatcher, SIGNAL(aboutToBlock()), mDisplay.data(), SLOT(flushRequests())); - QObject::connect(dispatcher, SIGNAL(awake()), mDisplay.data(), SLOT(flushRequests())); - int fd = wl_display_get_fd(mDisplay->wl_display()); QSocketNotifier *sn = new QSocketNotifier(fd, QSocketNotifier::Read, mDisplay.data()); QObject::connect(sn, SIGNAL(activated(QSocketDescriptor)), mDisplay.data(), SLOT(flushRequests())); @@ -203,6 +199,13 @@ void QWaylandIntegration::initialize() // Call after eventDispatcher is fully connected, for QWaylandDisplay::forceRoundTrip() mDisplay->initialize(); + // But the aboutToBlock() and awake() should be connected after initializePlatform(). + // Otherwise the connected flushRequests() may consumes up all events before processEvents starts to wait, + // so that processEvents(QEventLoop::WaitForMoreEvents) may be blocked in the forceRoundTrip(). + QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::eventDispatcher; + QObject::connect(dispatcher, SIGNAL(aboutToBlock()), mDisplay.data(), SLOT(flushRequests())); + QObject::connect(dispatcher, SIGNAL(awake()), mDisplay.data(), SLOT(flushRequests())); + // Qt does not support running with no screens mDisplay->ensureScreen(); } -- 2.49.0 From 52a15fa6e08b056dcff74afc677d895251b97148 Mon Sep 17 00:00:00 2001 From: Adrien Faveraux Date: Fri, 26 Nov 2021 09:18:58 +0100 Subject: [PATCH 19/57] Move the wayland socket polling to a separate event thread New event threads is introduced which calls poll() on the wayland fd, instead of relying on the event dispatcher by using the QSocketNotifier. This allows to call in the proper order the wl_display_prepare_read(), poll() and wl_display_read_events() functions. One thread is responsible for the default queue; when needed, it emit a signal so that the main thread can dispatch the queue. Another thread is responsible for the dedicated queue for frame callbacks; this thread will dispatch events on the thread itself. QWaylandWindow is updated to, instead of each window's dedicated event queue, use this queue for frame callbacks. Co-authored-by: Ratchanan Srirattanamet Task-number: QTBUG-66075 Change-Id: Ibb33ad7f4193b866d1b8d7a0405a94d59dcad5eb Reviewed-by: Qt CI Bot Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 92a7904d9651348b0c307e84251c8440c6f75b22) --- src/client/qwaylanddisplay.cpp | 302 +++++++++++++++++++++-------- src/client/qwaylanddisplay_p.h | 21 +- src/client/qwaylandintegration.cpp | 4 +- src/client/qwaylandwindow.cpp | 34 +++- src/client/qwaylandwindow_p.h | 2 +- 5 files changed, 255 insertions(+), 108 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index c2482030..c38f6f82 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -87,10 +87,203 @@ #include +#include // for std::tie + +static void checkWaylandError(struct wl_display *display) +{ + int ecode = wl_display_get_error(display); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + // special case this to provide a nicer error + qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + } else { + qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + } + _exit(1); +} + QT_BEGIN_NAMESPACE namespace QtWaylandClient { +class EventThread : public QThread +{ + Q_OBJECT +public: + enum OperatingMode { + EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread. + SelfDispatch, // Dispatch the events inside this thread. + }; + + EventThread(struct wl_display * wl, struct wl_event_queue * ev_queue, + OperatingMode mode) + : m_fd(wl_display_get_fd(wl)) + , m_pipefd{ -1, -1 } + , m_wldisplay(wl) + , m_wlevqueue(ev_queue) + , m_mode(mode) + , m_reading(true) + , m_quitting(false) + { + setObjectName(QStringLiteral("WaylandEventThread")); + } + + void readAndDispatchEvents() + { + /* + * Dispatch pending events and flush the requests at least once. If the event thread + * is not reading, try to call _prepare_read() to allow the event thread to poll(). + * If that fails, re-try dispatch & flush again until _prepare_read() is successful. + * + * This allow any call to readAndDispatchEvents() to start event thread's polling, + * not only the one issued from event thread's waitForReading(), which means functions + * called from dispatch_pending() can safely spin an event loop. + */ + for (;;) { + if (dispatchQueuePending() < 0) { + checkWaylandError(m_wldisplay); + return; + } + + wl_display_flush(m_wldisplay); + + // We have to check if event thread is reading every time we dispatch + // something, as that may recursively call this function. + if (m_reading.loadAcquire()) + break; + + if (prepareReadQueue() == 0) { + QMutexLocker l(&m_mutex); + m_reading.storeRelease(true); + m_cond.wakeOne(); + break; + } + } + } + + void stop() + { + // We have to both write to the pipe and set the flag, as the thread may be + // either in the poll() or waiting for _prepare_read(). + if (m_pipefd[1] != -1 && write(m_pipefd[1], "\0", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + { + QMutexLocker l(&m_mutex); + m_quitting = true; + m_cond.wakeOne(); + } + + wait(); + } + +Q_SIGNALS: + void needReadAndDispatch(); + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (qt_safe_pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the + // outbound ones. Wait until it's done before proceeding, unless we're told to quit. + while (waitForReading()) { + pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_pipefd[0], POLLIN, 0 } }; + poll(fds, 2, -1); + + if (fds[1].revents & POLLIN) { + // we don't really care to read the byte that was written here since we're closing down + wl_display_cancel_read(m_wldisplay); + break; + } + + if (fds[0].revents & POLLIN) + wl_display_read_events(m_wldisplay); + // The polll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop + // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which + // case we don't care anymore about them. + else + wl_display_cancel_read(m_wldisplay); + } + } + +private: + bool waitForReading() + { + Q_ASSERT(QThread::currentThread() == this); + + m_reading.storeRelease(false); + + if (m_mode == SelfDispatch) { + readAndDispatchEvents(); + } else { + Q_EMIT needReadAndDispatch(); + + QMutexLocker lock(&m_mutex); + // m_reading might be set from our emit or some other invocation of + // readAndDispatchEvents(). + while (!m_reading.loadRelaxed() && !m_quitting) + m_cond.wait(&m_mutex); + } + + return !m_quitting; + } + + int dispatchQueuePending() + { + if (m_wlevqueue) + return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue); + else + return wl_display_dispatch_pending(m_wldisplay); + } + + int prepareReadQueue() + { + if (m_wlevqueue) + return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue); + else + return wl_display_prepare_read(m_wldisplay); + } + + int m_fd; + int m_pipefd[2]; + wl_display *m_wldisplay; + wl_event_queue *m_wlevqueue; + OperatingMode m_mode; + + /* Concurrency note when operating in EmitToDispatch mode: + * m_reading is set to false inside event thread's waitForReading(), and is + * set to true inside main thread's readAndDispatchEvents(). + * The lock is not taken when setting m_reading to false, as the main thread + * is not actively waiting for it to turn false. However, the lock is taken + * inside readAndDispatchEvents() before setting m_reading to true, + * as the event thread is actively waiting for it under the wait condition. + */ + + QAtomicInteger m_reading; + bool m_quitting; + QMutex m_mutex; + QWaitCondition m_cond; +}; + Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland"); // for general (uncategorized) Wayland platform logging struct wl_surface *QWaylandDisplay::createSurface(void *handle) @@ -164,6 +357,12 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) QWaylandDisplay::~QWaylandDisplay(void) { + if (m_eventThread) + m_eventThread->stop(); + + if (m_frameEventQueueThread) + m_frameEventQueueThread->stop(); + if (mSyncCallback) wl_callback_destroy(mSyncCallback); @@ -210,98 +409,37 @@ void QWaylandDisplay::ensureScreen() void QWaylandDisplay::checkError() const { - int ecode = wl_display_get_error(mDisplay); - if ((ecode == EPIPE || ecode == ECONNRESET)) { - // special case this to provide a nicer error - qWarning("The Wayland connection broke. Did the Wayland compositor die?"); - } else { - qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); - } - _exit(1); + checkWaylandError(mDisplay); } +// Called in main thread, either from queued signal or directly. void QWaylandDisplay::flushRequests() { - if (wl_display_prepare_read(mDisplay) == 0) { - wl_display_read_events(mDisplay); - } - - if (wl_display_dispatch_pending(mDisplay) < 0) - checkError(); - - { - QReadLocker locker(&m_frameQueueLock); - for (const FrameQueue &q : mExternalQueues) { - QMutexLocker locker(q.mutex); - while (wl_display_prepare_read_queue(mDisplay, q.queue) != 0) - wl_display_dispatch_queue_pending(mDisplay, q.queue); - wl_display_read_events(mDisplay); - wl_display_dispatch_queue_pending(mDisplay, q.queue); - } - } - - wl_display_flush(mDisplay); -} - -void QWaylandDisplay::blockingReadEvents() -{ - if (wl_display_dispatch(mDisplay) < 0) - checkError(); -} - -void QWaylandDisplay::destroyFrameQueue(const QWaylandDisplay::FrameQueue &q) -{ - QWriteLocker locker(&m_frameQueueLock); - auto it = std::find_if(mExternalQueues.begin(), - mExternalQueues.end(), - [&q] (const QWaylandDisplay::FrameQueue &other){ return other.queue == q.queue; }); - Q_ASSERT(it != mExternalQueues.end()); - mExternalQueues.erase(it); - if (q.queue != nullptr) - wl_event_queue_destroy(q.queue); - delete q.mutex; + m_eventThread->readAndDispatchEvents(); } -QWaylandDisplay::FrameQueue QWaylandDisplay::createFrameQueue() +// We have to wait until we have an eventDispatcher before creating the eventThread, +// otherwise forceRoundTrip() may block inside _events_read() because eventThread is +// polling. +void QWaylandDisplay::initEventThread() { - QWriteLocker locker(&m_frameQueueLock); - FrameQueue q{createEventQueue()}; - mExternalQueues.append(q); - return q; -} + m_eventThread.reset( + new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); + connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, + &QWaylandDisplay::flushRequests, Qt::QueuedConnection); + m_eventThread->start(); -wl_event_queue *QWaylandDisplay::createEventQueue() -{ - return wl_display_create_queue(mDisplay); + // wl_display_disconnect() free this. + m_frameEventQueue = wl_display_create_queue(mDisplay); + m_frameEventQueueThread.reset( + new EventThread(mDisplay, m_frameEventQueue, EventThread::SelfDispatch)); + m_frameEventQueueThread->start(); } -void QWaylandDisplay::dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout) +void QWaylandDisplay::blockingReadEvents() { - if (!condition()) - return; - - QElapsedTimer timer; - timer.start(); - struct pollfd pFd = qt_make_pollfd(wl_display_get_fd(mDisplay), POLLIN); - while (timeout == -1 || timer.elapsed() < timeout) { - while (wl_display_prepare_read_queue(mDisplay, queue) != 0) - wl_display_dispatch_queue_pending(mDisplay, queue); - - wl_display_flush(mDisplay); - - const int remaining = qMax(timeout - timer.elapsed(), 0ll); - const int pollTimeout = timeout == -1 ? -1 : remaining; - if (qt_poll_msecs(&pFd, 1, pollTimeout) > 0) - wl_display_read_events(mDisplay); - else - wl_display_cancel_read(mDisplay); - - if (wl_display_dispatch_queue_pending(mDisplay, queue) < 0) - checkError(); - - if (!condition()) - break; - } + if (wl_display_dispatch(mDisplay) < 0) + checkWaylandError(mDisplay); } QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const @@ -678,6 +816,8 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int p } // namespace QtWaylandClient +#include "qwaylanddisplay.moc" + QT_END_NAMESPACE #include "moc_qwaylanddisplay_p.cpp" diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index 49820255..cf91b924 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -111,6 +111,7 @@ class QWaylandSurface; class QWaylandShellIntegration; class QWaylandCursor; class QWaylandCursorTheme; +class EventThread; typedef void (*RegistryListener)(void *data, struct wl_registry *registry, @@ -122,12 +123,6 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandDisplay : public QObject, public QtWayland Q_OBJECT public: - struct FrameQueue { - FrameQueue(wl_event_queue *q = nullptr) : queue(q), mutex(new QMutex) {} - wl_event_queue *queue; - QMutex *mutex; - }; - QWaylandDisplay(QWaylandIntegration *waylandIntegration); ~QWaylandDisplay(void) override; @@ -216,12 +211,11 @@ public: void handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice); void handleWindowDestroyed(QWaylandWindow *window); - wl_event_queue *createEventQueue(); - FrameQueue createFrameQueue(); - void destroyFrameQueue(const FrameQueue &q); - void dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout = -1); + wl_event_queue *frameEventQueue() { return m_frameEventQueue; }; bool isKeyboardAvailable() const; + + void initEventThread(); public slots: void blockingReadEvents(); void flushRequests(); @@ -244,6 +238,9 @@ private: }; struct wl_display *mDisplay = nullptr; + QScopedPointer m_eventThread; + wl_event_queue *m_frameEventQueue = nullptr; + QScopedPointer m_frameEventQueueThread; QtWayland::wl_compositor mCompositor; QScopedPointer mShm; QList mWaitingScreens; @@ -282,11 +279,9 @@ private: QWaylandInputDevice *mLastInputDevice = nullptr; QPointer mLastInputWindow; QPointer mLastKeyboardFocus; - QVector mActiveWindows; - QVector mExternalQueues; + QList mActiveWindows; struct wl_callback *mSyncCallback = nullptr; static const wl_callback_listener syncCallbackListener; - QReadWriteLock m_frameQueueLock; bool mClientSideInputContextRequested = !QPlatformInputContextFactory::requested().isNull(); diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 661cea53..fbf00c6b 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -192,9 +192,7 @@ QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const void QWaylandIntegration::initialize() { - int fd = wl_display_get_fd(mDisplay->wl_display()); - QSocketNotifier *sn = new QSocketNotifier(fd, QSocketNotifier::Read, mDisplay.data()); - QObject::connect(sn, SIGNAL(activated(QSocketDescriptor)), mDisplay.data(), SLOT(flushRequests())); + mDisplay->initEventThread(); // Call after eventDispatcher is fully connected, for QWaylandDisplay::forceRoundTrip() mDisplay->initialize(); diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 41e56d24..a38f7d55 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -76,7 +76,6 @@ QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr; QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) : QPlatformWindow(window) , mDisplay(display) - , mFrameQueue(mDisplay->createFrameQueue()) , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) { { @@ -95,8 +94,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) QWaylandWindow::~QWaylandWindow() { - mDisplay->destroyFrameQueue(mFrameQueue); - delete mWindowDecoration; if (mSurface) @@ -648,6 +645,8 @@ const wl_callback_listener QWaylandWindow::callbackListener = { void QWaylandWindow::handleFrameCallback() { + QMutexLocker locker(&mFrameSyncMutex); + mWaitingForFrameCallback = false; mFrameCallbackElapsedTimer.invalidate(); @@ -669,12 +668,16 @@ void QWaylandWindow::handleFrameCallback() mWaitingForUpdateDelivery = true; QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); } + + mFrameSyncWait.notify_all(); } bool QWaylandWindow::waitForFrameSync(int timeout) { - QMutexLocker locker(mFrameQueue.mutex); - mDisplay->dispatchQueueWhile(mFrameQueue.queue, [&]() { return mWaitingForFrameCallback; }, timeout); + QMutexLocker locker(&mFrameSyncMutex); + + QDeadlineTimer deadline(timeout); + while (mWaitingForFrameCallback && mFrameSyncWait.wait(&mFrameSyncMutex, deadline)) { } if (mWaitingForFrameCallback) { qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; @@ -1186,8 +1189,11 @@ void QWaylandWindow::requestUpdate() Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA // If we have a frame callback all is good and will be taken care of there - if (mWaitingForFrameCallback) - return; + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log @@ -1200,7 +1206,12 @@ void QWaylandWindow::requestUpdate() // so use invokeMethod to delay the delivery a bit. QMetaObject::invokeMethod(this, [this] { // Things might have changed in the meantime - if (hasPendingUpdateRequest() && !mWaitingForFrameCallback) + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } + if (hasPendingUpdateRequest()) deliverUpdateRequest(); }, Qt::QueuedConnection); } @@ -1220,9 +1231,10 @@ void QWaylandWindow::handleUpdate() if (!mSurface) return; - QMutexLocker locker(mFrameQueue.mutex); + QMutexLocker locker(&mFrameSyncMutex); + struct ::wl_surface *wrappedSurface = reinterpret_cast(wl_proxy_create_wrapper(mSurface->object())); - wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mFrameQueue.queue); + wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mDisplay->frameEventQueue()); mFrameCallback = wl_surface_frame(wrappedSurface); wl_proxy_wrapper_destroy(wrappedSurface); wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); @@ -1232,6 +1244,8 @@ void QWaylandWindow::handleUpdate() // Start a timer for handling the case when the compositor stops sending frame callbacks. if (mFrameCallbackTimeout > 0) { QMetaObject::invokeMethod(this, [this] { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) { if (mFrameCallbackCheckIntervalTimerId < 0) mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index fb3ed606..54ac67a9 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -232,7 +232,7 @@ protected: int mFrameCallbackCheckIntervalTimerId = -1; QElapsedTimer mFrameCallbackElapsedTimer; struct ::wl_callback *mFrameCallback = nullptr; - QWaylandDisplay::FrameQueue mFrameQueue; + QMutex mFrameSyncMutex; QWaitCondition mFrameSyncWait; // True when we have called deliverRequestUpdate, but the client has not yet attached a new buffer -- 2.49.0 From 0068c01fc78ef3f4a2e75adf21028a44c239bff7 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 1 Feb 2022 13:05:36 +0200 Subject: [PATCH 20/57] Client: Remove mWaitingForUpdateDelivery Currently, mWaitingForUpdateDelivery is shared between the main thread (doHandleFrameCallback()) and the frame callback event thread (handleFrameCallback()), however the access to it is not synchronized between both threads. On the other hand, QWaylandWindow already ensures not to create a frame callback if there's already one pending. This change removes mWaitingForUpdateDelivery flag because it should be already covered by mWaitingForFrameCallback and to remove unsynchronized shared state between threads. Change-Id: I0e5a25d18d1e66c4d7683e7e972330c4d7cbbf38 Reviewed-by: David Edmundson (cherry picked from commit feb1a5c207c13d0bf87c0d8ad039279dbf8cee9e) --- src/client/qwaylandwindow.cpp | 29 ++++++++++++----------------- src/client/qwaylandwindow_p.h | 1 - 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index a38f7d55..6bf0fc4b 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -651,23 +651,18 @@ void QWaylandWindow::handleFrameCallback() mFrameCallbackElapsedTimer.invalidate(); // The rest can wait until we can run it on the correct thread - if (!mWaitingForUpdateDelivery) { - auto doHandleExpose = [this]() { - bool wasExposed = isExposed(); - mFrameCallbackTimedOut = false; - if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? - sendExposeEvent(QRect(QPoint(), geometry().size())); - if (wasExposed && hasPendingUpdateRequest()) - deliverUpdateRequest(); - - mWaitingForUpdateDelivery = false; - }; - - // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() - // in the single-threaded case. - mWaitingForUpdateDelivery = true; - QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); - } + auto doHandleExpose = [this]() { + bool wasExposed = isExposed(); + mFrameCallbackTimedOut = false; + if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? + sendExposeEvent(QRect(QPoint(), geometry().size())); + if (wasExposed && hasPendingUpdateRequest()) + deliverUpdateRequest(); + }; + + // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() + // in the single-threaded case. + QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); mFrameSyncWait.notify_all(); } diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 54ac67a9..cf7ce879 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -228,7 +228,6 @@ protected: WId mWindowId; bool mWaitingForFrameCallback = false; bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out - bool mWaitingForUpdateDelivery = false; int mFrameCallbackCheckIntervalTimerId = -1; QElapsedTimer mFrameCallbackElapsedTimer; struct ::wl_callback *mFrameCallback = nullptr; -- 2.49.0 From a9f4245f4d42f544414f6bde4dc132aaf3622af0 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Wed, 9 Feb 2022 17:20:48 +0000 Subject: [PATCH 21/57] client: Simplify round trip behavior The custom event queue was removed in 302d4ffb8549214eb4028dc3e47ec4ee4e12ffbd (2015) so the comment about not being able to use the inbuilt round trip method no longer applies. This fixes a real world problem. Use of a blocking round trip should not process non wayland events. Doing so can lead to misbehaviour client side as things happen out of order. The move to the event thread created several regressions as we now get events before the QGuiApplication is fully constructed. Change-Id: I650481f49a47ed1a9778c7e1bc3c48db6e8f0031 Reviewed-by: Vlad Zahorodnii Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 62646d9122845d7bd9104b610478cebde3e769c7) --- src/client/qwaylanddisplay.cpp | 43 +--------------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index c38f6f82..b8da02b3 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -615,50 +615,9 @@ uint32_t QWaylandDisplay::currentTimeMillisec() return 0; } -static void -sync_callback(void *data, struct wl_callback *callback, uint32_t serial) -{ - Q_UNUSED(serial) - bool *done = static_cast(data); - - *done = true; - - // If the wl_callback done event is received after the condition check in the while loop in - // forceRoundTrip(), but before the call to processEvents, the call to processEvents may block - // forever if no more events are posted (eventhough the callback is handled in response to the - // aboutToBlock signal). Hence, we wake up the event dispatcher so forceRoundTrip may return. - // (QTBUG-64696) - if (auto *dispatcher = QThread::currentThread()->eventDispatcher()) - dispatcher->wakeUp(); - - wl_callback_destroy(callback); -} - -static const struct wl_callback_listener sync_listener = { - sync_callback -}; - void QWaylandDisplay::forceRoundTrip() { - // wl_display_roundtrip() works on the main queue only, - // but we use a separate one, so basically reimplement it here - int ret = 0; - bool done = false; - wl_callback *callback = wl_display_sync(mDisplay); - wl_callback_add_listener(callback, &sync_listener, &done); - flushRequests(); - if (QThread::currentThread()->eventDispatcher()) { - while (!done && ret >= 0) { - QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::WaitForMoreEvents); - ret = wl_display_dispatch_pending(mDisplay); - } - } else { - while (!done && ret >= 0) - ret = wl_display_dispatch(mDisplay); - } - - if (ret == -1 && !done) - wl_callback_destroy(callback); + wl_display_roundtrip(mDisplay); } bool QWaylandDisplay::supportsWindowDecoration() const -- 2.49.0 From 089279664f3586bb77b5378575f33af208732571 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 19 Feb 2022 17:01:04 +0200 Subject: [PATCH 22/57] Client: Fix opaque region setter The rect is in the global coordinate system, while the opaque region must be in the surface local coordinate system. Change-Id: I75042b4d779dfd4dfe610aad1f0387879f11b048 Reviewed-by: Aleix Pol Gonzalez (cherry picked from commit f9425f573b18c0b66fd9ad9c2805e8b8b9a3ec77) --- src/client/qwaylandwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 6bf0fc4b..9cc8cb12 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -372,7 +372,7 @@ void QWaylandWindow::setGeometry(const QRect &rect) mShellSurface->setWindowGeometry(windowContentGeometry()); if (isOpaque() && mMask.isEmpty()) - setOpaqueArea(rect); + setOpaqueArea(QRect(QPoint(0, 0), rect.size())); } void QWaylandWindow::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset) -- 2.49.0 From d8b932f753536c96acf8e8fc5cf774e8acae360e Mon Sep 17 00:00:00 2001 From: Fabian Vogt Date: Fri, 4 Feb 2022 11:07:36 +0100 Subject: [PATCH 23/57] Use proper dependencies in compile tests Use the dependencies as found by the "libraries" section instead of relying on them being available in the default location (e.g. "-ldrm"). Additionally, VK_USE_PLATFORM_WAYLAND_KHR requires , so add the wayland-client dependency. This fixes those tests if e.g. wayland-client headers need to be found through pkgconfig. This part of the code changed completely in Qt 6, so this is a totally different patch and not a cherry-pick of 5fc2e1915c3a ("CMake: Fix qtwayland feature detection"). Fixes: QTBUG-100475 --- src/client/configure.json | 5 +++-- src/compositor/configure.json | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/client/configure.json b/src/client/configure.json index 73f23362..6247f85e 100644 --- a/src/client/configure.json +++ b/src/client/configure.json @@ -167,7 +167,8 @@ "exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;", "return 0;" ] - } + }, + "use": "wayland-client" }, "egl_1_5-wayland": { "label": "EGL 1.5 with Wayland Platform", @@ -182,7 +183,7 @@ "eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_EXT, (struct wl_display *)(nullptr), nullptr);" ] }, - "use": "egl" + "use": "egl wayland-client" } }, diff --git a/src/compositor/configure.json b/src/compositor/configure.json index c5b0f03e..031e4cc3 100644 --- a/src/compositor/configure.json +++ b/src/compositor/configure.json @@ -7,6 +7,31 @@ "testDir": "../../config.tests", "libraries": { + "wayland-client": { + "label": "Wayland client library", + "headers": "wayland-version.h", + "test": { + "main": [ + "#if WAYLAND_VERSION_MAJOR < 1", + "# error Wayland 1.8.0 or higher required", + "#endif", + "#if WAYLAND_VERSION_MAJOR == 1", + "# if WAYLAND_VERSION_MINOR < 8", + "# error Wayland 1.8.0 or higher required", + "# endif", + "# if WAYLAND_VERSION_MINOR == 8", + "# if WAYLAND_VERSION_MICRO < 0", + "# error Wayland 1.8.0 or higher required", + "# endif", + "# endif", + "#endif" + ] + }, + "sources": [ + { "type": "pkgConfig", "args": "wayland-client" }, + "-lwayland-client" + ] + }, "wayland-server": { "label": "wayland-server", "headers": "wayland-version.h", @@ -193,7 +218,8 @@ "exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;", "return 0;" ] - } + }, + "use": "wayland-client" } }, -- 2.49.0 From 8584cc9e877ecf67452c523d1c3f1691a6af1607 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Tue, 15 Mar 2022 15:59:15 +0100 Subject: [PATCH 24/57] Revert "Client: Remove mWaitingForUpdateDelivery" The reverted commit introduces a severe performance regression when a client window is resized while a QtQuick renderthread animation is running. This reverts commit feb1a5c207c13d0bf87c0d8ad039279dbf8cee9e. Fixes: QTBUG-101726 Change-Id: Ib5b52ce06efec8c86fada1623c2af82099e57fc6 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/client/qwaylandwindow.cpp | 12 +++++++++--- src/client/qwaylandwindow_p.h | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 9cc8cb12..3d1be31c 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -658,11 +658,17 @@ void QWaylandWindow::handleFrameCallback() sendExposeEvent(QRect(QPoint(), geometry().size())); if (wasExposed && hasPendingUpdateRequest()) deliverUpdateRequest(); + + mWaitingForUpdateDelivery = false; }; - // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() - // in the single-threaded case. - QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); + if (!mWaitingForUpdateDelivery) { + // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() + // in the single-threaded case. + mWaitingForUpdateDelivery = true; + QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); + } + mFrameSyncWait.notify_all(); } diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index cf7ce879..54ac67a9 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -228,6 +228,7 @@ protected: WId mWindowId; bool mWaitingForFrameCallback = false; bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out + bool mWaitingForUpdateDelivery = false; int mFrameCallbackCheckIntervalTimerId = -1; QElapsedTimer mFrameCallbackElapsedTimer; struct ::wl_callback *mFrameCallback = nullptr; -- 2.49.0 From a743f6e0516b2d286fefe74b0c045509fed9626a Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Tue, 15 Mar 2022 16:53:04 +0100 Subject: [PATCH 25/57] Fix race condition on mWaitingForUpdateDelivery Change-Id: I0e91bda73722468b9339fc434fe04420b5e7d3da Reviewed-by: David Edmundson --- src/client/qwaylandwindow.cpp | 7 ++----- src/client/qwaylandwindow_p.h | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 3d1be31c..014e2d87 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -652,24 +652,21 @@ void QWaylandWindow::handleFrameCallback() // The rest can wait until we can run it on the correct thread auto doHandleExpose = [this]() { + mWaitingForUpdateDelivery.storeRelease(false); bool wasExposed = isExposed(); mFrameCallbackTimedOut = false; if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? sendExposeEvent(QRect(QPoint(), geometry().size())); if (wasExposed && hasPendingUpdateRequest()) deliverUpdateRequest(); - - mWaitingForUpdateDelivery = false; }; - if (!mWaitingForUpdateDelivery) { + if (mWaitingForUpdateDelivery.testAndSetAcquire(false, true)) { // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() // in the single-threaded case. - mWaitingForUpdateDelivery = true; QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); } - mFrameSyncWait.notify_all(); } diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 54ac67a9..c0a76345 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -228,7 +228,7 @@ protected: WId mWindowId; bool mWaitingForFrameCallback = false; bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out - bool mWaitingForUpdateDelivery = false; + QAtomicInt mWaitingForUpdateDelivery = false; int mFrameCallbackCheckIntervalTimerId = -1; QElapsedTimer mFrameCallbackElapsedTimer; struct ::wl_callback *mFrameCallback = nullptr; -- 2.49.0 From 5bc262cd799c8aec1a67193dfb5e2cb95962978f Mon Sep 17 00:00:00 2001 From: Kenneth Topp Date: Mon, 4 Apr 2022 09:36:21 -0400 Subject: [PATCH 26/57] use poll(2) when reading from clipboard change clipboard read away from select(2) call which can fail when an application has large number of open files Change-Id: I6d98c6bb11cdd5b6171b01cfeb0044dd41cf9fb5 Reviewed-by: Thiago Macieira (cherry picked from commit 829a9f62a96721c142f53e12a8812e8231b20317) --- src/client/qwaylanddataoffer.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/client/qwaylanddataoffer.cpp b/src/client/qwaylanddataoffer.cpp index 09e29973..0241a1df 100644 --- a/src/client/qwaylanddataoffer.cpp +++ b/src/client/qwaylanddataoffer.cpp @@ -195,17 +195,18 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QVariant::T int QWaylandMimeData::readData(int fd, QByteArray &data) const { - fd_set readset; - FD_ZERO(&readset); - FD_SET(fd, &readset); - struct timeval timeout; + struct pollfd readset; + readset.fd = fd; + readset.events = POLLIN; + struct timespec timeout; timeout.tv_sec = 1; - timeout.tv_usec = 0; + timeout.tv_nsec = 0; + Q_FOREVER { - int ready = select(FD_SETSIZE, &readset, nullptr, nullptr, &timeout); + int ready = qt_safe_poll(&readset, 1, &timeout); if (ready < 0) { - qWarning() << "QWaylandDataOffer: select() failed"; + qWarning() << "QWaylandDataOffer: qt_safe_poll() failed"; return -1; } else if (ready == 0) { qWarning("QWaylandDataOffer: timeout reading from pipe"); -- 2.49.0 From 32b52debc17b894e02c4292ad408d4ce60ac4880 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 22 Feb 2022 12:31:08 +0100 Subject: [PATCH 27/57] Reduce memory leakage We need to clean up the event queue when we're done. Change-Id: I13a9eb77e978f4eab227a3a28dab8ebc8de94405 Reviewed-by: David Edmundson (cherry picked from commit 1264e5f565d8fb7ac200e4b00531ab876922458f) --- src/client/qwaylanddisplay.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index b8da02b3..4a331a4c 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -381,6 +381,9 @@ QWaylandDisplay::~QWaylandDisplay(void) #endif if (mDisplay) wl_display_disconnect(mDisplay); + + if (m_frameEventQueue) + wl_event_queue_destroy(m_frameEventQueue); } // Steps which is called just after constructor. This separates registry_global() out of the constructor -- 2.49.0 From cedbdae068bedfafeb43c6661a88b8ea28cce680 Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Wed, 20 Jul 2022 15:57:40 -0700 Subject: [PATCH 28/57] Only close popup in the the hierchary Imagine following event sequences: 1. a tooltip is shown. activePopups = {tooltip} 2. user click menu bar to show the menu, QMenu::setVisible is called. now activePopups(tooltip, menu} 3. tooltip visibility changed to false. 4. closePopups() close both tooltip and menu. This is a common pattern under wayland that menu is shown as a invisible state. This patch tries to memorize the surface hierchary used to create the popup role. And only close those popups whose ancesotor is hidden. Pick-to: 6.4 Change-Id: I78aa0b4e32a5812603e003e756d8bcd202e94af4 Reviewed-by: David Edmundson (cherry picked from commit f8e3257e9b1e22d52e9c221c62b8d9b6dd1151a3) --- src/client/qwaylandwindow.cpp | 33 ++++--- src/client/qwaylandwindow_p.h | 6 ++ .../xdg-shell-v5/qwaylandxdgpopupv5.cpp | 5 +- .../xdg-shell-v5/qwaylandxdgpopupv5_p.h | 3 +- .../xdg-shell-v5/qwaylandxdgshellv5.cpp | 2 +- .../xdg-shell-v6/qwaylandxdgshellv6.cpp | 3 + .../xdg-shell/qwaylandxdgshell.cpp | 22 +++-- .../xdg-shell/qwaylandxdgshell_p.h | 5 +- tests/auto/client/xdgshell/tst_xdgshell.cpp | 87 +++++++++++++++++++ 9 files changed, 136 insertions(+), 30 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 014e2d87..1e916ca1 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -239,6 +239,7 @@ bool QWaylandWindow::shouldCreateSubSurface() const void QWaylandWindow::reset() { + closeChildPopups(); delete mShellSurface; mShellSurface = nullptr; delete mSubSurfaceWindow; @@ -397,21 +398,6 @@ void QWaylandWindow::sendExposeEvent(const QRect &rect) mLastExposeGeometry = rect; } - -static QVector> activePopups; - -void QWaylandWindow::closePopups(QWaylandWindow *parent) -{ - while (!activePopups.isEmpty()) { - auto popup = activePopups.takeLast(); - if (popup.isNull()) - continue; - if (popup.data() == parent) - return; - popup->reset(); - } -} - QPlatformScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const { QReadLocker lock(&mSurfaceLock); @@ -431,8 +417,6 @@ void QWaylandWindow::setVisible(bool visible) lastVisible = visible; if (visible) { - if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) - activePopups << this; initWindow(); setGeometry(windowGeometry()); @@ -441,7 +425,6 @@ void QWaylandWindow::setVisible(bool visible) // QWaylandShmBackingStore::beginPaint(). } else { sendExposeEvent(QRect()); - closePopups(this); reset(); } } @@ -1304,6 +1287,20 @@ void QWaylandWindow::setOpaqueArea(const QRegion &opaqueArea) wl_region_destroy(region); } +void QWaylandWindow::addChildPopup(QWaylandWindow *surface) { + mChildPopups.append(surface); +} + +void QWaylandWindow::removeChildPopup(QWaylandWindow *surface) { + mChildPopups.removeAll(surface); +} + +void QWaylandWindow::closeChildPopups() { + while (!mChildPopups.isEmpty()) { + auto popup = mChildPopups.takeLast(); + popup->reset(); + } +} } QT_END_NAMESPACE diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index c0a76345..2be87bc0 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -207,6 +207,10 @@ public: void handleUpdate(); void deliverUpdateRequest() override; + void addChildPopup(QWaylandWindow* child); + void removeChildPopup(QWaylandWindow* child); + void closeChildPopups(); + public slots: void applyConfigure(); @@ -262,6 +266,8 @@ protected: QWaylandBuffer *mQueuedBuffer = nullptr; QRegion mQueuedBufferDamage; + QList> mChildPopups; + private: void setGeometry_helper(const QRect &rect); void initWindow(); diff --git a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp index 85d25e3c..60bdd491 100644 --- a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp +++ b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp @@ -47,18 +47,21 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { -QWaylandXdgPopupV5::QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow *window) +QWaylandXdgPopupV5::QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow* parent, QWaylandWindow *window) : QWaylandShellSurface(window) , QtWayland::xdg_popup_v5(popup) + , m_parent(parent) , m_window(window) { if (window->display()->windowExtension()) m_extendedWindow = new QWaylandExtendedSurface(window); + m_parent->addChildPopup(m_window); } QWaylandXdgPopupV5::~QWaylandXdgPopupV5() { xdg_popup_destroy(object()); + m_parent->removeChildPopup(m_window); delete m_extendedWindow; } diff --git a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h index 7494f6a6..d85f130b 100644 --- a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h +++ b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h @@ -70,7 +70,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandXdgPopupV5 : public QWaylandShellSurface { Q_OBJECT public: - QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow *window); + QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow* parent, QWaylandWindow *window); ~QWaylandXdgPopupV5() override; protected: @@ -78,6 +78,7 @@ protected: private: QWaylandExtendedSurface *m_extendedWindow = nullptr; + QWaylandWindow *m_parent = nullptr; QWaylandWindow *m_window = nullptr; }; diff --git a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp index 7e242c4a..def8452a 100644 --- a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp +++ b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp @@ -84,7 +84,7 @@ QWaylandXdgPopupV5 *QWaylandXdgShellV5::createXdgPopup(QWaylandWindow *window, Q int x = position.x() + parentWindow->frameMargins().left(); int y = position.y() + parentWindow->frameMargins().top(); - auto popup = new QWaylandXdgPopupV5(get_xdg_popup(window->wlSurface(), parentSurface, seat, m_popupSerial, x, y), window); + auto popup = new QWaylandXdgPopupV5(get_xdg_popup(window->wlSurface(), parentSurface, seat, m_popupSerial, x, y), parentWindow, window); m_popups.append(window); QObject::connect(popup, &QWaylandXdgPopupV5::destroyed, [this, window](){ m_popups.removeOne(window); diff --git a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp index 8c371661..151c78e3 100644 --- a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp +++ b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp @@ -174,6 +174,7 @@ QWaylandXdgSurfaceV6::Popup::Popup(QWaylandXdgSurfaceV6 *xdgSurface, QWaylandXdg , m_xdgSurface(xdgSurface) , m_parent(parent) { + m_parent->window()->addChildPopup(m_xdgSurface->window()); } QWaylandXdgSurfaceV6::Popup::~Popup() @@ -181,6 +182,8 @@ QWaylandXdgSurfaceV6::Popup::~Popup() if (isInitialized()) destroy(); + m_parent->window()->removeChildPopup(m_xdgSurface->window()); + if (m_grabbing) { auto *shell = m_xdgSurface->m_shell; Q_ASSERT(shell->m_topmostGrabbingPopup == this); diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index e7bd5117..c79b0e49 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -196,12 +196,17 @@ QtWayland::xdg_toplevel::resize_edge QWaylandXdgSurface::Toplevel::convertToResi | ((edges & Qt::RightEdge) ? resize_edge_right : 0)); } -QWaylandXdgSurface::Popup::Popup(QWaylandXdgSurface *xdgSurface, QWaylandXdgSurface *parent, +QWaylandXdgSurface::Popup::Popup(QWaylandXdgSurface *xdgSurface, QWaylandWindow *parent, QtWayland::xdg_positioner *positioner) - : xdg_popup(xdgSurface->get_popup(parent->object(), positioner->object())) - , m_xdgSurface(xdgSurface) + : m_xdgSurface(xdgSurface) + , m_parentXdgSurface(qobject_cast(parent->shellSurface())) , m_parent(parent) { + + init(xdgSurface->get_popup(m_parentXdgSurface ? m_parentXdgSurface->object() : nullptr, positioner->object())); + if (m_parent) { + m_parent->addChildPopup(m_xdgSurface->window()); + } } QWaylandXdgSurface::Popup::~Popup() @@ -209,10 +214,14 @@ QWaylandXdgSurface::Popup::~Popup() if (isInitialized()) destroy(); + if (m_parent) { + m_parent->removeChildPopup(m_xdgSurface->window()); + } + if (m_grabbing) { auto *shell = m_xdgSurface->m_shell; Q_ASSERT(shell->m_topmostGrabbingPopup == this); - shell->m_topmostGrabbingPopup = m_parent->m_popup; + shell->m_topmostGrabbingPopup = m_parentXdgSurface ? m_parentXdgSurface->m_popup : nullptr; m_grabbing = false; // Synthesize Qt enter/leave events for popup @@ -406,8 +415,6 @@ void QWaylandXdgSurface::setPopup(QWaylandWindow *parent) { Q_ASSERT(!m_toplevel && !m_popup); - auto parentXdgSurface = static_cast(parent->shellSurface()); - auto positioner = new QtWayland::xdg_positioner(m_shell->create_positioner()); // set_popup expects a position relative to the parent QPoint transientPos = m_window->geometry().topLeft(); // this is absolute @@ -422,8 +429,9 @@ void QWaylandXdgSurface::setPopup(QWaylandWindow *parent) positioner->set_size(m_window->geometry().width(), m_window->geometry().height()); positioner->set_constraint_adjustment(QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y); - m_popup = new Popup(this, parentXdgSurface, positioner); + m_popup = new Popup(this, parent, positioner); positioner->destroy(); + delete positioner; } diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h index 96785205..4b518f0a 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h @@ -131,14 +131,15 @@ private: class Popup : public QtWayland::xdg_popup { public: - Popup(QWaylandXdgSurface *xdgSurface, QWaylandXdgSurface *parent, QtWayland::xdg_positioner *positioner); + Popup(QWaylandXdgSurface *xdgSurface, QWaylandWindow *parent, QtWayland::xdg_positioner *positioner); ~Popup() override; void grab(QWaylandInputDevice *seat, uint serial); void xdg_popup_popup_done() override; QWaylandXdgSurface *m_xdgSurface = nullptr; - QWaylandXdgSurface *m_parent = nullptr; + QWaylandXdgSurface *m_parentXdgSurface = nullptr; + QWaylandWindow *m_parent = nullptr; bool m_grabbing = false; }; diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp index 1da70ff2..c5271f63 100644 --- a/tests/auto/client/xdgshell/tst_xdgshell.cpp +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -46,6 +46,7 @@ private slots: void configureStates(); void popup(); void tooltipOnPopup(); + void tooltipAndSiblingPopup(); void switchPopups(); void hidePopupParent(); void pongs(); @@ -346,6 +347,92 @@ void tst_xdgshell::tooltipOnPopup() QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); } +void tst_xdgshell::tooltipAndSiblingPopup() +{ + class ToolTip : public QRasterWindow { + public: + explicit ToolTip(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::ToolTip); + resize(100, 100); + show(); + } + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_popup = new QRasterWindow; + m_popup->setTransientParent(transientParent()); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + + QRasterWindow *m_popup = nullptr; + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_tooltip = new ToolTip(this); + } + ToolTip *m_tooltip = nullptr; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([=] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([=] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([=] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup()->m_grabbed); + + exec([=] { + auto *surface = xdgPopup()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)); + exec([=] { xdgPopup(1)->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_grabbed); + + // Close the middle tooltip (it should not close the sibling popup) + window.m_tooltip->close(); + + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + // Verify the remaining xdg surface is a grab popup.. + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)->m_grabbed); + + window.m_tooltip->m_popup->close(); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); +} + // QTBUG-65680 void tst_xdgshell::switchPopups() { -- 2.49.0 From dd9a2f1e29ed3ecbbaf9362cdccda7f25dc51b29 Mon Sep 17 00:00:00 2001 From: Roman Genkhel Date: Thu, 12 Nov 2020 12:21:51 +0300 Subject: [PATCH 29/57] Check pointer for null before use in ASSERT Task-number: QTBUG-85195 Change-Id: I331e54f6e58aa9d536351a55223610c60b3cb414 Reviewed-by: David Edmundson (cherry picked from commit e235e8ddb1fc3cc5ab3b70b1fb285770b2c8c9ca) --- src/client/qwaylandwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 1e916ca1..d98a70e4 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -536,12 +536,12 @@ void QWaylandWindow::sendRecursiveExposeEvent() void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { - Q_ASSERT(!buffer->committed()); QReadLocker locker(&mSurfaceLock); if (mSurface == nullptr) return; if (buffer) { + Q_ASSERT(!buffer->committed()); handleUpdate(); buffer->setBusy(); -- 2.49.0 From f77a9af68b6b3d2827c6942fec8607d4b57ffdd7 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Mon, 6 Jul 2020 14:37:35 +0200 Subject: [PATCH 30/57] Use wl_surface.damage_buffer on the client side Prefer the newer, recommended damage_buffer when the compositor supports it. Fixes: QTBUG-74929 Change-Id: I9107966910b616a666931404a7b41bfac14c22c0 Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 314fd6db51277224cdc799b039ef79db1101f5cd) --- src/client/qwaylanddisplay.cpp | 2 +- src/client/qwaylandwindow.cpp | 16 +++++++++++++--- tests/auto/client/shared/coreprotocol.h | 2 +- tests/auto/client/shared_old/mockcompositor.cpp | 2 +- tests/auto/client/shared_old/mocksurface.cpp | 10 ++++++++++ tests/auto/client/shared_old/mocksurface.h | 2 ++ 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 4a331a4c..f23c459d 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -493,7 +493,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin if (interface == QStringLiteral("wl_output")) { mWaitingScreens << new QWaylandScreen(this, version, id); } else if (interface == QStringLiteral("wl_compositor")) { - mCompositorVersion = qMin((int)version, 3); + mCompositorVersion = qMin((int)version, 4); mCompositor.init(registry, id, mCompositorVersion); } else if (interface == QStringLiteral("wl_shm")) { mShm.reset(new QWaylandShm(this, version, id)); diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index d98a70e4..60665394 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -563,7 +563,11 @@ void QWaylandWindow::damage(const QRect &rect) if (mSurface == nullptr) return; - mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + const int s = scale(); + if (mDisplay->compositorVersion() >= 4) + mSurface->damage_buffer(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()); + else + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); } void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage) @@ -599,8 +603,14 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) return; attachOffset(buffer); - for (const QRect &rect: damage) - mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + if (mDisplay->compositorVersion() >= 4) { + const int s = scale(); + for (const QRect &rect: damage) + mSurface->damage_buffer(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()); + } else { + for (const QRect &rect: damage) + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } Q_ASSERT(!buffer->committed()); buffer->setCommitted(); mSurface->commit(); diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h index a1af137a..296dbf47 100644 --- a/tests/auto/client/shared/coreprotocol.h +++ b/tests/auto/client/shared/coreprotocol.h @@ -158,7 +158,7 @@ class WlCompositor : public Global, public QtWaylandServer::wl_compositor { Q_OBJECT public: - explicit WlCompositor(CoreCompositor *compositor, int version = 3) + explicit WlCompositor(CoreCompositor *compositor, int version = 4) : QtWaylandServer::wl_compositor(compositor->m_display, version) , m_compositor(compositor) {} diff --git a/tests/auto/client/shared_old/mockcompositor.cpp b/tests/auto/client/shared_old/mockcompositor.cpp index a415cbf5..b1d3d07d 100644 --- a/tests/auto/client/shared_old/mockcompositor.cpp +++ b/tests/auto/client/shared_old/mockcompositor.cpp @@ -342,7 +342,7 @@ Compositor::Compositor(MockCompositor *mockCompositor) exit(EXIT_FAILURE); } - wl_global_create(m_display, &wl_compositor_interface, 1, this, bindCompositor); + wl_global_create(m_display, &wl_compositor_interface, 4, this, bindCompositor); m_data_device_manager.reset(new DataDeviceManager(this, m_display)); diff --git a/tests/auto/client/shared_old/mocksurface.cpp b/tests/auto/client/shared_old/mocksurface.cpp index e9df5f90..c3246e4a 100644 --- a/tests/auto/client/shared_old/mocksurface.cpp +++ b/tests/auto/client/shared_old/mocksurface.cpp @@ -125,6 +125,16 @@ void Surface::surface_damage(Resource *resource, Q_UNUSED(height); } +void Surface::surface_damage_buffer(Resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(width); + Q_UNUSED(height); +} + void Surface::surface_frame(Resource *resource, uint32_t callback) { diff --git a/tests/auto/client/shared_old/mocksurface.h b/tests/auto/client/shared_old/mocksurface.h index 949dc23d..d176837e 100644 --- a/tests/auto/client/shared_old/mocksurface.h +++ b/tests/auto/client/shared_old/mocksurface.h @@ -65,6 +65,8 @@ protected: struct wl_resource *buffer, int x, int y) override; void surface_damage(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override; + void surface_damage_buffer(Resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) override; void surface_frame(Resource *resource, uint32_t callback) override; void surface_commit(Resource *resource) override; -- 2.49.0 From 0935e540b8a35f451cd473665c81deb8ccc7cc89 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Fri, 5 Aug 2022 15:00:31 +0100 Subject: [PATCH 31/57] Client: clear focus on touch cancel When we get a touch_cancel event all touches should be treated as lifted. The next frame call focus is set, with no pending touch points but without having gone through touch_up. We call mPendingTouchPoints.last() without guards even though it is potentially now empty. Change-Id: I3719f9507c5d397d8641692271d878076b7c23b8 Reviewed-by: Shawn Rutledge Reviewed-by: Liang Qi Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit dbdcd92363b44d89440dcb195d8cb9e6c34f0ddf) --- src/client/qwaylandinputdevice.cpp | 1 + tests/auto/client/seatv5/tst_seatv5.cpp | 30 +++++++++++++++++++++++ tests/auto/client/shared/coreprotocol.cpp | 7 ++++++ tests/auto/client/shared/coreprotocol.h | 1 + 4 files changed, 39 insertions(+) diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 5ec64faf..76cbfb1f 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -1391,6 +1391,7 @@ void QWaylandInputDevice::Touch::touch_cancel() if (touchExt) touchExt->touchCanceled(); + mFocus = nullptr; QWindowSystemInterface::handleTouchCancelEvent(nullptr, mParent->mTouchDevice); } diff --git a/tests/auto/client/seatv5/tst_seatv5.cpp b/tests/auto/client/seatv5/tst_seatv5.cpp index 9312c2e5..b063e0d9 100644 --- a/tests/auto/client/seatv5/tst_seatv5.cpp +++ b/tests/auto/client/seatv5/tst_seatv5.cpp @@ -73,6 +73,7 @@ private slots: void multiTouch(); void multiTouchUpAndMotionFrame(); void tapAndMoveInSameFrame(); + void cancelTouch(); }; void tst_seatv5::bindsToSeat() @@ -646,5 +647,34 @@ void tst_seatv5::tapAndMoveInSameFrame() QTRY_COMPARE(window.m_events.last().touchPoints.first().state(), Qt::TouchPointState::TouchPointReleased); } +void tst_seatv5::cancelTouch() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32, 32}, 1); + t->sendFrame(c); + t->sendCancel(c); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.length(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchCancel); + QCOMPARE(e.touchPoints.length(), 0); + } +} + QCOMPOSITOR_TEST_MAIN(tst_seatv5) #include "tst_seatv5.moc" diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp index 0d988521..d1a2e7cb 100644 --- a/tests/auto/client/shared/coreprotocol.cpp +++ b/tests/auto/client/shared/coreprotocol.cpp @@ -451,6 +451,13 @@ void Touch::sendFrame(wl_client *client) send_frame(r->handle); } +void Touch::sendCancel(wl_client *client) +{ + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + send_cancel(r->handle); +} + uint Keyboard::sendEnter(Surface *surface) { auto serial = m_seat->m_compositor->nextSerial(); diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h index 296dbf47..210d8ddb 100644 --- a/tests/auto/client/shared/coreprotocol.h +++ b/tests/auto/client/shared/coreprotocol.h @@ -364,6 +364,7 @@ public: uint sendUp(wl_client *client, int id); void sendMotion(wl_client *client, const QPointF &position, int id); void sendFrame(wl_client *client); + void sendCancel(wl_client *client); Seat *m_seat = nullptr; }; -- 2.49.0 From 1eba2ed4c7a45c4cf6098dac9c4e578da2880be2 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Thu, 3 Feb 2022 19:42:33 +0000 Subject: [PATCH 32/57] Guard mResizeDirty by the correctMutex mResizeDirty is used in the GUI thread in setCanResize which can be called from the GUI thread. It is queried and set whilst the resizeLock is held. We need to guard our usage. Change-Id: I5f8dcf8aa2cb2c4bb6274103df1da9e3e268605a Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 4ac96662c936821efff2875bbe555b40612caf8a) --- src/client/qwaylandwindow.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 60665394..e5d1a97d 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -358,11 +358,12 @@ void QWaylandWindow::setGeometry(const QRect &rect) if (mWindowDecoration) mWindowDecoration->update(); - if (mResizeAfterSwap && windowType() == Egl && mSentInitialResize) + if (mResizeAfterSwap && windowType() == Egl && mSentInitialResize) { + QMutexLocker lock(&mResizeLock); mResizeDirty = true; - else + } else { QWindowSystemInterface::handleGeometryChange(window(), geometry()); - + } mSentInitialResize = true; } QRect exposeGeometry(QPoint(), geometry().size()); -- 2.49.0 From 276082c73945b024ac603f65fad8699c5a70a17c Mon Sep 17 00:00:00 2001 From: Albert Astals Cid Date: Fri, 9 Sep 2022 15:37:49 +0200 Subject: [PATCH 33/57] Fix compile tests Broken in c618467da4c06528537026e2b78f92265bce446f --- tests/auto/client/seatv5/tst_seatv5.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/auto/client/seatv5/tst_seatv5.cpp b/tests/auto/client/seatv5/tst_seatv5.cpp index b063e0d9..2ea382f1 100644 --- a/tests/auto/client/seatv5/tst_seatv5.cpp +++ b/tests/auto/client/seatv5/tst_seatv5.cpp @@ -665,9 +665,9 @@ void tst_seatv5::cancelTouch() { auto e = window.m_events.takeFirst(); QCOMPARE(e.type, QEvent::TouchBegin); - QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPointStates, Qt::TouchPointPressed); QCOMPARE(e.touchPoints.length(), 1); - QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + QCOMPARE(e.touchPoints.first().pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); } { auto e = window.m_events.takeFirst(); -- 2.49.0 From 9052ae44c73bcf9b538f1a37e8797848492b4cd2 Mon Sep 17 00:00:00 2001 From: Fushan Wen Date: Sun, 18 Sep 2022 18:17:18 +0800 Subject: [PATCH 34/57] Call `finishDrag()` in `QWaylandDataDevice::dragSourceCancelled()` Drags can either get finished or cancelled. If a drag is finished successfully we call finish on the QBasicDrag instance, which quits the nested event loop. This patch adds the connection for cancelled drags. See also: https://bugs.kde.org/show_bug.cgi?id=446111 Pick-to: 6.4 6.2 5.15 Change-Id: Ib93040648da88a433d647c87adcb7a7fabcaef6c Reviewed-by: Liang Qi (cherry picked from commit c92282b865efcf8c571bb52b5f96d8ad260a1cda) BUG: 446111 --- src/client/qwaylanddatadevice.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/qwaylanddatadevice.cpp b/src/client/qwaylanddatadevice.cpp index 89e4e372..5c5b8485 100644 --- a/src/client/qwaylanddatadevice.cpp +++ b/src/client/qwaylanddatadevice.cpp @@ -302,6 +302,7 @@ void QWaylandDataDevice::selectionSourceCancelled() #if QT_CONFIG(draganddrop) void QWaylandDataDevice::dragSourceCancelled() { + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(); m_dragSource.reset(); } -- 2.49.0 From cdea3b7e5f10e0f9c55135ba27a62f2677231160 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Mon, 12 Sep 2022 13:28:08 +0100 Subject: [PATCH 35/57] Hold surface read lock throughout QWaylandEglWindow::updateSurface QWaylandEGLWindow::updateSurface is called from both the main and render threads. It is called on the render thread when making the surface current, which could be after the window is hidden if there are cleanup jobs to be done. Whilst the getter wlSurface() holds a read lock, it's not enough as we need the instance alive between the two calls and throughout the mesa code. This potentially fixes a crash seen in mesa where we crash creating a surface for an invalid wl_surface object. Change-Id: I497356e752ffaf3549d174f10c4c268234b02cbd Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 50f1ccc66c68f9f4c0b08400747942109c16b2be) --- src/client/qwaylandwindow_p.h | 6 ++++-- .../client/wayland-egl/qwaylandeglwindow.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 2be87bc0..ea3d1995 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -220,7 +220,11 @@ signals: protected: QWaylandDisplay *mDisplay = nullptr; + + // mSurface can be written by the main thread. Other threads should claim a read lock for access + mutable QReadWriteLock mSurfaceLock; QScopedPointer mSurface; + QWaylandShellSurface *mShellSurface = nullptr; QWaylandSubSurface *mSubSurfaceWindow = nullptr; QVector mChildren; @@ -294,8 +298,6 @@ private: static QWaylandWindow *mMouseGrab; - mutable QReadWriteLock mSurfaceLock; - friend class QWaylandSubSurface; }; diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp index 64f7caeb..dbe2845a 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp +++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp @@ -40,6 +40,7 @@ #include "qwaylandeglwindow.h" #include +#include #include "qwaylandglcontext.h" #include @@ -115,6 +116,7 @@ void QWaylandEglWindow::updateSurface(bool create) } mOffset = QPoint(); } else { + QReadLocker locker(&mSurfaceLock); if (m_waylandEglWindow) { int current_width, current_height; static bool disableResizeCheck = qgetenv("QT_WAYLAND_DISABLE_RESIZECHECK").toInt(); @@ -129,8 +131,8 @@ void QWaylandEglWindow::updateSurface(bool create) m_resize = true; } - } else if (create && wlSurface()) { - m_waylandEglWindow = wl_egl_window_create(wlSurface(), sizeWithMargins.width(), sizeWithMargins.height()); + } else if (create && mSurface) { + m_waylandEglWindow = wl_egl_window_create(mSurface->object(), sizeWithMargins.width(), sizeWithMargins.height()); m_requestedSize = sizeWithMargins; } -- 2.49.0 From cb024141363e56cca76304457098ff7fbe7dd3e2 Mon Sep 17 00:00:00 2001 From: David Redondo Date: Wed, 8 Jun 2022 11:25:59 +0200 Subject: [PATCH 36/57] Keep toplevel windows in the top left corner of the screen We can't know the actual position of a window on the screen. This causes an issue when Widgets try to position a popup/menu absolutely and keep it on the screen when the screen geometry doesn't include (0,0). Instead report their positions always as the top left corner of the screen that they are on. This new behavior can be disabled for qt-shell or via an environment variable by users that rely on the old behavior. Fixes: QTBUG-85297 Change-Id: Iacb91cb03a0df87af950115760d2f41124ac06a3 Reviewed-by: Qt CI Bot Reviewed-by: David Edmundson Reviewed-by: Aleix Pol Gonzalez (cherry picked from commit a46795a22e05722917c6ebc60ed01bebf49898ae) --- src/client/qwaylandintegration.cpp | 3 +++ src/client/qwaylandwindow.cpp | 14 +++++++++++++- src/client/qwaylandwindow_p.h | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index fbf00c6b..54861600 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -125,6 +125,9 @@ QWaylandIntegration::QWaylandIntegration() #endif reconfigureInputContext(); + + QWaylandWindow::fixedToplevelPositions = + !qEnvironmentVariableIsSet("QT_WAYLAND_DISABLE_FIXED_POSITIONS"); } QWaylandIntegration::~QWaylandIntegration() diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index e5d1a97d..464441b1 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -350,8 +350,13 @@ void QWaylandWindow::setGeometry_helper(const QRect &rect) } } -void QWaylandWindow::setGeometry(const QRect &rect) +void QWaylandWindow::setGeometry(const QRect &r) { + auto rect = r; + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip) { + rect.moveTo(screen()->geometry().topLeft()); + } setGeometry_helper(rect); if (window()->isVisible() && rect.isValid()) { @@ -1033,6 +1038,13 @@ void QWaylandWindow::handleScreensChanged() QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); mLastReportedScreen = newScreen; + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip + && geometry().topLeft() != newScreen->geometry().topLeft()) { + auto geometry = this->geometry(); + geometry.moveTo(newScreen->geometry().topLeft()); + setGeometry(geometry); + } int scale = newScreen->isPlaceholder() ? 1 : static_cast(newScreen)->scale(); if (scale != mScale) { diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index ea3d1995..487a91a6 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -98,6 +98,9 @@ public: QWaylandWindow(QWindow *window, QWaylandDisplay *display); ~QWaylandWindow() override; + // Keep Toplevels position on the top left corner of their screen + static inline bool fixedToplevelPositions = true; + virtual WindowType windowType() const = 0; virtual void ensureSize(); WId winId() const override; -- 2.49.0 From f04b16e84979b8ec576adf5bdfdc3abf5aab696b Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Thu, 17 Nov 2022 15:25:37 +0200 Subject: [PATCH 37/57] Client: Add F_SEAL_SHRINK seal to shm backing file This lets libwayland-server avoid installing a SIGBUS handler when it wants to mmap() the backing file and access the contents of shared memory client buffers. Change-Id: Id0b17f729799535d73e8700c5a99c32fd88a068a Reviewed-by: Qt CI Bot Reviewed-by: David Edmundson (cherry picked from commit 0c1cbb376e0cf878e3a91ab4917fe784a3b4c547) --- src/client/qwaylandshmbackingstore.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index dc7ff670..98acd42d 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -52,6 +52,7 @@ #include +#include #include #include @@ -61,6 +62,9 @@ # ifndef MFD_CLOEXEC # define MFD_CLOEXEC 0x0001U # endif +# ifndef MFD_ALLOW_SEALING +# define MFD_ALLOW_SEALING 0x0002U +# endif #endif QT_BEGIN_NAMESPACE @@ -75,7 +79,9 @@ QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, int fd = -1; #ifdef SYS_memfd_create - fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC); + fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); #endif QScopedPointer filePointer; -- 2.49.0 From a18c3a89bd7dc8d9b7acc1cbde157147479731d6 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Mon, 21 Nov 2022 18:39:40 +0200 Subject: [PATCH 38/57] Client: Call wl_output_release() upon QWaylandScreen destruction It ensures that the proxy gets destroyed. Change-Id: I915cc8814e33dd3b0405b2bf82bd12ce6b5f785b Reviewed-by: David Edmundson (cherry picked from commit 054e54759dbd6c3e76b55d5c4a9a54f62967ad1a) --- src/client/qwaylandscreen.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/qwaylandscreen.cpp b/src/client/qwaylandscreen.cpp index 7c2d9be3..64ae4fe7 100644 --- a/src/client/qwaylandscreen.cpp +++ b/src/client/qwaylandscreen.cpp @@ -81,6 +81,8 @@ QWaylandScreen::~QWaylandScreen() { if (zxdg_output_v1::isInitialized()) zxdg_output_v1::destroy(); + if (wl_output::isInitialized() && wl_output_get_version(wl_output::object()) >= WL_OUTPUT_RELEASE_SINCE_VERSION) + wl_output::release(); } uint QWaylandScreen::requiredEvents() const -- 2.49.0 From 9af4a53aa7a1ec0dad74d115527eeec18b5f4075 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 22 Nov 2022 12:33:41 +0200 Subject: [PATCH 39/57] Client: Bump wl_output version wl_output_release is available starting with wl_output v3. Change-Id: I21822b26728ffb9318f1f8b4bd82ef7329682838 Reviewed-by: David Edmundson (cherry picked from commit c14916f5fd84f6b5483024b3df77592661a0f04e) --- src/client/qwaylandscreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/qwaylandscreen.cpp b/src/client/qwaylandscreen.cpp index 64ae4fe7..5537dafd 100644 --- a/src/client/qwaylandscreen.cpp +++ b/src/client/qwaylandscreen.cpp @@ -60,7 +60,7 @@ QWaylandXdgOutputManagerV1::QWaylandXdgOutputManagerV1(QWaylandDisplay* display, } QWaylandScreen::QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id) - : QtWayland::wl_output(waylandDisplay->wl_registry(), id, qMin(version, 2)) + : QtWayland::wl_output(waylandDisplay->wl_registry(), id, qMin(version, 3)) , m_outputId(id) , mWaylandDisplay(waylandDisplay) , mOutputName(QStringLiteral("Screen%1").arg(id)) -- 2.49.0 From eade2f1f746d239410d185d88404c203fed7ebdf Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Sun, 27 Nov 2022 12:44:40 -0800 Subject: [PATCH 40/57] Fix frame sync related to unprotected multithread access There is a few crashes happens in real life that frame callback is double-free'd and hit an assertion in wayland-client. e.g. https://bugs.kde.org/show_bug.cgi?id=450003 This is due to the WaylandEventThread and calls to QWaylandWindow::reset may free and unset the mFrameCallback at the same time. mFrameSyncMutex should be used to protect such access. Pick-to: 6.4 Change-Id: Ie01d08d07a2f10f70606ed1935caac09cb4f0382 (cherry picked from commit b6cbb5e323822d6e3ef5ed4dd5a4c4cc1ea85038) --- src/client/qwaylandwindow.cpp | 64 ++++++++++++++++++++--------------- src/client/qwaylandwindow_p.h | 11 +++--- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 464441b1..96de798b 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -252,13 +252,16 @@ void QWaylandWindow::reset() mSurface.reset(); } - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - } + { + QMutexLocker lock(&mFrameSyncMutex); + if (mFrameCallback) { + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + } - mFrameCallbackElapsedTimer.invalidate(); - mWaitingForFrameCallback = false; + mFrameCallbackElapsedTimer.invalidate(); + mWaitingForFrameCallback = false; + } mFrameCallbackTimedOut = false; mMask = QRegion(); @@ -633,18 +636,21 @@ const wl_callback_listener QWaylandWindow::callbackListener = { [](void *data, wl_callback *callback, uint32_t time) { Q_UNUSED(time); auto *window = static_cast(data); - - Q_ASSERT(callback == window->mFrameCallback); - wl_callback_destroy(callback); - window->mFrameCallback = nullptr; - - window->handleFrameCallback(); + window->handleFrameCallback(callback); } }; -void QWaylandWindow::handleFrameCallback() +void QWaylandWindow::handleFrameCallback(wl_callback* callback) { QMutexLocker locker(&mFrameSyncMutex); + if (!mFrameCallback) { + // This means the callback is already unset by QWaylandWindow::reset. + // The wl_callback object will be destroyed there too. + return; + } + Q_ASSERT(callback == mFrameCallback); + wl_callback_destroy(callback); + mFrameCallback = nullptr; mWaitingForFrameCallback = false; mFrameCallbackElapsedTimer.invalidate(); @@ -1172,19 +1178,24 @@ void QWaylandWindow::timerEvent(QTimerEvent *event) if (event->timerId() != mFrameCallbackCheckIntervalTimerId) return; - bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); - if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { - killTimer(mFrameCallbackCheckIntervalTimerId); - mFrameCallbackCheckIntervalTimerId = -1; - } - if (mFrameCallbackElapsedTimer.isValid() && callbackTimerExpired) { - mFrameCallbackElapsedTimer.invalidate(); + { + QMutexLocker lock(&mFrameSyncMutex); - qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; - mFrameCallbackTimedOut = true; - mWaitingForUpdate = false; - sendExposeEvent(QRect()); + bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); + if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; + } + if (!mFrameCallbackElapsedTimer.isValid() || !callbackTimerExpired) { + return; + } + mFrameCallbackElapsedTimer.invalidate(); } + + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + sendExposeEvent(QRect()); } void QWaylandWindow::requestUpdate() @@ -1227,15 +1238,14 @@ void QWaylandWindow::handleUpdate() { qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread(); - if (mWaitingForFrameCallback) - return; - // TODO: Should sync subsurfaces avoid requesting frame callbacks? QReadLocker lock(&mSurfaceLock); if (!mSurface) return; QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; struct ::wl_surface *wrappedSurface = reinterpret_cast(wl_proxy_create_wrapper(mSurface->object())); wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mDisplay->frameEventQueue()); diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 487a91a6..2f219d8c 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -237,12 +237,13 @@ protected: Qt::MouseButtons mMousePressedInContentArea = Qt::NoButton; WId mWindowId; - bool mWaitingForFrameCallback = false; bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out - QAtomicInt mWaitingForUpdateDelivery = false; int mFrameCallbackCheckIntervalTimerId = -1; - QElapsedTimer mFrameCallbackElapsedTimer; - struct ::wl_callback *mFrameCallback = nullptr; + QAtomicInt mWaitingForUpdateDelivery = false; + + bool mWaitingForFrameCallback = false; // Protected by mFrameSyncMutex + QElapsedTimer mFrameCallbackElapsedTimer; // Protected by mFrameSyncMutex + struct ::wl_callback *mFrameCallback = nullptr; // Protected by mFrameSyncMutex QMutex mFrameSyncMutex; QWaitCondition mFrameSyncWait; @@ -297,7 +298,7 @@ private: QRect mLastExposeGeometry; static const wl_callback_listener callbackListener; - void handleFrameCallback(); + void handleFrameCallback(struct ::wl_callback* callback); static QWaylandWindow *mMouseGrab; -- 2.49.0 From 029095e9efb0c296771d3df6c68b03a3e8de791e Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 27 Sep 2022 22:05:07 +0300 Subject: [PATCH 41/57] Client: Handle zwp_primary_selection_device_manager_v1 global removal The zwp_primary_selection_device_manager_v1 global can be withdrawn if the compositor disables the primary selection, i.e. middle click to paste selected text. QtWayland needs to handle that; otherwise the app can crash. Pick-to: 6.5 Change-Id: Idbb4db18b605f85a5951fa12c1bdf61898b0d123 Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 45163234a4e4baad0012d3ee07501093d98ba91c) --- src/client/qwaylanddisplay.cpp | 9 +++++++++ src/client/qwaylandprimaryselectionv1.cpp | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index f23c459d..cc321497 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -519,6 +519,8 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin #if QT_CONFIG(wayland_client_primary_selection) } else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setPrimarySelectionDevice(mPrimarySelectionManager->createDevice(inputDevice)); #endif } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); @@ -577,6 +579,13 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) inputDevice->setTextInput(nullptr); mWaylandIntegration->reconfigureInputContext(); } +#if QT_CONFIG(wayland_client_primary_selection) + if (global.interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { + mPrimarySelectionManager.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setPrimarySelectionDevice(nullptr); + } +#endif mGlobals.removeAt(i); break; } diff --git a/src/client/qwaylandprimaryselectionv1.cpp b/src/client/qwaylandprimaryselectionv1.cpp index 7805dd73..dac532b2 100644 --- a/src/client/qwaylandprimaryselectionv1.cpp +++ b/src/client/qwaylandprimaryselectionv1.cpp @@ -54,11 +54,6 @@ QWaylandPrimarySelectionDeviceManagerV1::QWaylandPrimarySelectionDeviceManagerV1 : zwp_primary_selection_device_manager_v1(display->wl_registry(), id, qMin(version, uint(1))) , m_display(display) { - // Create devices for all seats. - // This only works if we get the global before all devices - const auto seats = m_display->inputDevices(); - for (auto *seat : seats) - seat->setPrimarySelectionDevice(createDevice(seat)); } QWaylandPrimarySelectionDeviceV1 *QWaylandPrimarySelectionDeviceManagerV1::createDevice(QWaylandInputDevice *seat) -- 2.49.0 From 42fead566aa704237efa3c883597d4df32b56c3c Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Mon, 19 Dec 2022 15:31:03 +0100 Subject: [PATCH 42/57] Fixes the build on CentOS Change-Id: I3c21972e7681be99b0f45c3ea3a57be285e4ff8e --- src/client/qwaylandshmbackingstore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index 98acd42d..41cffdf7 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -78,7 +78,7 @@ QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, int alloc = stride * size.height(); int fd = -1; -#ifdef SYS_memfd_create +#if defined(SYS_memfd_create) && defined(F_SEAL_SEAL) fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd >= 0) fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); -- 2.49.0 From 14ae8c7d0375693ab15b7642876a2114495a2d56 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Mon, 23 May 2022 09:47:24 +0200 Subject: [PATCH 43/57] client: Avoid protocol error with invalid min/max size If the application sets an invalid minimum and maximum size (where the minimum is higher than the maximum), then we would blindly send this over the protocol, which is a protocol error according to the spec. Qt compositors will warn about this and ignore the size, but mainly because "but there's no matching error defined" according to the comment. Other compositors may close the connection when this happens. To avoid crashing the app based on bogus min/max size, we make sure we never send a maximum size which is less than the minimum size. This corresponds to the behavior of compositors which accept the size without raising an error: the minimum size takes precedence. Note that 0 means "no maximum size" in the protocol, so we cap the value before applying this logic. [ChangeLog][Client] Fixed an issue where setting an invalid minimum and maximum size on a window would cause some compositors to raise a protocol error. Pick-to: 6.2 6.3 Fixes: QTBUG-102626 Fixes: QTBUG-103391 Change-Id: I4004a4550a9fe3dae6a27169b4d1a9a616e21841 Reviewed-by: David Edmundson (cherry picked from commit 487de47277ccc31891f6340ce4c971c91336d9a4) --- src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index c79b0e49..6ddcc410 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -387,10 +387,10 @@ void QWaylandXdgSurface::setSizeHints() const int minHeight = qMax(0, m_window->windowMinimumSize().height()); m_toplevel->set_min_size(minWidth, minHeight); - int maxWidth = qMax(0, m_window->windowMaximumSize().width()); + int maxWidth = qMax(minWidth, m_window->windowMaximumSize().width()); if (maxWidth == QWINDOWSIZE_MAX) maxWidth = 0; - int maxHeight = qMax(0, m_window->windowMaximumSize().height()); + int maxHeight = qMax(minHeight, m_window->windowMaximumSize().height()); if (maxHeight == QWINDOWSIZE_MAX) maxHeight = 0; m_toplevel->set_max_size(maxWidth, maxHeight); -- 2.49.0 From e4922c95446cf0693da5c47d1064a5120e6287ce Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 22 Nov 2022 23:27:34 +0200 Subject: [PATCH 44/57] Client: Fix handling of Qt::BlankCursor The cursor may not be properly set when a window has Qt::BlankCursor and it's shown. In that case, the cursor surface may not be present and wl_pointer.set_cursor won't be called. On the other hand, wl_pointer.set_cursor must be always called when wl_pointer.enter is received. Pick-to: 6.5 Change-Id: I8540e7a02df1579b3380a1a1d4cfab42c1ab3104 Reviewed-by: David Edmundson Reviewed-by: Qt CI Bot (cherry picked from commit e954853f0e68d78ac1a98bc3533713881496064c) --- src/client/qwaylandinputdevice.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 76cbfb1f..278e31f9 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -311,8 +311,7 @@ void QWaylandInputDevice::Pointer::updateCursor() auto shape = seat()->mCursor.shape; if (shape == Qt::BlankCursor) { - if (mCursor.surface) - mCursor.surface->hide(); + getOrCreateCursorSurface()->hide(); return; } -- 2.49.0 From 78b46c8d7045a8f4d40d55a959c995cdfabbe4d9 Mon Sep 17 00:00:00 2001 From: Marco Martin Date: Fri, 24 Feb 2023 17:40:48 +0100 Subject: [PATCH 45/57] client: Force a roundtrip when an XdgOutput is not ready yet Is possible that the server sends a surface_enter before all the information of the XdgOutput have been processed by the client. in this case the associated QScreen doesn't exist yet, causing a QWindow::SetScreen(nullptr), which will fall back to QGuiApplication::primaryScreen(), having the QWindow being assigned the wrong screen Change-Id: I923d5d3a35484deafa6f0572f79c16c27b1f87f0 Reviewed-by: David Edmundson --- src/client/qwaylandwindow.cpp | 2 ++ tests/auto/client/shared/coreprotocol.cpp | 2 ++ tests/auto/client/shared/coreprotocol.h | 3 ++ tests/auto/client/xdgoutput/tst_xdgoutput.cpp | 35 +++++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 96de798b..5280a9f7 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -1042,6 +1042,8 @@ void QWaylandWindow::handleScreensChanged() if (newScreen == mLastReportedScreen) return; + if (!newScreen->isPlaceholder() && !newScreen->QPlatformScreen::screen()) + mDisplay->forceRoundTrip(); QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); mLastReportedScreen = newScreen; if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp index d1a2e7cb..53e12291 100644 --- a/tests/auto/client/shared/coreprotocol.cpp +++ b/tests/auto/client/shared/coreprotocol.cpp @@ -185,6 +185,8 @@ void Output::output_bind_resource(QtWaylandServer::wl_output::Resource *resource if (m_version >= WL_OUTPUT_DONE_SINCE_VERSION) wl_output::send_done(resource->handle); + + Q_EMIT outputBound(resource); } // Seat stuff diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h index 210d8ddb..00c439e1 100644 --- a/tests/auto/client/shared/coreprotocol.h +++ b/tests/auto/client/shared/coreprotocol.h @@ -273,6 +273,9 @@ public: OutputData m_data; int m_version = 1; // TODO: remove on libwayland upgrade +Q_SIGNALS: + void outputBound(Resource *resource); + protected: void output_bind_resource(Resource *resource) override; }; diff --git a/tests/auto/client/xdgoutput/tst_xdgoutput.cpp b/tests/auto/client/xdgoutput/tst_xdgoutput.cpp index 20f762e0..2a0cad1d 100644 --- a/tests/auto/client/xdgoutput/tst_xdgoutput.cpp +++ b/tests/auto/client/xdgoutput/tst_xdgoutput.cpp @@ -55,6 +55,7 @@ private slots: void primaryScreen(); void overrideGeometry(); void changeGeometry(); + void outputCreateEnterRace(); }; void tst_xdgoutput::cleanup() @@ -134,5 +135,39 @@ void tst_xdgoutput::changeGeometry() exec([&] { remove(output(1)); }); } +void tst_xdgoutput::outputCreateEnterRace() +{ + m_config.autoConfigure = true; + m_config.autoEnter = false; + QRasterWindow window; + QSignalSpy screenChanged(&window, &QWindow::screenChanged); + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([=] { xdgToplevel()->surface()->sendEnter(output(0));}); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + auto *out = exec([=] { + return add(); + }); + + // In Compositor Thread + connect(out, &Output::outputBound, this, [this](QtWaylandServer::wl_output::Resource *resource){ + auto surface = xdgToplevel()->surface(); + surface->sendLeave(output(0)); + surface->QtWaylandServer::wl_surface::send_enter(surface->resource()->handle, resource->handle); + }, Qt::DirectConnection); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(window.screen(), QGuiApplication::screens()[1]); + + exec([=] { remove(out); }); + m_config.autoConfigure = false; + m_config.autoEnter = true; +} + QCOMPOSITOR_TEST_MAIN(tst_xdgoutput) #include "tst_xdgoutput.moc" -- 2.49.0 From d6fc2f8ddc1eb2d016b7bbeccef5f433e9d8e0b6 Mon Sep 17 00:00:00 2001 From: David Redondo Date: Tue, 11 Apr 2023 14:27:27 +0200 Subject: [PATCH 46/57] Destroy frame queue before display wl_event_queue_destroy accesses the display. Found by running a test under valgrind. Pick-to: 6.5 Change-Id: Ic89cbd3b6e98b4fc9561b0e63b5fab4886a1ec50 Reviewed-by: David Edmundson (cherry picked from commit a76bf824fcd1cc3789f0d3454a0423c0241d9718) --- src/client/qwaylanddisplay.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index cc321497..737b539d 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -379,11 +379,12 @@ QWaylandDisplay::~QWaylandDisplay(void) #if QT_CONFIG(cursor) qDeleteAll(mCursorThemes); #endif - if (mDisplay) - wl_display_disconnect(mDisplay); if (m_frameEventQueue) wl_event_queue_destroy(m_frameEventQueue); + + if (mDisplay) + wl_display_disconnect(mDisplay); } // Steps which is called just after constructor. This separates registry_global() out of the constructor -- 2.49.0 From 80bce1e6a818ee86011cfdad6b2a9a79cd4e0300 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Wed, 7 Jun 2023 22:12:15 +0100 Subject: [PATCH 47/57] client: Fix crash on dnd updates after client facing drag ends A platform drag and a application-facing drag have two different lifespans. The platform drag lasts until all mimedata is transferred and the client receiving the drops marks it as finished. The application facing QDrag lasts until the client deletes it. We can get a crash if we get updates during this time. The drop event is guarded, but not the action negotiation. Pick-to: 6.6 Change-Id: Ib9c047f04d65883105d4cd3f169637d0e038a63f Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 22daca49b807fefba58113a06b86df4274e49f62) --- src/client/qwaylanddatadevice.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/qwaylanddatadevice.cpp b/src/client/qwaylanddatadevice.cpp index 5c5b8485..c57f1a49 100644 --- a/src/client/qwaylanddatadevice.cpp +++ b/src/client/qwaylanddatadevice.cpp @@ -138,6 +138,9 @@ bool QWaylandDataDevice::startDrag(QMimeData *mimeData, Qt::DropActions supporte connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled); connect(m_dragSource.data(), &QWaylandDataSource::dndResponseUpdated, this, [this](bool accepted, Qt::DropAction action) { auto drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); + if (!drag->currentDrag()) { + return; + } // in old versions drop action is not set, so we guess if (wl_data_source_get_version(m_dragSource->object()) < 3) { drag->setResponse(accepted); -- 2.49.0 From 5223ce7a3620a35197d98be95d7d8ffbe1565979 Mon Sep 17 00:00:00 2001 From: Michael Weghorn Date: Mon, 20 Feb 2023 14:02:23 +0100 Subject: [PATCH 48/57] Convert cursor bitmap to supported format The 1-bit image formats QImage::Format_Mono and QImage::Format_MonoLSB used by cursor bitmaps don't have a corresponding wl_shm_format. Therefore, convert to a supported image format as necessary to make such bitmap cursors work on Wayland as well. Fixes: QTBUG-95434 Change-Id: I402fd870b301ddc01075251b66f2cf7cc1923133 Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit 45ec1362f8fcb5ade92f4d2d4985b1c24e78c8ba) Backport changes: Use Qt::ReturnByValue version for QCursor::mask() and QCursor::bitmap() --- src/client/qwaylandcursor.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/client/qwaylandcursor.cpp b/src/client/qwaylandcursor.cpp index e4eca9d4..ba76ba2d 100644 --- a/src/client/qwaylandcursor.cpp +++ b/src/client/qwaylandcursor.cpp @@ -44,6 +44,7 @@ #include "qwaylandshmbackingstore_p.h" #include +#include #include #include @@ -250,7 +251,27 @@ QWaylandCursor::QWaylandCursor(QWaylandDisplay *display) QSharedPointer QWaylandCursor::cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor) { Q_ASSERT(cursor->shape() == Qt::BitmapCursor); - const QImage &img = cursor->pixmap().toImage(); + + const QBitmap mask = cursor->mask(Qt::ReturnByValue); + QImage img; + if (cursor->pixmap().isNull()) + img = cursor->bitmap(Qt::ReturnByValue).toImage(); + else + img = cursor->pixmap().toImage(); + + // convert to supported format if necessary + if (!display->shm()->formatSupported(img.format())) { + if (mask.isNull()) { + img.convertTo(QImage::Format_RGB32); + } else { + // preserve mask + img.convertTo(QImage::Format_ARGB32); + QPixmap pixmap = QPixmap::fromImage(img); + pixmap.setMask(mask); + img = pixmap.toImage(); + } + } + QSharedPointer buffer(new QWaylandShmBuffer(display, img.size(), img.format())); memcpy(buffer->image()->bits(), img.bits(), size_t(img.sizeInBytes())); return buffer; -- 2.49.0 From 9214e8235fa31123e83fda8b2a6bc32999863c1d Mon Sep 17 00:00:00 2001 From: Jungi Byun Date: Wed, 27 Jan 2021 08:24:23 +0900 Subject: [PATCH 49/57] Replace scale with devicePixelRatio for non-integer scaling The 'scale' event from wayland cannot support non-integer scaling which was originally supported in Qt. As default, devicePixelRatio follows the 'scale' so that the high DPI still works as the mechanism in Wayland. But if non-integer scaling factor such as 150% is needed, it can be supported to override the devicePixelRatio. Change-Id: I63a04db27bd521264b6d0904e1ddd05a572dc970 Reviewed-by: Elvis Lee Reviewed-by: Jungi Byun Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit cf98abbc6ae9ba9373803ffe193f839324e0c80b) --- src/client/qwaylandabstractdecoration.cpp | 2 +- src/client/qwaylandshmbackingstore.cpp | 6 +++--- src/client/qwaylandshmbackingstore_p.h | 2 +- src/client/qwaylandwindow.cpp | 16 ++++++++-------- src/client/qwaylandwindow_p.h | 2 +- .../client/wayland-egl/qwaylandglcontext.cpp | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/client/qwaylandabstractdecoration.cpp b/src/client/qwaylandabstractdecoration.cpp index b628930d..d15a7f9f 100644 --- a/src/client/qwaylandabstractdecoration.cpp +++ b/src/client/qwaylandabstractdecoration.cpp @@ -122,7 +122,7 @@ const QImage &QWaylandAbstractDecoration::contentImage() if (d->m_isDirty) { // Update the decoration backingstore - const int bufferScale = waylandWindow()->scale(); + const qreal bufferScale = waylandWindow()->scale(); const QSize imageSize = waylandWindow()->surfaceSize() * bufferScale; d->m_decorationContentImage = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); // Only scale by buffer scale, not QT_SCALE_FACTOR etc. diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index 41cffdf7..90e37e95 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -72,7 +72,7 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, - const QSize &size, QImage::Format format, int scale) + const QSize &size, QImage::Format format, qreal scale) { int stride = size.width() * 4; int alloc = stride * size.height(); @@ -114,7 +114,7 @@ QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, QWaylandShm* shm = display->shm(); wl_shm_format wl_format = shm->formatFrom(format); mImage = QImage(data, size.width(), size.height(), stride, format); - mImage.setDevicePixelRatio(qreal(scale)); + mImage.setDevicePixelRatio(scale); mShmPool = wl_shm_create_pool(shm->object(), fd, alloc); init(wl_shm_pool_create_buffer(mShmPool,0, size.width(), size.height(), @@ -277,7 +277,7 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size) void QWaylandShmBackingStore::resize(const QSize &size) { QMargins margins = windowDecorationMargins(); - int scale = waylandWindow()->scale(); + qreal scale = waylandWindow()->scale(); QSize sizeWithMargins = (size + QSize(margins.left()+margins.right(),margins.top()+margins.bottom())) * scale; // We look for a free buffer to draw into. If the buffer is not the last buffer we used, diff --git a/src/client/qwaylandshmbackingstore_p.h b/src/client/qwaylandshmbackingstore_p.h index e01632da..f3fae438 100644 --- a/src/client/qwaylandshmbackingstore_p.h +++ b/src/client/qwaylandshmbackingstore_p.h @@ -71,7 +71,7 @@ class QWaylandWindow; class Q_WAYLAND_CLIENT_EXPORT QWaylandShmBuffer : public QWaylandBuffer { public: QWaylandShmBuffer(QWaylandDisplay *display, - const QSize &size, QImage::Format format, int scale = 1); + const QSize &size, QImage::Format format, qreal scale = 1); ~QWaylandShmBuffer() override; QSize size() const override { return mImage.size(); } int scale() const override { return int(mImage.devicePixelRatio()); } diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 5280a9f7..38b10269 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -185,7 +185,7 @@ void QWaylandWindow::initWindow() // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale() // to inform the compositor that high-resolution buffers will be provided. if (mDisplay->compositorVersion() >= 3) - mSurface->set_buffer_scale(scale()); + mSurface->set_buffer_scale(mScale); if (QScreen *s = window()->screen()) setOrientationMask(s->orientationUpdateMask()); @@ -572,9 +572,9 @@ void QWaylandWindow::damage(const QRect &rect) if (mSurface == nullptr) return; - const int s = scale(); + const qreal s = scale(); if (mDisplay->compositorVersion() >= 4) - mSurface->damage_buffer(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()); + mSurface->damage_buffer(qFloor(s * rect.x()), qFloor(s * rect.y()), qCeil(s * rect.width()), qCeil(s * rect.height())); else mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); } @@ -613,9 +613,9 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) attachOffset(buffer); if (mDisplay->compositorVersion() >= 4) { - const int s = scale(); + const qreal s = scale(); for (const QRect &rect: damage) - mSurface->damage_buffer(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()); + mSurface->damage_buffer(qFloor(s * rect.x()), qFloor(s * rect.y()), qCeil(s * rect.width()), qCeil(s * rect.height())); } else { for (const QRect &rect: damage) mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); @@ -1106,14 +1106,14 @@ bool QWaylandWindow::isActive() const return mDisplay->isWindowActivated(this); } -int QWaylandWindow::scale() const +qreal QWaylandWindow::scale() const { - return mScale; + return devicePixelRatio(); } qreal QWaylandWindow::devicePixelRatio() const { - return mScale; + return qreal(mScale); } bool QWaylandWindow::setMouseGrabEnabled(bool grab) diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 2f219d8c..741f9e5c 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -158,7 +158,7 @@ public: void setMask(const QRegion ®ion) override; - int scale() const; + qreal scale() const; qreal devicePixelRatio() const override; void requestActivateWindow() override; diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp index c1f45fa6..bbc63444 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp +++ b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp @@ -195,7 +195,7 @@ public: QOpenGLTextureCache *cache = QOpenGLTextureCache::cacheForContext(m_context->context()); QSize surfaceSize = window->surfaceSize(); - int scale = window->scale() ; + qreal scale = window->scale() ; glViewport(0, 0, surfaceSize.width() * scale, surfaceSize.height() * scale); //Draw Decoration -- 2.49.0 From 9792ec7b5edc421c7ca88bd10cadc8b6b8462128 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Thu, 3 Aug 2023 12:28:44 +0300 Subject: [PATCH 50/57] Client: Fix buffer damage If the specified damage rectangle has fractional coordinates in the buffer local coordinate space, the buffer damage needs to be expanded, i.e. - bufferRect.left = floor(rect.left * scale) - bufferRect.right = ceil(rect.right * scale) = ceil((rect.x + rect.width) * scale) Flooring the coordinates and ceiling the size is not enough. It can produce incorrect results. For example, consider that a rectangle with logical coordinates of QRect(0, 23, 179, 46) has been damaged in a window with scale 1.5. When flooring the coordinates and ceiling the size, the following buffer damage rect will be produced: QRect(0, 34, 269, 69). Its height is off by 1, the expected height is 70 (ceil((23 + 46) * 1.5) - floor(23 * 1.5) = ceil(103.5) - floor(34.5) = 104 - 34 = 70). Pick-to: 5.15 6.5 6.6 Change-Id: I927e75a2224bb58b4634125011d1305dbdfbb3aa Reviewed-by: David Edmundson (cherry picked from commit d79db699866b37bd3e3358ca18a210dfc5c0b4b9) --- src/client/qwaylandwindow.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 38b10269..5d01507d 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -573,10 +573,15 @@ void QWaylandWindow::damage(const QRect &rect) return; const qreal s = scale(); - if (mDisplay->compositorVersion() >= 4) - mSurface->damage_buffer(qFloor(s * rect.x()), qFloor(s * rect.y()), qCeil(s * rect.width()), qCeil(s * rect.height())); - else + if (mDisplay->compositorVersion() >= 4) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } else { mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } } void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage) @@ -614,8 +619,13 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) attachOffset(buffer); if (mDisplay->compositorVersion() >= 4) { const qreal s = scale(); - for (const QRect &rect: damage) - mSurface->damage_buffer(qFloor(s * rect.x()), qFloor(s * rect.y()), qCeil(s * rect.width()), qCeil(s * rect.height())); + for (const QRect &rect : damage) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } } else { for (const QRect &rect: damage) mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); -- 2.49.0 From b2757109906778aee52f8fd97f129800b8b10139 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Fri, 25 Aug 2023 10:19:07 +0300 Subject: [PATCH 51/57] Client: Commit the initial surface state explicitly QWaylandWindow lacks an explicit step to finish initializing the shell surface by committing the surface. So far it used to work because of hidden surface commits in QWaylandWindow::handleContentOrientationChange(), QWaylandWindow::setMask() and so on. This change adds an explicit step to commit the initial surface state to make the shell surface initialization robust. Change-Id: Ibc38a4e0dbea689a727451c25a61af0270c7e548 Reviewed-by: David Edmundson (cherry picked from commit 225432c2294bdfbf24856b2f155cd274b24543b2) --- src/client/qwaylandwindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 5d01507d..5eee0414 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -200,6 +200,8 @@ void QWaylandWindow::initWindow() mShellSurface->requestWindowStates(window()->windowStates()); handleContentOrientationChange(window()->contentOrientation()); mFlags = window()->flags(); + + mSurface->commit(); } void QWaylandWindow::initializeWlSurface() -- 2.49.0 From cd324e2ff6130dbd2a17799e28a87743894e5d94 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Fri, 15 Sep 2023 10:06:32 +0300 Subject: [PATCH 52/57] tests: Fix tst_xdgshell::minMaxSize() Amends e8cff6fb39c0fd01548bce18542820a6612dbe49. The new size hints will be committed when the surface is committed. Change-Id: I94e944fee7dac63d5e9ac86fb348b5d24d54abfc Reviewed-by: Eskil Abrahamsen Blomfeldt (cherry picked from commit d8d3d6097afeac62f1b0285e3d5365c7cb580547) --- tests/auto/client/xdgshell/tst_xdgshell.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp index c5271f63..f2181fd6 100644 --- a/tests/auto/client/xdgshell/tst_xdgshell.cpp +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -604,9 +604,11 @@ void tst_xdgshell::minMaxSize() QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, QSize(1000, 1000)); window.setMaximumSize(QSize(500, 400)); + window.update(); QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, QSize(500, 400)); window.setMinimumSize(QSize(50, 40)); + window.update(); QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, QSize(50, 40)); } -- 2.49.0 From 1bbec113edfd6424f769a83b877541e5147f8233 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Fri, 25 Aug 2023 10:15:29 +0300 Subject: [PATCH 53/57] Client: Remove some surface commits The buffer transform, input and opaque regions are double buffered state. They will be applied on the next surface commit. But the issue with them is that the relevant code makes surface commits too. It's undesired as it can lead to qtwayland committing partial state, for example it can break xdg surface window geometry. This change removes hidden surface commits. The relevant properties will be applied on the next frame. Change-Id: I1c40c9a5430fb6b91d7643b20d628f8a9a9d501a Reviewed-by: David Edmundson (cherry picked from commit e8cff6fb39c0fd01548bce18542820a6612dbe49) --- src/client/qwaylandwindow.cpp | 4 ---- src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp | 3 --- 2 files changed, 7 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 5eee0414..7a9bccc1 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -478,8 +478,6 @@ void QWaylandWindow::setMask(const QRegion &mask) if (isOpaque()) setOpaqueArea(mMask); } - - mSurface->commit(); } void QWaylandWindow::applyConfigureWhenPossible() @@ -794,8 +792,6 @@ void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orient Q_UNREACHABLE(); } mSurface->set_buffer_transform(transform); - // set_buffer_transform is double buffered, we need to commit. - mSurface->commit(); } void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask) diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index 6ddcc410..9c6cbb81 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -370,9 +370,6 @@ bool QWaylandXdgSurface::wantsDecorations() const void QWaylandXdgSurface::propagateSizeHints() { setSizeHints(); - - if (m_toplevel && m_window) - m_window->commit(); } void QWaylandXdgSurface::setWindowGeometry(const QRect &rect) -- 2.49.0 From 686d1a0026a37f2fc3225b6f91ee30a73cb1c105 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 30 Aug 2023 09:49:41 +0300 Subject: [PATCH 54/57] Client: Avoid locking resizing in QWaylandShmBackingStore QWaylandWindow::setCanResize(false) will block applying configure events. QWaylandWindow::setCanResize(true) will unblock configure events and potentially apply a scheduled configure event if there's one. QWaylandWindow::setCanResize(true) has to be called **after** committing the surface to ensure that the xdg window geometry matches the buffer. We don't want the xdg window geometry change when painting. Unfortunately, setCanResize(true) can be called before the surface is committed when using a RasterSurface, for example - QWaylandShmBackingStore::beginPaint(): calls setCanResize(false) - QWaylandShmBackingStore::endPaint(): calls setCanResize(true) - QWaylandWindow::setCanResize(true): applies pending configure event - QWaylandShmBackingStore::flush(): commits the surface, but the xdg window geometry is wrong now As is, beginPaint() and endPaint() are not entirely correct functions where configure events can be blocked. We need functions that wrap both painting and flushing, which are not feasible with the current backing store design. On the other hand, it's worth noting that blocking configure events in the backing store is not necessary because painting happens on the main thread unlike OpenGL or Vulkan code paths. Given the lack of synchronization points and the fact that rendering happens on the main thread, this change removes blocking configure events in QWaylandShmBackingStore. It fixes dolphin and various other applications that use QtWidgets jumping while being interactively resized. Change-Id: I156e4fd5e04a6bba7e8d48171510d5ab0ec89713 Reviewed-by: David Edmundson (cherry picked from commit 8828452bcf2ecf4e02a64380a1697d148c4366b0) --- src/client/qwaylandshmbackingstore.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index 90e37e95..145f933b 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -186,8 +186,6 @@ void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) mPainting = true; ensureSize(); - waylandWindow()->setCanResize(false); - if (mBackBuffer->image()->hasAlphaChannel()) { QPainter p(paintDevice()); p.setCompositionMode(QPainter::CompositionMode_Source); @@ -202,7 +200,6 @@ void QWaylandShmBackingStore::endPaint() mPainting = false; if (mPendingFlush) flush(window(), mPendingRegion, QPoint()); - waylandWindow()->setCanResize(true); } void QWaylandShmBackingStore::ensureSize() -- 2.49.0 From 898f4f4b8761f54ba4f8ea15451ad22802ad6fc4 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Fri, 10 May 2024 13:20:30 +0200 Subject: [PATCH 55/57] Fix race condition in drag and drop The data source may be deleted by libwayland while we hold a reference to it. This could cause crashes when dragging and dropping repeatedly and very rapidly between two components. Tapping into sourceDestroyed() for this as well allows us to recover more gracefully. This also required adding some null pointer checks to the code, since it wasn't really prepared for the data source disappearing. Pick-to: 5.15 6.2 6.5 6.7 6.8 Fixes: QTBUG-124502 Change-Id: Ic3df8bf70176c5424ac5c693f8456f61e7b2762b Reviewed-by: Paul Olav Tvete (cherry picked from commit 792bd8510e3bc6b47bcaedfb1386390ce3a10a3a) --- src/compositor/wayland_wrapper/qwldatadevice.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/compositor/wayland_wrapper/qwldatadevice.cpp b/src/compositor/wayland_wrapper/qwldatadevice.cpp index a3a795f9..f301678e 100644 --- a/src/compositor/wayland_wrapper/qwldatadevice.cpp +++ b/src/compositor/wayland_wrapper/qwldatadevice.cpp @@ -76,6 +76,9 @@ void DataDevice::sourceDestroyed(DataSource *source) { if (m_selectionSource == source) m_selectionSource = nullptr; + + if (m_dragDataSource == source) + m_dragDataSource = nullptr; } #if QT_CONFIG(draganddrop) @@ -105,9 +108,11 @@ void DataDevice::setDragFocus(QWaylandSurface *focus, const QPointF &localPositi if (m_dragDataSource && !offer) return; - send_enter(resource->handle, serial, focus->resource(), - wl_fixed_from_double(localPosition.x()), wl_fixed_from_double(localPosition.y()), - offer->resource()->handle); + if (offer) { + send_enter(resource->handle, serial, focus->resource(), + wl_fixed_from_double(localPosition.x()), wl_fixed_from_double(localPosition.y()), + offer->resource()->handle); + } m_dragFocus = focus; m_dragFocusResource = resource; @@ -139,7 +144,7 @@ void DataDevice::drop() if (m_dragFocusResource) { send_drop(m_dragFocusResource->handle); setDragFocus(nullptr, QPoint()); - } else { + } else if (m_dragDataSource) { m_dragDataSource->cancel(); } m_dragOrigin = nullptr; @@ -155,6 +160,8 @@ void DataDevice::data_device_start_drag(Resource *resource, struct ::wl_resource { m_dragClient = resource->client(); m_dragDataSource = source ? DataSource::fromResource(source) : nullptr; + if (m_dragDataSource) + m_dragDataSource->setDevice(this); m_dragOrigin = QWaylandSurface::fromResource(origin); QWaylandDrag *drag = m_seat->drag(); setDragIcon(icon ? QWaylandSurface::fromResource(icon) : nullptr); -- 2.49.0 From ec4fa1a0bf2981b7ef6d7caec84f3b1ac706d14b Mon Sep 17 00:00:00 2001 From: Liu Zheng Date: Wed, 24 Jul 2024 09:22:42 +0800 Subject: [PATCH 56/57] fix: variable not initialized If the disableResizeCheck logic is not checked, initialization will fail. Problems will arise later. Pick-to: 6.8 6.7 6.5 6.2 5.15 Change-Id: Ifffd4f7407b3ef616d436b81f3b2148c1139c3f7 Reviewed-by: Liang Qi (cherry picked from commit dc49720c135e0d59dff42c9fd28f57a6199c33b9) --- .../client/wayland-egl/qwaylandeglwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp index dbe2845a..95e8c666 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp +++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp @@ -118,7 +118,8 @@ void QWaylandEglWindow::updateSurface(bool create) } else { QReadLocker locker(&mSurfaceLock); if (m_waylandEglWindow) { - int current_width, current_height; + int current_width = 0; + int current_height = 0; static bool disableResizeCheck = qgetenv("QT_WAYLAND_DISABLE_RESIZECHECK").toInt(); if (!disableResizeCheck) { -- 2.49.0 From c1ac089f3f37ab172d8010b0fabf4007d62d1812 Mon Sep 17 00:00:00 2001 From: Kai Uwe Broulik Date: Thu, 4 Jan 2024 20:30:33 +0100 Subject: [PATCH 57/57] bradient: Use QWaylandWindow actual window title It may include a suffix containing the application name and is also what's set on the XDG Toplevel, i.e. what the rest of the environment (task switcher, etc) sees. This stores the title in QWaylandWindow and adds a getter to retrieve it. Pick-to: 6.7 Change-Id: I84f41c68b16b680cdbb5cf656c7078d1e41767d4 Reviewed-by: David Edmundson (cherry-picked from commit 34c1dca) --- src/client/qwaylandwindow.cpp | 35 ++++++++++++++--------- src/client/qwaylandwindow_p.h | 2 ++ src/plugins/decorations/bradient/main.cpp | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 7a9bccc1..7c338d27 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -303,26 +303,33 @@ void QWaylandWindow::setParent(const QPlatformWindow *parent) } } +QString QWaylandWindow::windowTitle() const +{ + return mWindowTitle; +} + void QWaylandWindow::setWindowTitle(const QString &title) { - if (mShellSurface) { - const QString separator = QString::fromUtf8(" \xe2\x80\x94 "); // unicode character U+2014, EM DASH - const QString formatted = formatWindowTitle(title, separator); + const QString separator = QString::fromUtf8(" \xe2\x80\x94 "); // unicode character U+2014, EM DASH + const QString formatted = formatWindowTitle(title, separator); - const int libwaylandMaxBufferSize = 4096; - // Some parts of the buffer is used for metadata, so subtract 100 to be on the safe side. - // Also, QString is in utf-16, which means that in the worst case each character will be - // three bytes when converted to utf-8 (which is what libwayland uses), so divide by three. - const int maxLength = libwaylandMaxBufferSize / 3 - 100; + const int libwaylandMaxBufferSize = 4096; + // Some parts of the buffer is used for metadata, so subtract 100 to be on the safe side. + // Also, QString is in utf-16, which means that in the worst case each character will be + // three bytes when converted to utf-8 (which is what libwayland uses), so divide by three. + const int maxLength = libwaylandMaxBufferSize / 3 - 100; - auto truncated = QStringRef(&formatted).left(maxLength); - if (truncated.length() < formatted.length()) { - qCWarning(lcQpaWayland) << "Window titles longer than" << maxLength << "characters are not supported." - << "Truncating window title (from" << formatted.length() << "chars)"; - } - mShellSurface->setTitle(truncated.toString()); + auto truncated = QStringRef(&formatted).left(maxLength); + if (truncated.length() < formatted.length()) { + qCWarning(lcQpaWayland) << "Window titles longer than" << maxLength << "characters are not supported." + << "Truncating window title (from" << formatted.length() << "chars)"; } + mWindowTitle = truncated.toString(); + + if (mShellSurface) + mShellSurface->setTitle(mWindowTitle); + if (mWindowDecoration && window()->isVisible()) mWindowDecoration->update(); } diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 741f9e5c..5e9f4ebf 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -107,6 +107,7 @@ public: void setVisible(bool visible) override; void setParent(const QPlatformWindow *parent) override; + QString windowTitle() const; void setWindowTitle(const QString &title) override; inline QIcon windowIcon() const; @@ -263,6 +264,7 @@ protected: int mScale = 1; QPlatformScreen *mLastReportedScreen = nullptr; + QString mWindowTitle; QIcon mWindowIcon; Qt::WindowFlags mFlags; diff --git a/src/plugins/decorations/bradient/main.cpp b/src/plugins/decorations/bradient/main.cpp index fa885143..fbdbe284 100644 --- a/src/plugins/decorations/bradient/main.cpp +++ b/src/plugins/decorations/bradient/main.cpp @@ -171,7 +171,7 @@ void QWaylandBradientDecoration::paint(QPaintDevice *device) } // Window title - QString windowTitleText = window()->title(); + QString windowTitleText = waylandWindow()->windowTitle(); if (!windowTitleText.isEmpty()) { if (m_windowTitle.text() != windowTitleText) { m_windowTitle.setText(windowTitleText); -- 2.49.0