qt5.12: Add patches for QTBUG-73459 and QTBUG-69077

QT 5.12 introduced a regression, where a QT program wouldn't show its
tray icon, if there was no tray bar during program startup. (QTBUG-73459)

QT 5.12 introduced a regression, where qtwebengine applications would
freeze in some wayland compositors if a surface from the instance was not
visible (for example having a qutebrowser window on another workspace in
sway would freeze all qutebrowser windows).

Both got fixed already in Qt 5.12.4, but according to #57042 and its
sibling issues/PRs it doesn't seem to get fixed in near future for
nixpkgs.
This commit is contained in:
Milan Pässler 2019-09-09 12:02:38 +02:00
parent d046ab888c
commit 94af997d40
4 changed files with 812 additions and 0 deletions

View file

@ -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 =

View file

@ -0,0 +1,175 @@
From 4d5e59c54a805ba4e7311fe58c9adc492ca1b35a Mon Sep 17 00:00:00 2001
From: Alexander Volkov <a.volkov@rusbitech.ru>
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 <gatis.paeglis@qt.io>
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<QSystemTrayIconPrivate *>(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

View file

@ -0,0 +1,139 @@
From 9aced4f9571e74cc57b853598aa4b3f38d66363d Mon Sep 17 00:00:00 2001
From: Johan Klokkhammer Helsing <johan.helsing@qt.io>
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 <giulio.camuffo@kdab.com>
Reviewed-by: David Edmundson <davidedmundson@kde.org>
---
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<QWaylandBuffer> &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

View file

@ -0,0 +1,493 @@
From d85beeb65820d6e5733f88af63b15f77d03aa6ba Mon Sep 17 00:00:00 2001
From: Johan Klokkhammer Helsing <johan.helsing@qt.io>
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 <paul.tvete@qt.io>
---
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 <QtWaylandClient/private/qwayland-text-input-unstable-v2.h>
+#include <QtCore/private/qcore_unix_p.h>
+
#include <QtCore/QAbstractEventDispatcher>
#include <QtGui/private/qguiapplication_p.h>
@@ -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<bool ()> 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<bool()> 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 <QtGui/private/qwindow_p.h>
#include <QtCore/QDebug>
+#include <QtCore/QThread>
#include <wayland-client.h>
@@ -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<QWaylandWindow*>(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<QWaylandWindow*>(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<wl_proxy *>(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 &region);
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