From 33335644e8abc14c60ad3d94f0a8eff21b3c4ea4 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Mon, 27 Jul 2020 10:55:57 +0200 Subject: [PATCH] Backport upstream patches --- qt5-qtwayland.spec | 16 ++- ...akt-surface-created-destroyed-events.patch | 92 +++++++++++++ ...-fix-scroll-direction-with-touchpads.patch | 113 +++++++++++++++ ...scale-on-creation-instead-of-on-show.patch | 46 +++++++ ...meback-callback-timeout-configurable.patch | 103 ++++++++++++++ ...se-event-when-toplevel-is-configured.patch | 59 ++++++++ ...yland-fix-leaking-of-callback-timers.patch | 129 ++++++++++++++++++ 7 files changed, 555 insertions(+), 3 deletions(-) create mode 100644 qtwayland-client-dont-send-fakt-surface-created-destroyed-events.patch create mode 100644 qtwayland-client-fix-scroll-direction-with-touchpads.patch create mode 100644 qtwayland-client-initialize-mscale-on-creation-instead-of-on-show.patch create mode 100644 qtwayland-client-make-frameback-callback-timeout-configurable.patch create mode 100644 qtwayland-client-send-subsurface-expose-event-when-toplevel-is-configured.patch create mode 100644 qtwayland-fix-leaking-of-callback-timers.patch diff --git a/qt5-qtwayland.spec b/qt5-qtwayland.spec index d245e8b..3a5a108 100644 --- a/qt5-qtwayland.spec +++ b/qt5-qtwayland.spec @@ -3,15 +3,13 @@ Summary: Qt5 - Wayland platform support and QtCompositor module Name: qt5-%{qt_module} Version: 5.14.2 -Release: 3%{?dist} +Release: 4%{?dist} License: LGPLv3 Url: http://www.qt.io %global majmin %(echo %{version} | cut -d. -f1-2) Source0: https://download.qt.io/official_releases/qt/%{majmin}/%{version}/submodules/%{qt_module}-everywhere-src-%{version}.tar.xz -# Upstream patches - # Bold fonts in qt applications appear much too heavy # https://bugzilla.redhat.com/show_bug.cgi?id=1823984 Patch0: qtwayland-dont-force-gamma-correction-off.patch @@ -26,6 +24,14 @@ Patch1: qtwayland-remove-recursion-in-data-offer-retrieval.patch # https://bugzilla.redhat.com/show_bug.cgi?id=1732129 Patch50: qtwayland-use-gnome-platform-theme-on-gnome-based-desktops.patch +# Upstream patches +Patch100: qtwayland-fix-leaking-of-callback-timers.patch +Patch101: qtwayland-client-make-frameback-callback-timeout-configurable.patch +Patch102: qtwayland-client-fix-scroll-direction-with-touchpads.patch +Patch103: qtwayland-client-initialize-mscale-on-creation-instead-of-on-show.patch +Patch104: qtwayland-client-send-subsurface-expose-event-when-toplevel-is-configured.patch +Patch105: qtwayland-client-dont-send-fakt-surface-created-destroyed-events.patch + # filter qml provides %global __provides_exclude_from ^%{_qt5_archdatadir}/qml/.*\\.so$ @@ -131,6 +137,10 @@ popd %changelog +* Mon Jul 27 2020 Jan Grulich - 5.14.2-4 +- Backport upstream patches + Resolves: bz#1860455 + * Thu Apr 30 2020 Ivan Mironov - 5.14.2-3 - Cherry-pick fix for clipboard related crash from v5.15.0 diff --git a/qtwayland-client-dont-send-fakt-surface-created-destroyed-events.patch b/qtwayland-client-dont-send-fakt-surface-created-destroyed-events.patch new file mode 100644 index 0000000..ab81a05 --- /dev/null +++ b/qtwayland-client-dont-send-fakt-surface-created-destroyed-events.patch @@ -0,0 +1,92 @@ +From cd21404f99b486ff62225699e1a4bdc0d5b3d5c1 Mon Sep 17 00:00:00 2001 +From: David Edmundson +Date: Sun, 23 Jun 2019 15:09:51 +0200 +Subject: [PATCH] Client: Don't send fake SurfaceCreated/Destroyed events + +QPlatformSurface relates to the platform window, not the wl_surface. +The events are already emitted by QPlatformWindow on create/destroy. + +To preserve compatibility for a previous KDE version it was faked to +emit the events when the wl_surface is created/hidden to keep behavior. +This is no longer necessary, and it has caused multiple errors, the latest +being a crash when switching between sub-menus with the Sway compositor. + +[ChangeLog][QPA plugin] QWaylandWindow no longer sends fake SurfaceCreated/Destroyed events. +Use expose events to be notified when a Wayland surface appears. + +Task-number: QTBUG-76324 +Fixes: QTBUG-81952 +Pick-to: 5.15 +Change-Id: I2f003bc9da85f032a0053677fd281152099fc9eb +Reviewed-by: Paul Olav Tvete +Reviewed-by: Tor Arne Vestbø +Reviewed-by: David Edmundson +--- + +diff --git a/examples/wayland/custom-extension/client-common/customextension.cpp b/examples/wayland/custom-extension/client-common/customextension.cpp +index aa0cb58..8b77c06 100644 +--- a/examples/wayland/custom-extension/client-common/customextension.cpp ++++ b/examples/wayland/custom-extension/client-common/customextension.cpp +@@ -81,8 +81,11 @@ QWindow *CustomExtension::windowForSurface(struct ::wl_surface *surface) + + bool CustomExtension::eventFilter(QObject *object, QEvent *event) + { +- if (event->type() == QEvent::PlatformSurface +- && static_cast(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { ++ if (event->type() == QEvent::Expose) { ++ auto *exposeEvent = static_cast(event); ++ if (exposeEvent->region().isNull()) ++ return false; ++ + QWindow *window = qobject_cast(object); + Q_ASSERT(window); + window->removeEventFilter(this); +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index 9669cbb..d0d834b 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -100,7 +100,7 @@ QWaylandWindow::~QWaylandWindow() + delete mWindowDecoration; + + if (mSurface) +- reset(false); ++ reset(); + + const QWindow *parent = window(); + const auto tlw = QGuiApplication::topLevelWindows(); +@@ -127,8 +127,6 @@ void QWaylandWindow::initWindow() + + if (!mSurface) { + initializeWlSurface(); +- QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceCreated); +- QGuiApplication::sendEvent(window(), &e); + } + + if (shouldCreateSubSurface()) { +@@ -241,12 +239,8 @@ bool QWaylandWindow::shouldCreateSubSurface() const + return QPlatformWindow::parent() != nullptr; + } + +-void QWaylandWindow::reset(bool sendDestroyEvent) ++void QWaylandWindow::reset() + { +- if (mSurface && sendDestroyEvent) { +- QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed); +- QGuiApplication::sendEvent(window(), &e); +- } + delete mShellSurface; + mShellSurface = nullptr; + delete mSubSurfaceWindow; +diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h +index b57f5dd..a65301c 100644 +--- a/src/client/qwaylandwindow_p.h ++++ b/src/client/qwaylandwindow_p.h +@@ -261,7 +261,7 @@ private: + void initializeWlSurface(); + bool shouldCreateShellSurface() const; + bool shouldCreateSubSurface() const; +- void reset(bool sendDestroyEvent = true); ++ void reset(); + void sendExposeEvent(const QRect &rect); + static void closePopups(QWaylandWindow *parent); + QWaylandScreen *calculateScreenFromSurfaceEvents() const; diff --git a/qtwayland-client-fix-scroll-direction-with-touchpads.patch b/qtwayland-client-fix-scroll-direction-with-touchpads.patch new file mode 100644 index 0000000..ff0d3e3 --- /dev/null +++ b/qtwayland-client-fix-scroll-direction-with-touchpads.patch @@ -0,0 +1,113 @@ +From 7f065653e2f372ee5b7e2ea4dbed6681ef41be68 Mon Sep 17 00:00:00 2001 +From: David Edmundson +Date: Tue, 07 Apr 2020 17:36:19 +0100 +Subject: [PATCH] Client: Fix scroll direction with touchpads + +Wayland axis events and QWheelEvents are opposite way rounds. This was +handled for the case of discrete events, but not continuous events. + +This leads to touchpads moving the wrong way round compared to other +clients. + +Change-Id: Iee4f5c620940a491949d4039cb3ac70649d83a2b +Pick-to: 5.15 +--- + +diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp +index e0f0c6c..ed4a0eb 100644 +--- a/src/client/qwaylandinputdevice.cpp ++++ b/src/client/qwaylandinputdevice.cpp +@@ -1025,6 +1025,12 @@ + *accumulatedError += delta - pixelDelta; + Q_ASSERT(qAbs(accumulatedError->x()) < 1.0); + Q_ASSERT(qAbs(accumulatedError->y()) < 1.0); ++ ++ // for continuous scroll events things should be ++ // in the same direction ++ // i.e converted so downwards surface co-ordinates (positive axis_value) ++ // goes to downwards in wheel event (negative value) ++ pixelDelta *= -1; + return pixelDelta; + } + +diff --git a/tests/auto/client/seatv5/tst_seatv5.cpp b/tests/auto/client/seatv5/tst_seatv5.cpp +index e333082..9312c2e 100644 +--- a/tests/auto/client/seatv5/tst_seatv5.cpp ++++ b/tests/auto/client/seatv5/tst_seatv5.cpp +@@ -63,6 +63,7 @@ + void simpleAxis(); + void fingerScroll(); + void fingerScrollSlow(); ++ void continuousScroll(); + void wheelDiscreteScroll(); + + // Touch tests +@@ -252,7 +253,7 @@ + QCOMPARE(e.phase, Qt::ScrollUpdate); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + // QCOMPARE(e.angleDelta, angleDelta); // TODO: what should this be? +- QCOMPARE(e.pixelDelta, QPoint(0, 10)); ++ QCOMPARE(e.pixelDelta, QPoint(0, -10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + +@@ -269,7 +270,7 @@ + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); + QVERIFY(qAbs(e.angleDelta.x()) > qAbs(e.angleDelta.y())); // Horizontal scroll +- QCOMPARE(e.pixelDelta, QPoint(10, 0)); ++ QCOMPARE(e.pixelDelta, QPoint(-10, 0)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + +@@ -284,7 +285,7 @@ + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); +- QCOMPARE(e.pixelDelta, QPoint(10, 10)); ++ QCOMPARE(e.pixelDelta, QPoint(-10, -10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + +@@ -338,7 +339,7 @@ + accumulated += e.pixelDelta; + QTRY_VERIFY(!window.m_events.empty()); + } +- QCOMPARE(accumulated.y(), 1); ++ QCOMPARE(accumulated.y(), -1); + } + void tst_seatv5::wheelDiscreteScroll() + { +@@ -370,6 +371,32 @@ + } + } + ++void tst_seatv5::continuousScroll() ++{ ++ WheelWindow window; ++ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); ++ ++ exec([=] { ++ auto *p = pointer(); ++ auto *c = client(); ++ p->sendEnter(xdgToplevel()->surface(), {32, 32}); ++ p->sendFrame(c); ++ p->sendAxisSource(c, Pointer::axis_source_continuous); ++ p->sendAxis(c, Pointer::axis_vertical_scroll, 10); ++ p->sendAxis(c, Pointer::axis_horizontal_scroll, -5); ++ p->sendFrame(c); ++ }); ++ ++ QTRY_VERIFY(!window.m_events.empty()); ++ { ++ auto e = window.m_events.takeFirst(); ++ QCOMPARE(e.phase, Qt::NoScrollPhase); ++ QCOMPARE(e.pixelDelta, QPoint(5, -10)); ++ QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // touchpads are not wheels ++ } ++ // Sending axis_stop is not mandatory when axis source != finger ++} ++ + void tst_seatv5::createsTouch() + { + QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().size(), 1); diff --git a/qtwayland-client-initialize-mscale-on-creation-instead-of-on-show.patch b/qtwayland-client-initialize-mscale-on-creation-instead-of-on-show.patch new file mode 100644 index 0000000..200052d --- /dev/null +++ b/qtwayland-client-initialize-mscale-on-creation-instead-of-on-show.patch @@ -0,0 +1,46 @@ +From 760abe1939f6afc146de2c5172b27b347043f327 Mon Sep 17 00:00:00 2001 +From: David Edmundson +Date: Fri, 24 Jul 2020 16:35:09 +0100 +Subject: [PATCH] client: Initialize mScale on creation instead of on show + +At the time of creating a QPlatformWindow we will have a QScreen +assigned. It may not be the right one until we get a screen_entered +event, pointing instead to the primary screen. + +When we get the screen_entered event we get the correct scale and call +handleWindowScreenChanged. However if it's the same screen as before +QWindowPrivate::setTopLevelScreen will no-op because from it's POV the +screen hasn't changed at all. + +This leaves the window having the scale change without any notification. + +This is notable with QQuickWidget which creates an FBO very early on +before the window is shown. This would then use the devicePixelRatio of +1 as it is currently unset and then not get any change notification. + +Change-Id: Ia7e4072e0bd900abc558bf8930fef4e1e7d2c553 +Pick-to: 5.15 +--- + +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index cfcc735..f7647a4 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -86,6 +86,8 @@ QWaylandWindow::QWaylandWindow(QWindow *window) + mFrameCallbackTimeout = frameCallbackTimeout; + } + ++ mScale = waylandScreen() ? waylandScreen()->scale() : 1; // fallback to 1 if we don't have a real screen ++ + static WId id = 1; + mWindowId = id++; + initializeWlSurface(); +@@ -184,8 +186,6 @@ void QWaylandWindow::initWindow() + } + } + +- mScale = waylandScreen()->scale(); +- + // Enable high-dpi rendering. Scale() returns the screen scale factor and will + // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale() + // to inform the compositor that high-resolution buffers will be provided. diff --git a/qtwayland-client-make-frameback-callback-timeout-configurable.patch b/qtwayland-client-make-frameback-callback-timeout-configurable.patch new file mode 100644 index 0000000..81b50d7 --- /dev/null +++ b/qtwayland-client-make-frameback-callback-timeout-configurable.patch @@ -0,0 +1,103 @@ +From efc94a48526baf92b8a78916e03e1fef5993fa95 Mon Sep 17 00:00:00 2001 +From: Eskil Abrahamsen Blomfeldt +Date: Mon, 06 Apr 2020 09:52:33 +0200 +Subject: [PATCH] Client: Make frame back callback timeout configurable + +To avoid rendering when windows are minimized, we try to detect +this case by checking if 100 ms passes after an update before +any frame callback arrives. Wayland expects to drive the rendering +through frame callbacks, while Qt expects to be informed when a +window is no longer visible and will run a tight, vsynced render loop +until it gets the appropriate event. This mismatch causes issues, +and the timeout is an attempt to avoid actively rendering to a hidden +surface by detecting the missing callbacks. + +It causes problems on embedded, though, when a device is so +busy that the timeout happens even when the window is visible. + +So we introduce a way to configure the timeout. Either increase +its duration if you can guarantee a certain minimum time between +events even with high load, or set it to 0 to disable it completely. +(at the expense of drawing to invisible windows). + +Note that this is required for fixing a critical memory leak that +happens when using Wayland on embedded. + +This was mistakenly pushed to 5.12 branch, so it will not be +automatically merged upstream. Therefore we have to +cherry-pick it. + +[ChangeLog][Client] Added support for QT_WAYLAND_FRAME_CALLBACK_TIMEOUT +environment variable, which can be used to disable or change the +internal frame callback timeout. If you see windows that stop rendering +or minimize on heavy load, then try setting the variable to 0. + +Task-number: QTBUG-82914 +Reviewed-by: David Edmundson +(cherry picked from commit 0a0e0eb8dca699858435dec5194b0b8b6ebd3cf8) +Change-Id: I2aeeecb0fab8f7be9b838e477c88eae22b322d75 +Reviewed-by: Janne Koskinen +--- + +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index 884a979..0545c80 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -79,6 +79,13 @@ + , mFrameQueue(mDisplay->createEventQueue()) + , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) + { ++ { ++ bool ok; ++ int frameCallbackTimeout = qEnvironmentVariableIntValue("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", &ok); ++ if (ok) ++ mFrameCallbackTimeout = frameCallbackTimeout; ++ } ++ + static WId id = 1; + mWindowId = id++; + initializeWlSurface(); +@@ -1096,7 +1103,7 @@ + if (event->timerId() != mFrameCallbackCheckIntervalTimerId) + return; + +- bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(100); ++ bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); + if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; +@@ -1157,13 +1164,15 @@ + mWaitingForUpdate = false; + + // Start a timer for handling the case when the compositor stops sending frame callbacks. +- QMetaObject::invokeMethod(this, [this] { +- if (mWaitingForFrameCallback) { +- if (mFrameCallbackCheckIntervalTimerId < 0) +- mFrameCallbackCheckIntervalTimerId = startTimer(100); +- mFrameCallbackElapsedTimer.start(); +- } +- }, Qt::QueuedConnection); ++ if (mFrameCallbackTimeout > 0) { ++ QMetaObject::invokeMethod(this, [this] { ++ if (mWaitingForFrameCallback) { ++ if (mFrameCallbackCheckIntervalTimerId < 0) ++ mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); ++ mFrameCallbackElapsedTimer.start(); ++ } ++ }, Qt::QueuedConnection); ++ } + } + + void QWaylandWindow::deliverUpdateRequest() +diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h +index ef03fd1..89d6272 100644 +--- a/src/client/qwaylandwindow_p.h ++++ b/src/client/qwaylandwindow_p.h +@@ -240,6 +240,7 @@ + bool mCanResize = true; + bool mResizeDirty = false; + bool mResizeAfterSwap; ++ int mFrameCallbackTimeout = 100; + QVariantMap m_properties; + + bool mSentInitialResize = false; diff --git a/qtwayland-client-send-subsurface-expose-event-when-toplevel-is-configured.patch b/qtwayland-client-send-subsurface-expose-event-when-toplevel-is-configured.patch new file mode 100644 index 0000000..6ea77eb --- /dev/null +++ b/qtwayland-client-send-subsurface-expose-event-when-toplevel-is-configured.patch @@ -0,0 +1,59 @@ +From e87bc5eda5191dfec9c8bdfdcd67faf15e79d1d1 Mon Sep 17 00:00:00 2001 +From: David Edmundson +Date: Sun, 26 Jul 2020 17:56:25 +0100 +Subject: [PATCH] Client: Send subsurface expose event when toplevel is configured + +If a subsurface is set to be visible on the cilent side before the top +level is configured it will do not create an exposeEvent and map a +buffer as we fail the check in isExposed() where we check the parent. +This is correct behavior. + +However, when the toplevel receives an applyConfigure from the shell +client we need subsurfaces to update accordingly. + +This fixes a race where subsurfaces are not shown with slow compositors. + +Change-Id: Icd156e7655d5b25535acc4d2fe77c31e19ebfa32 +Pick-to: 5.15 +--- + +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index f7647a4..9669cbb 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -517,10 +517,22 @@ void QWaylandWindow::applyConfigure() + doApplyConfigure(); + + lock.unlock(); +- sendExposeEvent(QRect(QPoint(), geometry().size())); ++ sendRecursiveExposeEvent(); + QWindowSystemInterface::flushWindowSystemEvents(); + } + ++void QWaylandWindow::sendRecursiveExposeEvent() ++{ ++ if (!window()->isVisible()) ++ return; ++ sendExposeEvent(QRect(QPoint(), geometry().size())); ++ ++ for (QWaylandSubSurface *subsurf : qAsConst(mChildren)) { ++ auto subWindow = subsurf->window(); ++ subWindow->sendRecursiveExposeEvent(); ++ } ++} ++ + void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) + { + Q_ASSERT(!buffer->committed()); +diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h +index f2055df..b57f5dd 100644 +--- a/src/client/qwaylandwindow_p.h ++++ b/src/client/qwaylandwindow_p.h +@@ -268,6 +268,7 @@ private: + + void handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); + void handleScreensChanged(); ++ void sendRecursiveExposeEvent(); + + bool mInResizeFromApplyConfigure = false; + QRect mLastExposeGeometry; diff --git a/qtwayland-fix-leaking-of-callback-timers.patch b/qtwayland-fix-leaking-of-callback-timers.patch new file mode 100644 index 0000000..d632af5 --- /dev/null +++ b/qtwayland-fix-leaking-of-callback-timers.patch @@ -0,0 +1,129 @@ +From fabaaec60a5fe477f7339c0d825d8b236a7e5330 Mon Sep 17 00:00:00 2001 +From: Janne Koskinen +Date: Tue, 24 Mar 2020 14:22:35 +0200 +Subject: [PATCH] Fix leaking of callback timers + +Use two timers to create timeout trigger for framecallback. +Removes multiple timers approach that is subject to race conditions. + +This was mistakenly pushed to 5.12 branch which will not be +automatically merged upstream, so it has to be cherry-picked. + +Fixes: QTBUG-82914 +Reviewed-by: Eskil Abrahamsen Blomfeldt +(cherry picked from commit d18c29931b0bc889fff66bdbde89133544ba0529) +Change-Id: I258c0c08f0ac5803192fc0024e40ba72e72c37a8 +Reviewed-by: Janne Koskinen +--- + +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index 9fa2f15..884a979 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -257,10 +257,7 @@ + mFrameCallback = nullptr; + } + +- int timerId = mFrameCallbackTimerId.fetchAndStoreOrdered(-1); +- if (timerId != -1) { +- killTimer(timerId); +- } ++ mFrameCallbackElapsedTimer.invalidate(); + mWaitingForFrameCallback = false; + mFrameCallbackTimedOut = false; + +@@ -602,15 +599,11 @@ + + void QWaylandWindow::handleFrameCallback() + { +- // Stop the timer and stop waiting immediately +- int timerId = mFrameCallbackTimerId.fetchAndStoreOrdered(-1); + mWaitingForFrameCallback = false; ++ mFrameCallbackElapsedTimer.invalidate(); + + // The rest can wait until we can run it on the correct thread +- auto doHandleExpose = [this, timerId]() { +- if (timerId != -1) +- killTimer(timerId); +- ++ auto doHandleExpose = [this]() { + bool wasExposed = isExposed(); + mFrameCallbackTimedOut = false; + if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? +@@ -645,13 +638,6 @@ + sendExposeEvent(QRect()); + } + +- // Stop current frame timer if any, can't use killTimer directly, because we might be on a diffent thread +- // Ordered semantics is needed to avoid stopping the timer twice and not miss it when it's +- // started by other writes +- int fcbId = mFrameCallbackTimerId.fetchAndStoreOrdered(-1); +- if (fcbId != -1) +- QMetaObject::invokeMethod(this, [this, fcbId] { killTimer(fcbId); }, Qt::QueuedConnection); +- + return !mWaitingForFrameCallback; + } + +@@ -1107,8 +1093,16 @@ + + void QWaylandWindow::timerEvent(QTimerEvent *event) + { +- if (mFrameCallbackTimerId.testAndSetOrdered(event->timerId(), -1)) { +- killTimer(event->timerId()); ++ if (event->timerId() != mFrameCallbackCheckIntervalTimerId) ++ return; ++ ++ bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(100); ++ if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { ++ killTimer(mFrameCallbackCheckIntervalTimerId); ++ mFrameCallbackCheckIntervalTimerId = -1; ++ } ++ if (mFrameCallbackElapsedTimer.isValid() && callbackTimerExpired) { ++ mFrameCallbackElapsedTimer.invalidate(); + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; +@@ -1162,15 +1156,13 @@ + mWaitingForFrameCallback = true; + mWaitingForUpdate = false; + +- // Stop current frame timer if any, can't use killTimer directly, see comment above. +- int fcbId = mFrameCallbackTimerId.fetchAndStoreOrdered(-1); +- if (fcbId != -1) +- QMetaObject::invokeMethod(this, [this, fcbId] { killTimer(fcbId); }, Qt::QueuedConnection); +- + // Start a timer for handling the case when the compositor stops sending frame callbacks. +- QMetaObject::invokeMethod(this, [this] { // Again; can't do it directly +- if (mWaitingForFrameCallback) +- mFrameCallbackTimerId = startTimer(100); ++ QMetaObject::invokeMethod(this, [this] { ++ if (mWaitingForFrameCallback) { ++ if (mFrameCallbackCheckIntervalTimerId < 0) ++ mFrameCallbackCheckIntervalTimerId = startTimer(100); ++ mFrameCallbackElapsedTimer.start(); ++ } + }, Qt::QueuedConnection); + } + +diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h +index 352df89..ef03fd1 100644 +--- a/src/client/qwaylandwindow_p.h ++++ b/src/client/qwaylandwindow_p.h +@@ -58,6 +58,7 @@ + #include + #include + #include ++#include + + #include + +@@ -225,7 +226,8 @@ + WId mWindowId; + bool mWaitingForFrameCallback = false; + bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out +- QAtomicInt mFrameCallbackTimerId = -1; // Started on commit, reset on frame callback ++ int mFrameCallbackCheckIntervalTimerId = -1; ++ QElapsedTimer mFrameCallbackElapsedTimer; + struct ::wl_callback *mFrameCallback = nullptr; + struct ::wl_event_queue *mFrameQueue = nullptr; + QWaitCondition mFrameSyncWait;