From eca0516905c3f88aba876d18742c9102da2319db Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 5 May 2021 20:49:26 +0300 Subject: [PATCH 01/38] 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.36.0 From 95a139413337bb92a9dbbbd95f61e52cc1f43649 Mon Sep 17 00:00:00 2001 From: Jaeyoon Jung Date: Mon, 15 Feb 2021 08:31:06 +0900 Subject: [PATCH 02/38] 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 7889f575..201b583b 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp +++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp @@ -131,14 +131,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 5b1f4d56..0079dfef 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h +++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h @@ -88,6 +88,7 @@ private: mutable QOpenGLFramebufferObject *m_contentFBO = nullptr; QSurfaceFormat m_format; + QSize m_requestedSize; }; } -- 2.36.0 From 4f5bef9e6e0d16bb859e7a99ef5ebfca2ac93f45 Mon Sep 17 00:00:00 2001 From: Albert Astals Cid Date: Mon, 10 May 2021 14:38:49 +0200 Subject: [PATCH 03/38] Include locale.h for setlocale/LC_CTYPE Pick-to: 5.15 Change-Id: Iced32a31a63cec71008549c1e0961d59ffc45a37 Reviewed-by: Aleix Pol Gonzalez (cherry picked from commit e9522eda46028f351d87311d898ab985856970b0) --- src/client/qwaylandinputcontext.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp index ef5aa375..503fd735 100644 --- a/src/client/qwaylandinputcontext.cpp +++ b/src/client/qwaylandinputcontext.cpp @@ -51,6 +51,10 @@ #include "qwaylandinputmethodeventbuilder_p.h" #include "qwaylandwindow_p.h" +#if QT_CONFIG(xkbcommon) +#include +#endif + QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcQpaInputMethods, "qt.qpa.input.methods") -- 2.36.0 From 58f7e6cfb61e4f170ab7ced2e01b6222036f934d Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Tue, 9 Feb 2021 16:09:21 +0000 Subject: [PATCH 04/38] 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 19944a34..54a69c3c 100644 --- a/src/client/qwaylanddatadevice.cpp +++ b/src/client/qwaylanddatadevice.cpp @@ -124,6 +124,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.36.0 From 0b8b965626c7d02ce885187fa46fe6c69af3ede3 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Fri, 14 May 2021 13:23:24 +0100 Subject: [PATCH 05/38] 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 125b1e19..797b06fe 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.36.0 From 71de867aab030a9f48bd73e4090d301da75e4102 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Mon, 3 May 2021 23:01:53 +0100 Subject: [PATCH 06/38] 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 3a1569f7..7d33dabd 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -105,8 +105,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; } @@ -257,6 +255,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 2277bbb8..2fdd0a7c 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.36.0 From 342e5b7d0b45db4ed6679af4b40b05b4bd96de09 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Mon, 14 Jun 2021 12:45:37 +0100 Subject: [PATCH 07/38] 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 797b06fe..edccfe63 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.36.0 From 7d6a8aa51603e39a5da5b87af2acd0433a265987 Mon Sep 17 00:00:00 2001 From: Zhang Liang Date: Mon, 1 Feb 2021 19:29:43 +0800 Subject: [PATCH 08/38] 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 f10c1f79..e0dfe8b2 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -452,9 +452,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.36.0 From bea49655add94c2ab77ec39dfe33bf6c7f5ce927 Mon Sep 17 00:00:00 2001 From: David Redondo Date: Wed, 26 May 2021 14:49:40 +0200 Subject: [PATCH 09/38] 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 c53ccb78..e5e7dd42 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.36.0 From c7022c1c0107781993b1d13e7aa11dd3f7486249 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Tue, 13 Jul 2021 13:32:15 +0200 Subject: [PATCH 10/38] Do not update the mask if we do not have a surface mMask serves as a cache to remember what we've sent, the source of truth for the value is window()->mask(). No need to store values that we are going to discard, because it will confuse the state of newly created windows. Change-Id: I6aa3da82c7f09c7ef90d0f7060f292fb042730f0 Pick-to: 5.15 6.2 Reviewed-by: David Edmundson (cherry picked from commit 962f87190c682562b369c5ebd93dc9ce0915ed7a) --- 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 e96d8fe9..bd70f4af 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -464,14 +464,15 @@ void QWaylandWindow::lower() void QWaylandWindow::setMask(const QRegion &mask) { + QReadLocker locker(&mSurfaceLock); + if (!mSurface) + return; + if (mMask == mask) return; mMask = mask; - if (!mSurface) - return; - if (mMask.isEmpty()) { mSurface->set_input_region(nullptr); -- 2.36.0 From 83a5e079e4bdf567010abc0b7d67eff052b76249 Mon Sep 17 00:00:00 2001 From: Jan Blackquill Date: Tue, 24 Aug 2021 14:36:34 -0400 Subject: [PATCH 11/38] 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 a5fdd34d..051a91dc 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.36.0 From 19d0878e56094b5cced1154ce07f566f2b147e2a Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Tue, 14 Sep 2021 11:56:23 +0200 Subject: [PATCH 12/38] Wayland client: Fix crash when windows are shown/hidden during drag Fixes: QTBUG-87624 Pick-to: 6.2 5.15 Change-Id: I1b9443df091878abcd4fbe9c55927cb819aebd59 Reviewed-by: David Edmundson (cherry picked from commit c64c5d3849b40617e1de0295f8690f354cab2b3a) --- src/client/qwaylanddatadevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/qwaylanddatadevice.cpp b/src/client/qwaylanddatadevice.cpp index 54a69c3c..bbd2d568 100644 --- a/src/client/qwaylanddatadevice.cpp +++ b/src/client/qwaylanddatadevice.cpp @@ -169,7 +169,7 @@ void QWaylandDataDevice::data_device_drop() void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id) { - auto *dragWaylandWindow = QWaylandWindow::fromWlSurface(surface); + auto *dragWaylandWindow = surface ? QWaylandWindow::fromWlSurface(surface) : nullptr; if (!dragWaylandWindow) return; // Ignore foreign surfaces -- 2.36.0 From abaa0b1765551533112944e624ac5989df7d7b6c Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 27 May 2021 19:55:04 -0300 Subject: [PATCH 13/38] 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 bd70f4af..85307875 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -1170,6 +1170,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 2fdd0a7c..e2593314 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.36.0 From 1428e39b6e686faf4d25ab4f8506662bcc23e6f9 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 27 May 2021 20:02:53 -0300 Subject: [PATCH 14/38] 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 85307875..c020a58f 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -622,9 +622,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(); } }; @@ -1179,11 +1183,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.36.0 From 132be08e4d81c1e05b93f31a6e2b6a6bd65d5726 Mon Sep 17 00:00:00 2001 From: Rodney Dawes Date: Fri, 15 Oct 2021 12:55:33 -0400 Subject: [PATCH 15/38] Fix the logic for decoding modifiers map in Wayland text input protocol Correctly check for the flags in the modifiers map when we get it from the compositor, instead of modifying the map in the for loop conditional. [ChangeLog][QWaylandInputContext] Fix modifiers map decoding logic when receiving the map from the compositor. Fixes: QTBUG-97094 Pick-to: 6.2 5.15 5.12 Change-Id: Idad19f7b1f4560d40abbb5b31032360cfe915261 Reviewed-by: Paul Olav Tvete --- src/client/qwaylandinputcontext.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp index 503fd735..e290baa2 100644 --- a/src/client/qwaylandinputcontext.cpp +++ b/src/client/qwaylandinputcontext.cpp @@ -387,8 +387,10 @@ void QWaylandTextInput::zwp_text_input_v2_input_method_changed(uint32_t serial, Qt::KeyboardModifiers QWaylandTextInput::modifiersToQtModifiers(uint32_t modifiers) { Qt::KeyboardModifiers ret = Qt::NoModifier; - for (int i = 0; modifiers >>= 1; ++i) { - ret |= m_modifiersMap[i]; + for (int i = 0; i < m_modifiersMap.size(); ++i) { + if (modifiers & (1 << i)) { + ret |= m_modifiersMap[i]; + } } return ret; } -- 2.36.0 From f73a3ec466eb30e554f918d6d2da2c5d1b0e23bd 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 16/38] 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 e0dfe8b2..27303110 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -575,14 +575,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; } @@ -627,6 +623,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 3b092bc8..09a1736a 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -215,6 +215,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 c020a58f..ba881cb3 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) @@ -1083,10 +1084,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 6cc1664b..e0687962 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 7d33dabd..d7d0ddf7 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -67,11 +67,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; @@ -85,16 +80,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 e2593314..73d1eb9c 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.36.0 From 83440ae9e002f0c7bdec6b54db6b382d2e28bf7d Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Fri, 16 Jul 2021 13:00:03 +0200 Subject: [PATCH 17/38] 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 27303110..9f595af3 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -597,6 +597,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 613fe862..aaec7eb8 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -1300,14 +1300,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.36.0 From 1a476429c2d9034322d5b3366ce53375e484353a Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Sat, 18 Dec 2021 23:42:49 -0800 Subject: [PATCH 18/38] Set preedit cursor when cursor equals to 0 Pick-to: 6.3 6.2 5.15 Change-Id: I832fbb22d973b36ac4ab51570fc53bc2e4c3ed58 Reviewed-by: Liang Qi (cherry picked from commit 719a55be13bdadfa659a732755f280e276a894bd) --- src/shared/qwaylandinputmethodeventbuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/qwaylandinputmethodeventbuilder.cpp b/src/shared/qwaylandinputmethodeventbuilder.cpp index 526d0ef4..25be2509 100644 --- a/src/shared/qwaylandinputmethodeventbuilder.cpp +++ b/src/shared/qwaylandinputmethodeventbuilder.cpp @@ -151,7 +151,7 @@ QInputMethodEvent QWaylandInputMethodEventBuilder::buildPreedit(const QString &t { QList attributes; - if (m_preeditCursor < 0) { + if (m_preeditCursor <= 0) { attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant())); } else if (m_preeditCursor > 0) { attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, indexFromWayland(text, m_preeditCursor), 1, QVariant())); -- 2.36.0 From 8afae71a44d0d5a0be477863da791dd2dfe2027d Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Tue, 16 Feb 2021 09:51:47 +0000 Subject: [PATCH 19/38] 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 bbd2d568..fbb5aa91 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(); @@ -123,8 +125,28 @@ bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) } 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; @@ -153,7 +175,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; } @@ -163,7 +185,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(); } } @@ -187,7 +213,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, @@ -198,11 +224,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() @@ -236,10 +258,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()); @@ -247,11 +269,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) @@ -281,11 +299,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)); @@ -298,6 +311,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 2297e8a1..c9e158cc 100644 --- a/src/client/qwaylanddataoffer.cpp +++ b/src/client/qwaylanddataoffer.cpp @@ -82,6 +82,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); @@ -93,6 +102,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 f45122fb..5599cbd4 100644 --- a/src/client/qwaylanddatasource.cpp +++ b/src/client/qwaylanddatasource.cpp @@ -101,7 +101,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 25afff79..96f07bc3 100644 --- a/src/client/qwaylanddatasource_p.h +++ b/src/client/qwaylanddatasource_p.h @@ -77,17 +77,25 @@ public: QMimeData *mimeData() const; 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: QWaylandDisplay *m_display = nullptr; 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 9f595af3..ea344c61 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -354,7 +354,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.36.0 From e92aff243eca4c1e30c093692dce6f7c91d7a19c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 18 Nov 2021 13:05:30 +0100 Subject: [PATCH 20/38] 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.36.0 From 340e89575c93435abab78ac73603b405f1f05ceb Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Sun, 14 Nov 2021 13:54:19 +0000 Subject: [PATCH 21/38] 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 ba881cb3..1597f67e 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -436,7 +436,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.36.0 From de7afd339100cac1470f875eafc22d3ee87870bd Mon Sep 17 00:00:00 2001 From: Elvis Lee Date: Thu, 18 Feb 2021 15:45:49 +0900 Subject: [PATCH 22/38] 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 ea344c61..0f75cb7e 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -158,13 +158,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) @@ -189,6 +182,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 09a1736a..d9c8849f 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -129,6 +129,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 e5e7dd42..f5632982 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.36.0 From a5df6f67f446ed091c688336510b5da2970a0d84 Mon Sep 17 00:00:00 2001 From: Elvis Lee Date: Wed, 17 Mar 2021 16:31:10 +0900 Subject: [PATCH 23/38] 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 f5632982..3a6fa651 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.36.0 From 967883d20e94183bd9cf6648297b9d76ba0e167e Mon Sep 17 00:00:00 2001 From: Adrien Faveraux Date: Fri, 26 Nov 2021 09:18:58 +0100 Subject: [PATCH 24/38] 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 0f75cb7e..a7ce280a 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -85,10 +85,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) @@ -162,6 +355,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); @@ -208,98 +407,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 @@ -674,4 +812,6 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int p } // namespace QtWaylandClient +#include "qwaylanddisplay.moc" + QT_END_NAMESPACE diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index d9c8849f..42bc661d 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -109,6 +109,7 @@ class QWaylandSurface; class QWaylandShellIntegration; class QWaylandCursor; class QWaylandCursorTheme; +class EventThread; typedef void (*RegistryListener)(void *data, struct wl_registry *registry, @@ -120,12 +121,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; @@ -212,12 +207,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(); @@ -240,6 +234,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; @@ -276,11 +273,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 3a6fa651..3b876047 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 1597f67e..7de19a74 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) @@ -635,6 +632,8 @@ const wl_callback_listener QWaylandWindow::callbackListener = { void QWaylandWindow::handleFrameCallback() { + QMutexLocker locker(&mFrameSyncMutex); + mWaitingForFrameCallback = false; mFrameCallbackElapsedTimer.invalidate(); @@ -656,12 +655,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"; @@ -1157,8 +1160,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 @@ -1171,7 +1177,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); } @@ -1191,9 +1202,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); @@ -1203,6 +1215,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 e0687962..d45980a8 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.36.0 From 520f58c24e0fbb33f84f329fc9879b72710c77ae Mon Sep 17 00:00:00 2001 From: Roman Genkhel Date: Thu, 12 Nov 2020 12:21:51 +0300 Subject: [PATCH 25/38] 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 7de19a74..ac01dc05 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -552,8 +552,8 @@ void QWaylandWindow::sendRecursiveExposeEvent() void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { - Q_ASSERT(!buffer->committed()); if (buffer) { + Q_ASSERT(!buffer->committed()); handleUpdate(); buffer->setBusy(); -- 2.36.0 From 72f64f397c72afb22df1825382e17a310517add1 Mon Sep 17 00:00:00 2001 From: Inho Lee Date: Mon, 1 Nov 2021 14:23:58 +0100 Subject: [PATCH 26/38] Do not create decorations when the shellSurface is not ready A cases reported that client windows try to make decorations when their shell surfaces are null. Since the surfaces' requests for decorations should be applied, those case will be failed to create decorations. This patch was modified by Paul Tvete's advice. (paul.tvete@qt.io) Pick-to: 6.2 5.15 Task-number: QTBUG-97608 Change-Id: I2563dbd73b730f81cc411857af07da99ceb2d063 Reviewed-by: Paul Olav Tvete (cherry picked from commit 246f0c0bc01dd059bf8165e81f7b49efa36e4d95) --- 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 ac01dc05..acfe390e 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -813,7 +813,7 @@ bool QWaylandWindow::createDecoration() decoration = false; if (mSubSurfaceWindow) decoration = false; - if (mShellSurface && !mShellSurface->wantsDecorations()) + if (!mShellSurface || !mShellSurface->wantsDecorations()) decoration = false; bool hadDecoration = mWindowDecoration; -- 2.36.0 From 6935647966b456e760745a6b2a13a04ba6543803 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Mon, 6 Jul 2020 14:37:35 +0200 Subject: [PATCH 27/38] 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 a7ce280a..6f1bada5 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -488,7 +488,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 acfe390e..4c5711a0 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -571,7 +571,11 @@ void QWaylandWindow::attachOffset(QWaylandBuffer *buffer) void QWaylandWindow::damage(const QRect &rect) { - 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) @@ -605,8 +609,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.36.0 From 506d0372178134f208fd08b3f6b9499fc0e14a5e Mon Sep 17 00:00:00 2001 From: Joni Poikelin Date: Thu, 3 Feb 2022 14:01:50 +0200 Subject: [PATCH 28/38] Fix crash if no input method module could be loaded Pick-to: 6.2 6.3 5.15 Change-Id: I8f346def616606a6c5540856bd08a84ee7ed5ca2 Reviewed-by: David Edmundson (cherry picked from commit 49fb7248f6ab7de046e2179c7861951ea1169e9b) --- src/client/qwaylandintegration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 3b876047..fbf00c6b 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -491,7 +491,7 @@ void QWaylandIntegration::reconfigureInputContext() } #endif - qCDebug(lcQpaWayland) << "using input method:" << inputContext()->metaObject()->className(); + qCDebug(lcQpaWayland) << "using input method:" << (inputContext() ? inputContext()->metaObject()->className() : ""); } QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QString &integrationName) -- 2.36.0 From c2e56e076f0ded39b1ab34ebf07afad2f344f53f Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 1 Feb 2022 13:05:36 +0200 Subject: [PATCH 29/38] 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 4c5711a0..949374b1 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -648,23 +648,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 d45980a8..3ff68ccb 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.36.0 From 2d0bd70b55ebde2e22d0b95e8122235f90c8f9f1 Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Tue, 8 Feb 2022 07:11:25 -0800 Subject: [PATCH 30/38] Cursor position == 0 should still show the cursor Otherwise the cursor would be hidden even if preedit is empty. Amends 719a55be13bdadfa659a732755f280e276a894bd Pick-to: 5.15 6.2 6.3 Change-Id: I320733b917779b7b51aa4a28eaea411fdb10a318 Reviewed-by: Liang Qi (cherry picked from commit 31ae194e295651d9ece03408630d2358acd4f7b4) --- src/shared/qwaylandinputmethodeventbuilder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/qwaylandinputmethodeventbuilder.cpp b/src/shared/qwaylandinputmethodeventbuilder.cpp index 25be2509..458d818e 100644 --- a/src/shared/qwaylandinputmethodeventbuilder.cpp +++ b/src/shared/qwaylandinputmethodeventbuilder.cpp @@ -151,9 +151,9 @@ QInputMethodEvent QWaylandInputMethodEventBuilder::buildPreedit(const QString &t { QList attributes; - if (m_preeditCursor <= 0) { + if (m_preeditCursor < 0) { attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant())); - } else if (m_preeditCursor > 0) { + } else { attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, indexFromWayland(text, m_preeditCursor), 1, QVariant())); } -- 2.36.0 From f0f7b2bea822f73ae00fcfd4ee1679596d550a20 Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Wed, 22 Dec 2021 10:42:38 -0800 Subject: [PATCH 31/38] Update the preedit styling mapping - None mapping to no style. - Default/Underline mapping to underline. - Highlight/Selection mapping to background color/text color with highlight/highlight text with underline. - Active/Inactive mapping to bold text with underline. - Incorrect mapping to red wave underline. Pick-to: 5.15 6.2 6.3 Change-Id: Iab51d671b8f83aece8596f7f7610de19343fcceb Reviewed-by: Aleix Pol Gonzalez (cherry picked from commit f1fb5d9e568a24e213ee41e82a1142cef56f1098) --- .../qwaylandinputmethodeventbuilder.cpp | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/shared/qwaylandinputmethodeventbuilder.cpp b/src/shared/qwaylandinputmethodeventbuilder.cpp index 458d818e..f50ccf30 100644 --- a/src/shared/qwaylandinputmethodeventbuilder.cpp +++ b/src/shared/qwaylandinputmethodeventbuilder.cpp @@ -39,7 +39,10 @@ #include "qwaylandinputmethodeventbuilder_p.h" +#include +#include #include +#include #include #ifdef QT_BUILD_WAYLANDCOMPOSITOR_LIB @@ -81,32 +84,38 @@ void QWaylandInputMethodEventBuilder::addPreeditStyling(uint32_t index, uint32_t QTextCharFormat format; switch (style) { - case 0: - case 1: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_NONE: + break; + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_DEFAULT: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_UNDERLINE: format.setFontUnderline(true); format.setUnderlineStyle(QTextCharFormat::SingleUnderline); m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); break; - case 2: - case 3: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_ACTIVE: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_INACTIVE: format.setFontWeight(QFont::Bold); format.setFontUnderline(true); format.setUnderlineStyle(QTextCharFormat::SingleUnderline); m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); break; - case 4: - format.setFontUnderline(true); - format.setUnderlineStyle(QTextCharFormat::SingleUnderline); - m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_HIGHLIGHT: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_SELECTION: + { + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + QPalette palette = qApp->palette(); + format.setBackground(QBrush(palette.color(QPalette::Active, QPalette::Highlight))); + format.setForeground(QBrush(palette.color(QPalette::Active, QPalette::HighlightedText))); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + } break; - case 5: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_INCORRECT: format.setFontUnderline(true); format.setUnderlineStyle(QTextCharFormat::WaveUnderline); format.setUnderlineColor(QColor(Qt::red)); m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); break; -// case QtWayland::wl_text_input::preedit_style_selection: -// case QtWayland::wl_text_input::preedit_style_none: default: break; } -- 2.36.0 From 596ecf46bb0c2427cda2894dd2157b3f5a2cd34f Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Wed, 9 Feb 2022 17:20:48 +0000 Subject: [PATCH 32/38] 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 6f1bada5..86045a35 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -611,50 +611,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.36.0 From f7b7b39d00ae31676fc678446d7090e7a9dd95b4 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 19 Feb 2022 17:01:04 +0200 Subject: [PATCH 33/38] 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 949374b1..fee2ecdd 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.36.0 From 6a95428357872353a20ab6dcc5f8facdb520e8dc Mon Sep 17 00:00:00 2001 From: Fabian Vogt Date: Fri, 4 Feb 2022 11:07:36 +0100 Subject: [PATCH 34/38] 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 | 8 ++++---- src/compositor/configure.json | 34 +++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/client/configure.json b/src/client/configure.json index 2f424580..29222357 100644 --- a/src/client/configure.json +++ b/src/client/configure.json @@ -149,8 +149,7 @@ "#endif" ] }, - "libs": "-ldrm", - "use": "egl" + "use": "drm egl" }, "vulkan-server-buffer": { "label": "Vulkan Buffer Sharing", @@ -168,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", @@ -183,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 bcfd5215..da95d07b 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", @@ -151,8 +176,7 @@ "#endif" ] }, - "libs": "-ldrm", - "use": "egl" + "use": "drm egl" }, "dmabuf-client-buffer": { "label": "Linux Client dma-buf Buffer Sharing", @@ -176,8 +200,7 @@ "return 0;" ] }, - "libs": "-ldrm", - "use": "egl" + "use": "drm egl" }, "vulkan-server-buffer": { "label": "Vulkan Buffer Sharing", @@ -195,7 +218,8 @@ "exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;", "return 0;" ] - } + }, + "use": "wayland-client" } }, -- 2.36.0 From 169052d99391eef62c181c8b5b107280688045d1 Mon Sep 17 00:00:00 2001 From: Liang Qi Date: Wed, 9 Mar 2022 10:47:42 +0100 Subject: [PATCH 35/38] client: update button state and etc in pointer_leave() The cleanup work needs to be done even the surface is null, for example, a window was closed in mouse press handler, then will not get a mouse release. Fixes: QTBUG-100942 Pick-to: 5.15 6.2 6.3 Change-Id: I637a6744909ddbe62bdeba6b21494e5a6ae7fa9f Reviewed-by: Tang Haixiang Reviewed-by: David Edmundson (cherry picked from commit 409d1080f25b653b3ff3f57c9776c5c390912206) --- src/client/qwaylandinputdevice.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index aaec7eb8..c3cc4eca 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -685,6 +685,11 @@ public: void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surface *surface) { + invalidateFocus(); + mButtons = Qt::NoButton; + + mParent->mTime = time; + // The event may arrive after destroying the window, indicated by // a null surface. if (!surface) @@ -696,11 +701,6 @@ void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surfac if (!QWaylandWindow::mouseGrab()) setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos)); - - invalidateFocus(); - mButtons = Qt::NoButton; - - mParent->mTime = time; } class MotionEvent : public QWaylandPointerEvent -- 2.36.0 From 36756f5d1b8891465bddd31e990c81e149dce0f1 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Tue, 15 Mar 2022 15:59:15 +0100 Subject: [PATCH 36/38] 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 fee2ecdd..bf41cc5b 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -655,11 +655,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 3ff68ccb..d45980a8 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.36.0 From a83e65ddc9a965b25e435d136849a50f0b99c4ae Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Tue, 15 Mar 2022 16:53:04 +0100 Subject: [PATCH 37/38] 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 bf41cc5b..ceaa4c73 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -649,24 +649,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 d45980a8..cb9135f6 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.36.0 From 36659e6130ed3fc2b3f0c91423408ef5ecb7b991 Mon Sep 17 00:00:00 2001 From: Kenneth Topp Date: Mon, 4 Apr 2022 09:36:21 -0400 Subject: [PATCH 38/38] 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 c9e158cc..fe0ea8c9 100644 --- a/src/client/qwaylanddataoffer.cpp +++ b/src/client/qwaylanddataoffer.cpp @@ -188,17 +188,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.36.0