diff --git a/pkgs/development/libraries/qt-5/5.12/default.nix b/pkgs/development/libraries/qt-5/5.12/default.nix index 7b4addd9161..a2062af65f0 100644 --- a/pkgs/development/libraries/qt-5/5.12/default.nix +++ b/pkgs/development/libraries/qt-5/5.12/default.nix @@ -53,6 +53,7 @@ let qtbase = [ ./qtbase.patch ./qtbase-fixguicmake.patch + ./qtbase-trayicons.patch # can be removed with 5.12.4 or 5.13 ]; qtdeclarative = [ ./qtdeclarative.patch ]; qtscript = [ ./qtscript.patch ]; @@ -67,6 +68,10 @@ let ./qtwebkit-darwin-no-qos-classes.patch ]; qttools = [ ./qttools.patch ]; + qtwayland = [ + ./qtwayland-fix-webengine-freezeups-1.patch # can be removed with 5.12.4 or 5.13 + ./qtwayland-fix-webengine-freezeups-2.patch # can be removed with 5.12.4 or 5.13 + ]; }; qtModule = diff --git a/pkgs/development/libraries/qt-5/5.12/qtbase-trayicons.patch b/pkgs/development/libraries/qt-5/5.12/qtbase-trayicons.patch new file mode 100644 index 00000000000..0376c1c96c2 --- /dev/null +++ b/pkgs/development/libraries/qt-5/5.12/qtbase-trayicons.patch @@ -0,0 +1,175 @@ +From 4d5e59c54a805ba4e7311fe58c9adc492ca1b35a Mon Sep 17 00:00:00 2001 +From: Alexander Volkov +Date: Mon, 4 Feb 2019 18:42:35 +0300 +Subject: [PATCH] QSystemTrayIcon/X11: Create tray icon window when system tray + appears + +... and destroy it otherwise. + +Fixes: QTBUG-61898 +Fixes: QTBUG-73459 +Done-with: Gatis Paeglis +Change-Id: I6bd8f397f7ccdb123f6a60d4fa466f7b0d760dfc +--- + src/widgets/util/qsystemtrayicon_p.h | 4 ++ + src/widgets/util/qsystemtrayicon_x11.cpp | 75 ++++++++++++++++++++++---------- + 2 files changed, 57 insertions(+), 22 deletions(-) + +diff --git a/src/widgets/util/qsystemtrayicon_p.h b/src/widgets/util/qsystemtrayicon_p.h +index 5bdf020a472..e31532ea193 100644 +--- a/src/widgets/util/qsystemtrayicon_p.h ++++ b/src/widgets/util/qsystemtrayicon_p.h +@@ -69,6 +69,7 @@ + QT_BEGIN_NAMESPACE + + class QSystemTrayIconSys; ++class QSystemTrayWatcher; + class QPlatformSystemTrayIcon; + class QToolButton; + class QLabel; +@@ -90,6 +91,8 @@ public: + void showMessage_sys(const QString &title, const QString &msg, const QIcon &icon, + QSystemTrayIcon::MessageIcon msgIcon, int msecs); + ++ void destroyIcon(); ++ + static bool isSystemTrayAvailable_sys(); + static bool supportsMessages_sys(); + +@@ -101,6 +104,7 @@ public: + QSystemTrayIconSys *sys; + QPlatformSystemTrayIcon *qpa_sys; + bool visible; ++ QSystemTrayWatcher *trayWatcher; + + private: + void install_sys_qpa(); +diff --git a/src/widgets/util/qsystemtrayicon_x11.cpp b/src/widgets/util/qsystemtrayicon_x11.cpp +index 86532456c76..70e5f3678ea 100644 +--- a/src/widgets/util/qsystemtrayicon_x11.cpp ++++ b/src/widgets/util/qsystemtrayicon_x11.cpp +@@ -92,9 +92,6 @@ protected: + virtual void resizeEvent(QResizeEvent *) override; + virtual void moveEvent(QMoveEvent *) override; + +-private slots: +- void systemTrayWindowChanged(QScreen *screen); +- + private: + QSystemTrayIcon *q; + }; +@@ -116,15 +113,6 @@ QSystemTrayIconSys::QSystemTrayIconSys(QSystemTrayIcon *qIn) + setMouseTracking(true); + } + +-void QSystemTrayIconSys::systemTrayWindowChanged(QScreen *) +-{ +- if (!locateSystemTray()) { +- QBalloonTip::hideBalloon(); +- hide(); // still no luck +- destroy(); +- } +-} +- + QRect QSystemTrayIconSys::globalGeometry() const + { + return QRect(mapToGlobal(QPoint(0, 0)), size()); +@@ -199,10 +187,41 @@ void QSystemTrayIconSys::resizeEvent(QResizeEvent *event) + } + //////////////////////////////////////////////////////////////////////////// + ++class QSystemTrayWatcher: public QObject ++{ ++ Q_OBJECT ++public: ++ QSystemTrayWatcher(QSystemTrayIcon *trayIcon) ++ : QObject(trayIcon) ++ , mTrayIcon(trayIcon) ++ { ++ // This code uses string-based syntax because we want to connect to a signal ++ // which is defined in XCB plugin - QXcbNativeInterface::systemTrayWindowChanged(). ++ connect(qGuiApp->platformNativeInterface(), SIGNAL(systemTrayWindowChanged(QScreen*)), ++ this, SLOT(systemTrayWindowChanged(QScreen*))); ++ } ++ ++private slots: ++ void systemTrayWindowChanged(QScreen *) ++ { ++ auto icon = static_cast(QObjectPrivate::get(mTrayIcon)); ++ icon->destroyIcon(); ++ if (icon->visible && locateSystemTray()) { ++ icon->sys = new QSystemTrayIconSys(mTrayIcon); ++ icon->sys->show(); ++ } ++ } ++ ++private: ++ QSystemTrayIcon *mTrayIcon = nullptr; ++}; ++//////////////////////////////////////////////////////////////////////////// ++ + QSystemTrayIconPrivate::QSystemTrayIconPrivate() + : sys(0), + qpa_sys(QGuiApplicationPrivate::platformTheme()->createPlatformSystemTrayIcon()), +- visible(false) ++ visible(false), ++ trayWatcher(nullptr) + { + } + +@@ -213,16 +232,21 @@ QSystemTrayIconPrivate::~QSystemTrayIconPrivate() + + void QSystemTrayIconPrivate::install_sys() + { ++ Q_Q(QSystemTrayIcon); ++ + if (qpa_sys) { + install_sys_qpa(); + return; + } +- Q_Q(QSystemTrayIcon); +- if (!sys && locateSystemTray()) { +- sys = new QSystemTrayIconSys(q); +- QObject::connect(QGuiApplication::platformNativeInterface(), SIGNAL(systemTrayWindowChanged(QScreen*)), +- sys, SLOT(systemTrayWindowChanged(QScreen*))); +- sys->show(); ++ ++ if (!sys) { ++ if (!trayWatcher) ++ trayWatcher = new QSystemTrayWatcher(q); ++ ++ if (locateSystemTray()) { ++ sys = new QSystemTrayIconSys(q); ++ sys->show(); ++ } + } + } + +@@ -241,14 +265,21 @@ void QSystemTrayIconPrivate::remove_sys() + remove_sys_qpa(); + return; + } ++ ++ destroyIcon(); ++} ++ ++void QSystemTrayIconPrivate::destroyIcon() ++{ + if (!sys) + return; + QBalloonTip::hideBalloon(); +- sys->hide(); // this should do the trick, but... +- delete sys; // wm may resize system tray only for DestroyEvents +- sys = 0; ++ sys->hide(); ++ delete sys; ++ sys = nullptr; + } + ++ + void QSystemTrayIconPrivate::updateIcon_sys() + { + if (qpa_sys) { +-- +2.16.3 + diff --git a/pkgs/development/libraries/qt-5/5.12/qtwayland-fix-webengine-freezeups-1.patch b/pkgs/development/libraries/qt-5/5.12/qtwayland-fix-webengine-freezeups-1.patch new file mode 100644 index 00000000000..2e5a890be05 --- /dev/null +++ b/pkgs/development/libraries/qt-5/5.12/qtwayland-fix-webengine-freezeups-1.patch @@ -0,0 +1,139 @@ +From 9aced4f9571e74cc57b853598aa4b3f38d66363d Mon Sep 17 00:00:00 2001 +From: Johan Klokkhammer Helsing +Date: Thu, 1 Nov 2018 13:48:52 +0100 +Subject: [PATCH 1/2] Client: Don't be exposed if we want to create a sub or + shell surface + +Because some shells don't allow attaching buffers before configure, we need to +not be exposed until we know that we don't want a shell surface. + +Change-Id: Ida7101a99f953d02cf6401e4ea8d28cfabd6e102 +Reviewed-by: Giulio Camuffo +Reviewed-by: David Edmundson +--- + src/client/qwaylanddisplay.cpp | 13 ++++++------- + src/client/qwaylanddisplay_p.h | 6 +++--- + src/client/qwaylandwindow.cpp | 18 +++++++++++++++--- + 3 files changed, 24 insertions(+), 13 deletions(-) + +diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp +index a2957e0d..f2bd3160 100644 +--- a/src/client/qwaylanddisplay.cpp ++++ b/src/client/qwaylanddisplay.cpp +@@ -88,13 +88,6 @@ struct wl_surface *QWaylandDisplay::createSurface(void *handle) + return surface; + } + +-QWaylandShellSurface *QWaylandDisplay::createShellSurface(QWaylandWindow *window) +-{ +- if (!mWaylandIntegration->shellIntegration()) +- return nullptr; +- return mWaylandIntegration->shellIntegration()->createShellSurface(window); +-} +- + struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) + { + struct ::wl_region *region = mCompositor.create_region(); +@@ -108,12 +101,18 @@ struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) + ::wl_subsurface *QWaylandDisplay::createSubSurface(QWaylandWindow *window, QWaylandWindow *parent) + { + if (!mSubCompositor) { ++ qCWarning(lcQpaWayland) << "Can't create subsurface, not supported by the compositor."; + return nullptr; + } + + return mSubCompositor->get_subsurface(window->object(), parent->object()); + } + ++QWaylandShellIntegration *QWaylandDisplay::shellIntegration() const ++{ ++ return mWaylandIntegration->shellIntegration(); ++} ++ + QWaylandClientBufferIntegration * QWaylandDisplay::clientBufferIntegration() const + { + return mWaylandIntegration->clientBufferIntegration(); +diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h +index 0dd8d7af..cc6a0a72 100644 +--- a/src/client/qwaylanddisplay_p.h ++++ b/src/client/qwaylanddisplay_p.h +@@ -94,7 +94,7 @@ class QWaylandQtKeyExtension; + class QWaylandWindow; + class QWaylandIntegration; + class QWaylandHardwareIntegration; +-class QWaylandShellSurface; ++class QWaylandShellIntegration; + class QWaylandCursorTheme; + + typedef void (*RegistryListener)(void *data, +@@ -115,13 +115,13 @@ public: + QWaylandScreen *screenForOutput(struct wl_output *output) const; + + struct wl_surface *createSurface(void *handle); +- QWaylandShellSurface *createShellSurface(QWaylandWindow *window); + struct ::wl_region *createRegion(const QRegion &qregion); + struct ::wl_subsurface *createSubSurface(QWaylandWindow *window, QWaylandWindow *parent); + ++ QWaylandShellIntegration *shellIntegration() const; + QWaylandClientBufferIntegration *clientBufferIntegration() const; +- + QWaylandWindowManagerIntegration *windowManagerIntegration() const; ++ + #if QT_CONFIG(cursor) + void setCursor(struct wl_buffer *buffer, struct wl_cursor_image *image, qreal dpr); + void setCursor(const QSharedPointer &buffer, const QPoint &hotSpot, qreal dpr); +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index 4ac2ca51..600ea1df 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -50,6 +50,7 @@ + #include "qwaylandnativeinterface_p.h" + #include "qwaylanddecorationfactory_p.h" + #include "qwaylandshmbackingstore_p.h" ++#include "qwaylandshellintegration_p.h" + + #if QT_CONFIG(wayland_datadevice) + #include "qwaylanddatadevice_p.h" +@@ -138,8 +139,9 @@ void QWaylandWindow::initWindow() + } + } else if (shouldCreateShellSurface()) { + Q_ASSERT(!mShellSurface); ++ Q_ASSERT(mDisplay->shellIntegration()); + +- mShellSurface = mDisplay->createShellSurface(this); ++ mShellSurface = mDisplay->shellIntegration()->createShellSurface(this); + if (mShellSurface) { + // Set initial surface title + setWindowTitle(window()->title()); +@@ -211,6 +213,9 @@ void QWaylandWindow::initializeWlSurface() + + bool QWaylandWindow::shouldCreateShellSurface() const + { ++ if (!mDisplay->shellIntegration()) ++ return false; ++ + if (shouldCreateSubSurface()) + return false; + +@@ -963,9 +968,16 @@ void QWaylandWindow::unfocus() + + bool QWaylandWindow::isExposed() const + { ++ if (!window()->isVisible()) ++ return false; ++ + if (mShellSurface) +- return window()->isVisible() && mShellSurface->isExposed(); +- return QPlatformWindow::isExposed(); ++ return mShellSurface->isExposed(); ++ ++ if (mSubSurfaceWindow) ++ return mSubSurfaceWindow->parent()->isExposed(); ++ ++ return !(shouldCreateShellSurface() || shouldCreateSubSurface()); + } + + bool QWaylandWindow::isActive() const +-- +2.22.0 + diff --git a/pkgs/development/libraries/qt-5/5.12/qtwayland-fix-webengine-freezeups-2.patch b/pkgs/development/libraries/qt-5/5.12/qtwayland-fix-webengine-freezeups-2.patch new file mode 100644 index 00000000000..b1a8d6c9ae0 --- /dev/null +++ b/pkgs/development/libraries/qt-5/5.12/qtwayland-fix-webengine-freezeups-2.patch @@ -0,0 +1,493 @@ +From d85beeb65820d6e5733f88af63b15f77d03aa6ba Mon Sep 17 00:00:00 2001 +From: Johan Klokkhammer Helsing +Date: Mon, 28 Jan 2019 09:48:26 +0100 +Subject: [PATCH 2/2] Client: Full implementation for frame callbacks (second + try) + +The Wayland plugin now takes full control over delivering update request and +implement frame callbacks for both egl and shm. + +[ChangeLog][QPA plugin] The non-blocking version of eglSwapBuffers is now used, if +supported. This fixed a bug where minimized windows would block the event loop. + +[ChangeLog][QPA plugin] Windows that don't get frame callbacks from the +compositor within 100 ms are now set as not exposed. This should stop most +clients from rendering unnecessary frames to minimized or hidden windows. + +Also, when we relied on the QPA version of requestUpdate, we would sometimes +deliver one update request while we were waiting for a frame callback. When we +implement the fallback timer ourselves we can make sure we only deliver the +fallback if there are no pending frame callbacks. + +QtQuick and other applications often depend on blocking swapBuffers to throttle +animations. If the context's surface format has a non-zero swapInterval, try to +emulate a blocking swap. + +Fixes: QTBUG-69077 +Change-Id: I3c6964f31a16e9aff70b8ec3c5340e640a30fef2 +Reviewed-by: Paul Olav Tvete +--- + src/client/qwaylanddisplay.cpp | 38 +++- + src/client/qwaylanddisplay_p.h | 3 + + src/client/qwaylandwindow.cpp | 180 +++++++++++++++--- + src/client/qwaylandwindow_p.h | 17 +- + .../client/wayland-egl/qwaylandglcontext.cpp | 25 ++- + .../qwaylandxcompositeeglcontext.cpp | 2 +- + .../qwaylandxcompositeglxcontext.cpp | 2 +- + 7 files changed, 218 insertions(+), 49 deletions(-) + +diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp +index f2bd3160..82003a30 100644 +--- a/src/client/qwaylanddisplay.cpp ++++ b/src/client/qwaylanddisplay.cpp +@@ -68,6 +68,8 @@ + + #include + ++#include ++ + #include + #include + +@@ -190,7 +192,6 @@ void QWaylandDisplay::flushRequests() + wl_display_flush(mDisplay); + } + +- + void QWaylandDisplay::blockingReadEvents() + { + if (wl_display_dispatch(mDisplay) < 0) { +@@ -204,6 +205,41 @@ void QWaylandDisplay::exitWithError() + ::exit(1); + } + ++wl_event_queue *QWaylandDisplay::createEventQueue() ++{ ++ return wl_display_create_queue(mDisplay); ++} ++ ++void QWaylandDisplay::dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout) ++{ ++ 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(); ++ exitWithError(); ++ } ++ if (!condition()) ++ break; ++ } ++} ++ + QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const + { + for (int i = 0; i < mScreens.size(); ++i) { +diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h +index cc6a0a72..fa8b4c3f 100644 +--- a/src/client/qwaylanddisplay_p.h ++++ b/src/client/qwaylanddisplay_p.h +@@ -182,6 +182,9 @@ public: + void handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice); + void handleWindowDestroyed(QWaylandWindow *window); + ++ wl_event_queue *createEventQueue(); ++ void dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout = -1); ++ + public slots: + void blockingReadEvents(); + void flushRequests(); +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index 600ea1df..4d399f8f 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -67,6 +67,7 @@ + #include + + #include ++#include + + #include + +@@ -81,6 +82,7 @@ QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr; + QWaylandWindow::QWaylandWindow(QWindow *window) + : QPlatformWindow(window) + , mDisplay(waylandScreen()->display()) ++ , mFrameQueue(mDisplay->createEventQueue()) + , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) + { + static WId id = 1; +@@ -363,6 +365,8 @@ void QWaylandWindow::sendExposeEvent(const QRect &rect) + { + if (!(mShellSurface && mShellSurface->handleExpose(rect))) + QWindowSystemInterface::handleExposeEvent(window(), rect); ++ else ++ qCDebug(lcQpaWayland) << "sendExposeEvent: intercepted by shell extension, not sending"; + mLastExposeGeometry = rect; + } + +@@ -547,18 +551,11 @@ void QWaylandWindow::handleScreenRemoved(QScreen *qScreen) + void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) + { + Q_ASSERT(!buffer->committed()); +- if (mFrameCallback) { +- wl_callback_destroy(mFrameCallback); +- mFrameCallback = nullptr; +- } +- + if (buffer) { +- mFrameCallback = frame(); +- wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); +- mWaitingForFrameSync = true; ++ handleUpdate(); + buffer->setBusy(); + +- attach(buffer->buffer(), x, y); ++ QtWayland::wl_surface::attach(buffer->buffer(), x, y); + } else { + QtWayland::wl_surface::attach(nullptr, 0, 0); + } +@@ -614,32 +611,61 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) + } + + const wl_callback_listener QWaylandWindow::callbackListener = { +- QWaylandWindow::frameCallback ++ [](void *data, wl_callback *callback, uint32_t time) { ++ Q_UNUSED(callback); ++ Q_UNUSED(time); ++ auto *window = static_cast(data); ++ if (window->thread() != QThread::currentThread()) ++ QMetaObject::invokeMethod(window, [=] { window->handleFrameCallback(); }, Qt::QueuedConnection); ++ else ++ window->handleFrameCallback(); ++ } + }; + +-void QWaylandWindow::frameCallback(void *data, struct wl_callback *callback, uint32_t time) ++void QWaylandWindow::handleFrameCallback() + { +- Q_UNUSED(time); +- Q_UNUSED(callback); +- QWaylandWindow *self = static_cast(data); ++ bool wasExposed = isExposed(); + +- self->mWaitingForFrameSync = false; +- if (self->mUpdateRequested) { +- self->mUpdateRequested = false; +- self->deliverUpdateRequest(); ++ if (mFrameCallbackTimerId != -1) { ++ killTimer(mFrameCallbackTimerId); ++ mFrameCallbackTimerId = -1; + } ++ ++ mWaitingForFrameCallback = false; ++ mFrameCallbackTimedOut = false; ++ ++ if (!wasExposed && isExposed()) ++ sendExposeEvent(QRect(QPoint(), geometry().size())); ++ if (wasExposed && hasPendingUpdateRequest()) ++ deliverUpdateRequest(); + } + + QMutex QWaylandWindow::mFrameSyncMutex; + +-void QWaylandWindow::waitForFrameSync() ++bool QWaylandWindow::waitForFrameSync(int timeout) + { + QMutexLocker locker(&mFrameSyncMutex); +- if (!mWaitingForFrameSync) +- return; +- mDisplay->flushRequests(); +- while (mWaitingForFrameSync) +- mDisplay->blockingReadEvents(); ++ if (!mWaitingForFrameCallback) ++ return true; ++ ++ wl_proxy_set_queue(reinterpret_cast(mFrameCallback), mFrameQueue); ++ mDisplay->dispatchQueueWhile(mFrameQueue, [&]() { return mWaitingForFrameCallback; }, timeout); ++ ++ if (mWaitingForFrameCallback) { ++ qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; ++ mFrameCallbackTimedOut = true; ++ mWaitingForUpdate = false; ++ sendExposeEvent(QRect()); ++ } ++ ++ // Stop current frame timer if any, can't use killTimer directly, because we might be on a diffent thread ++ if (mFrameCallbackTimerId != -1) { ++ int id = mFrameCallbackTimerId; ++ mFrameCallbackTimerId = -1; ++ QMetaObject::invokeMethod(this, [=] { killTimer(id); }, Qt::QueuedConnection); ++ } ++ ++ return !mWaitingForFrameCallback; + } + + QMargins QWaylandWindow::frameMargins() const +@@ -971,6 +997,9 @@ bool QWaylandWindow::isExposed() const + if (!window()->isVisible()) + return false; + ++ if (mFrameCallbackTimedOut) ++ return false; ++ + if (mShellSurface) + return mShellSurface->isExposed(); + +@@ -1046,12 +1075,107 @@ QVariant QWaylandWindow::property(const QString &name, const QVariant &defaultVa + return m_properties.value(name, defaultValue); + } + ++void QWaylandWindow::timerEvent(QTimerEvent *event) ++{ ++ if (event->timerId() == mFallbackUpdateTimerId) { ++ killTimer(mFallbackUpdateTimerId); ++ mFallbackUpdateTimerId = -1; ++ qCDebug(lcWaylandBackingstore) << "mFallbackUpdateTimer timed out"; ++ ++ if (!isExposed()) { ++ qCDebug(lcWaylandBackingstore) << "Fallback update timer: Window not exposed," ++ << "not delivering update request."; ++ return; ++ } ++ ++ if (mWaitingForUpdate && hasPendingUpdateRequest() && !mWaitingForFrameCallback) { ++ qCWarning(lcWaylandBackingstore) << "Delivering update request through fallback timer," ++ << "may not be in sync with display"; ++ deliverUpdateRequest(); ++ } ++ } ++ ++ if (event->timerId() == mFrameCallbackTimerId) { ++ killTimer(mFrameCallbackTimerId); ++ mFrameCallbackTimerId = -1; ++ qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; ++ mFrameCallbackTimedOut = true; ++ mWaitingForUpdate = false; ++ sendExposeEvent(QRect()); ++ } ++} ++ + void QWaylandWindow::requestUpdate() + { +- if (!mWaitingForFrameSync) +- QPlatformWindow::requestUpdate(); +- else +- mUpdateRequested = true; ++ 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; ++ ++ // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet ++ if (mWaitingForUpdate) { ++ // Ideally, we should just have returned here, but we're not guaranteed that the client ++ // will actually update, so start this timer to deliver another request update after a while ++ // *IF* the client doesn't update. ++ int fallbackTimeout = 100; ++ mFallbackUpdateTimerId = startTimer(fallbackTimeout); ++ return; ++ } ++ ++ // Some applications (such as Qt Quick) depend on updates being delivered asynchronously, ++ // so use invokeMethod to delay the delivery a bit. ++ QMetaObject::invokeMethod(this, [this] { ++ // Things might have changed in the meantime ++ if (hasPendingUpdateRequest() && !mWaitingForUpdate && !mWaitingForFrameCallback) ++ deliverUpdateRequest(); ++ }, Qt::QueuedConnection); ++} ++ ++// Should be called whenever we commit a buffer (directly through wl_surface.commit or indirectly ++// with eglSwapBuffers) to know when it's time to commit the next one. ++// Can be called from the render thread (without locking anything) so make sure to not make races in this method. ++void QWaylandWindow::handleUpdate() ++{ ++ // TODO: Should sync subsurfaces avoid requesting frame callbacks? ++ ++ if (mFrameCallback) { ++ wl_callback_destroy(mFrameCallback); ++ mFrameCallback = nullptr; ++ } ++ ++ if (mFallbackUpdateTimerId != -1) { ++ // Ideally, we would stop the fallback timer here, but since we're on another thread, ++ // it's not allowed. Instead we set mFallbackUpdateTimer to -1 here, so we'll just ++ // ignore it if it times out before it's cleaned up by the invokeMethod call. ++ int id = mFallbackUpdateTimerId; ++ mFallbackUpdateTimerId = -1; ++ QMetaObject::invokeMethod(this, [=] { killTimer(id); }, Qt::QueuedConnection); ++ } ++ ++ mFrameCallback = frame(); ++ wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); ++ mWaitingForFrameCallback = true; ++ mWaitingForUpdate = false; ++ ++ // Stop current frame timer if any, can't use killTimer directly, see comment above. ++ if (mFrameCallbackTimerId != -1) { ++ int id = mFrameCallbackTimerId; ++ mFrameCallbackTimerId = -1; ++ QMetaObject::invokeMethod(this, [=] { killTimer(id); }, Qt::QueuedConnection); ++ } ++ ++ // Start a timer for handling the case when the compositor stops sending frame callbacks. ++ QMetaObject::invokeMethod(this, [=] { // Again; can't do it directly ++ if (mWaitingForFrameCallback) ++ mFrameCallbackTimerId = startTimer(100); ++ }, Qt::QueuedConnection); ++} ++ ++void QWaylandWindow::deliverUpdateRequest() ++{ ++ mWaitingForUpdate = true; ++ QPlatformWindow::deliverUpdateRequest(); + } + + void QWaylandWindow::addAttachOffset(const QPoint point) +diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h +index 56ebd3cc..c47123dc 100644 +--- a/src/client/qwaylandwindow_p.h ++++ b/src/client/qwaylandwindow_p.h +@@ -120,7 +120,7 @@ public: + void handleExpose(const QRegion ®ion); + void commit(QWaylandBuffer *buffer, const QRegion &damage); + +- void waitForFrameSync(); ++ bool waitForFrameSync(int timeout); + + QMargins frameMargins() const override; + +@@ -191,7 +191,10 @@ public: + + bool startSystemMove(const QPoint &pos) override; + ++ void timerEvent(QTimerEvent *event) override; + void requestUpdate() override; ++ void handleUpdate(); ++ void deliverUpdateRequest() override; + + public slots: + void applyConfigure(); +@@ -211,10 +214,17 @@ protected: + Qt::MouseButtons mMousePressedInContentArea = Qt::NoButton; + + WId mWindowId; +- bool mWaitingForFrameSync = false; ++ bool mWaitingForFrameCallback = false; ++ bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out ++ int mFrameCallbackTimerId = -1; // Started on commit, reset on frame callback + struct ::wl_callback *mFrameCallback = nullptr; ++ struct ::wl_event_queue *mFrameQueue = nullptr; + QWaitCondition mFrameSyncWait; + ++ // True when we have called deliverRequestUpdate, but the client has not yet attached a new buffer ++ bool mWaitingForUpdate = false; ++ int mFallbackUpdateTimerId = -1; // Started when waiting for app to commit ++ + QMutex mResizeLock; + bool mWaitingToApplyConfigure = false; + bool mCanResize = true; +@@ -253,11 +263,10 @@ private: + void handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); + void handleScreenChanged(); + +- bool mUpdateRequested = false; + QRect mLastExposeGeometry; + + static const wl_callback_listener callbackListener; +- static void frameCallback(void *data, struct wl_callback *wl_callback, uint32_t time); ++ void handleFrameCallback(); + + static QMutex mFrameSyncMutex; + static QWaylandWindow *mMouseGrab; +diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp +index e58403ad..30dab408 100644 +--- a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp ++++ b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp +@@ -315,7 +315,9 @@ QWaylandGLContext::QWaylandGLContext(EGLDisplay eglDisplay, QWaylandDisplay *dis + mSupportNonBlockingSwap = false; + } + if (!mSupportNonBlockingSwap) { +- qWarning() << "Non-blocking swap buffers not supported. Subsurface rendering can be affected."; ++ qWarning(lcQpaWayland) << "Non-blocking swap buffers not supported." ++ << "Subsurface rendering can be affected." ++ << "It may also cause the event loop to freeze in some situations"; + } + + updateGLFormat(); +@@ -550,20 +552,15 @@ void QWaylandGLContext::swapBuffers(QPlatformSurface *surface) + m_blitter->blit(window); + } + +- +- QWaylandSubSurface *sub = window->subSurfaceWindow(); +- if (sub) { +- QMutexLocker l(sub->syncMutex()); +- +- int si = (sub->isSync() && mSupportNonBlockingSwap) ? 0 : m_format.swapInterval(); +- +- eglSwapInterval(m_eglDisplay, si); +- eglSwapBuffers(m_eglDisplay, eglSurface); +- } else { +- eglSwapInterval(m_eglDisplay, m_format.swapInterval()); +- eglSwapBuffers(m_eglDisplay, eglSurface); ++ int swapInterval = mSupportNonBlockingSwap ? 0 : m_format.swapInterval(); ++ eglSwapInterval(m_eglDisplay, swapInterval); ++ if (swapInterval == 0 && m_format.swapInterval() > 0) { ++ // Emulating a blocking swap ++ glFlush(); // Flush before waiting so we can swap more quickly when the frame event arrives ++ window->waitForFrameSync(100); + } +- ++ window->handleUpdate(); ++ eglSwapBuffers(m_eglDisplay, eglSurface); + + window->setCanResize(true); + } +diff --git a/src/hardwareintegration/client/xcomposite-egl/qwaylandxcompositeeglcontext.cpp b/src/hardwareintegration/client/xcomposite-egl/qwaylandxcompositeeglcontext.cpp +index c07ad534..a6fead95 100644 +--- a/src/hardwareintegration/client/xcomposite-egl/qwaylandxcompositeeglcontext.cpp ++++ b/src/hardwareintegration/client/xcomposite-egl/qwaylandxcompositeeglcontext.cpp +@@ -65,7 +65,7 @@ void QWaylandXCompositeEGLContext::swapBuffers(QPlatformSurface *surface) + QSize size = w->geometry().size(); + + w->commit(w->buffer(), QRegion(0, 0, size.width(), size.height())); +- w->waitForFrameSync(); ++ w->waitForFrameSync(100); + } + + EGLSurface QWaylandXCompositeEGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface) +diff --git a/src/hardwareintegration/client/xcomposite-glx/qwaylandxcompositeglxcontext.cpp b/src/hardwareintegration/client/xcomposite-glx/qwaylandxcompositeglxcontext.cpp +index 33ae2e03..35188741 100644 +--- a/src/hardwareintegration/client/xcomposite-glx/qwaylandxcompositeglxcontext.cpp ++++ b/src/hardwareintegration/client/xcomposite-glx/qwaylandxcompositeglxcontext.cpp +@@ -93,7 +93,7 @@ void QWaylandXCompositeGLXContext::swapBuffers(QPlatformSurface *surface) + glXSwapBuffers(m_display, w->xWindow()); + + w->commit(w->buffer(), QRegion(0, 0, size.width(), size.height())); +- w->waitForFrameSync(); ++ w->waitForFrameSync(100); + } + + QFunctionPointer QWaylandXCompositeGLXContext::getProcAddress(const char *procName) +-- +2.22.0 +