summaryrefslogtreecommitdiffstats
path: root/source/l/qt5/patches/24.diff
diff options
context:
space:
mode:
Diffstat (limited to 'source/l/qt5/patches/24.diff')
-rw-r--r--source/l/qt5/patches/24.diff585
1 files changed, 585 insertions, 0 deletions
diff --git a/source/l/qt5/patches/24.diff b/source/l/qt5/patches/24.diff
new file mode 100644
index 000000000..5921881b7
--- /dev/null
+++ b/source/l/qt5/patches/24.diff
@@ -0,0 +1,585 @@
+diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp
+index ea344c61c3a4643f7c725a6287f20d742b210d24..a7ce280a5df538917758e50ba8d2ee117378d546 100644
+--- a/src/client/qwaylanddisplay.cpp
++++ b/src/client/qwaylanddisplay.cpp
+@@ -85,10 +85,203 @@
+
+ #include <errno.h>
+
++#include <tuple> // 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<bool> 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)
+@@ -158,17 +351,16 @@ 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)
+ {
++ if (m_eventThread)
++ m_eventThread->stop();
++
++ if (m_frameEventQueueThread)
++ m_frameEventQueueThread->stop();
++
+ if (mSyncCallback)
+ wl_callback_destroy(mSyncCallback);
+
+@@ -189,6 +381,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)
+@@ -203,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<bool ()> 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
+@@ -669,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 09a1736a267d2816873667e9f1ecb4f4892f0ed0..42bc661d3064d770aa9fde8bd62ecdbbc89732a2 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,15 +121,11 @@ 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;
+
++ void initialize();
++
+ #if QT_CONFIG(xkbcommon)
+ struct xkb_context *xkbContext() const { return mXkbContext.get(); }
+ #endif
+@@ -210,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<bool()> condition, int timeout = -1);
++ wl_event_queue *frameEventQueue() { return m_frameEventQueue; };
+
+ bool isKeyboardAvailable() const;
++
++ void initEventThread();
+ public slots:
+ void blockingReadEvents();
+ void flushRequests();
+@@ -238,6 +234,9 @@ private:
+ };
+
+ struct wl_display *mDisplay = nullptr;
++ QScopedPointer<EventThread> m_eventThread;
++ wl_event_queue *m_frameEventQueue = nullptr;
++ QScopedPointer<EventThread> m_frameEventQueueThread;
+ QtWayland::wl_compositor mCompositor;
+ QScopedPointer<QWaylandShm> mShm;
+ QList<QWaylandScreen *> mWaitingScreens;
+@@ -274,11 +273,9 @@ private:
+ QWaylandInputDevice *mLastInputDevice = nullptr;
+ QPointer<QWaylandWindow> mLastInputWindow;
+ QPointer<QWaylandWindow> mLastKeyboardFocus;
+- QVector<QWaylandWindow *> mActiveWindows;
+- QVector<FrameQueue> mExternalQueues;
++ QList<QWaylandWindow *> 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 e5e7dd42c9b0145f4c9852f7e15dcc83106c321d..3b876047293887d17eeb28819c7386ded9e1f131 100644
+--- a/src/client/qwaylandintegration.cpp
++++ b/src/client/qwaylandintegration.cpp
+@@ -192,14 +192,18 @@ QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const
+
+ void QWaylandIntegration::initialize()
+ {
++ mDisplay->initEventThread();
++
++ // 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()));
+
+- 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()));
+-
+ // Qt does not support running with no screens
+ mDisplay->ensureScreen();
+ }
+diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp
+index 1597f67e63ae7834ded50e25b0acf86b71abcd73..7de19a742b6d3f6a3ce0955f59a5bf2879d29c9e 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<struct ::wl_surface *>(wl_proxy_create_wrapper(mSurface->object()));
+- wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mFrameQueue.queue);
++ wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(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 e06879620c3d033f093b0866f018ec80a72a97c3..d45980a80e9ecc9c5003fa2144de63e6337bda8a 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