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 +#include // for std::tie + +static void checkWaylandError(struct wl_display *display) +{ + int ecode = wl_display_get_error(display); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + // special case this to provide a nicer error + qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + } else { + qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + } + _exit(1); +} + QT_BEGIN_NAMESPACE namespace QtWaylandClient { +class EventThread : public QThread +{ + Q_OBJECT +public: + enum OperatingMode { + EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread. + SelfDispatch, // Dispatch the events inside this thread. + }; + + EventThread(struct wl_display * wl, struct wl_event_queue * ev_queue, + OperatingMode mode) + : m_fd(wl_display_get_fd(wl)) + , m_pipefd{ -1, -1 } + , m_wldisplay(wl) + , m_wlevqueue(ev_queue) + , m_mode(mode) + , m_reading(true) + , m_quitting(false) + { + setObjectName(QStringLiteral("WaylandEventThread")); + } + + void readAndDispatchEvents() + { + /* + * Dispatch pending events and flush the requests at least once. If the event thread + * is not reading, try to call _prepare_read() to allow the event thread to poll(). + * If that fails, re-try dispatch & flush again until _prepare_read() is successful. + * + * This allow any call to readAndDispatchEvents() to start event thread's polling, + * not only the one issued from event thread's waitForReading(), which means functions + * called from dispatch_pending() can safely spin an event loop. + */ + for (;;) { + if (dispatchQueuePending() < 0) { + checkWaylandError(m_wldisplay); + return; + } + + wl_display_flush(m_wldisplay); + + // We have to check if event thread is reading every time we dispatch + // something, as that may recursively call this function. + if (m_reading.loadAcquire()) + break; + + if (prepareReadQueue() == 0) { + QMutexLocker l(&m_mutex); + m_reading.storeRelease(true); + m_cond.wakeOne(); + break; + } + } + } + + void stop() + { + // We have to both write to the pipe and set the flag, as the thread may be + // either in the poll() or waiting for _prepare_read(). + if (m_pipefd[1] != -1 && write(m_pipefd[1], "\0", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + { + QMutexLocker l(&m_mutex); + m_quitting = true; + m_cond.wakeOne(); + } + + wait(); + } + +Q_SIGNALS: + void needReadAndDispatch(); + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (qt_safe_pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the + // outbound ones. Wait until it's done before proceeding, unless we're told to quit. + while (waitForReading()) { + pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_pipefd[0], POLLIN, 0 } }; + poll(fds, 2, -1); + + if (fds[1].revents & POLLIN) { + // we don't really care to read the byte that was written here since we're closing down + wl_display_cancel_read(m_wldisplay); + break; + } + + if (fds[0].revents & POLLIN) + wl_display_read_events(m_wldisplay); + // The polll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop + // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which + // case we don't care anymore about them. + else + wl_display_cancel_read(m_wldisplay); + } + } + +private: + bool waitForReading() + { + Q_ASSERT(QThread::currentThread() == this); + + m_reading.storeRelease(false); + + if (m_mode == SelfDispatch) { + readAndDispatchEvents(); + } else { + Q_EMIT needReadAndDispatch(); + + QMutexLocker lock(&m_mutex); + // m_reading might be set from our emit or some other invocation of + // readAndDispatchEvents(). + while (!m_reading.loadRelaxed() && !m_quitting) + m_cond.wait(&m_mutex); + } + + return !m_quitting; + } + + int dispatchQueuePending() + { + if (m_wlevqueue) + return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue); + else + return wl_display_dispatch_pending(m_wldisplay); + } + + int prepareReadQueue() + { + if (m_wlevqueue) + return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue); + else + return wl_display_prepare_read(m_wldisplay); + } + + int m_fd; + int m_pipefd[2]; + wl_display *m_wldisplay; + wl_event_queue *m_wlevqueue; + OperatingMode m_mode; + + /* Concurrency note when operating in EmitToDispatch mode: + * m_reading is set to false inside event thread's waitForReading(), and is + * set to true inside main thread's readAndDispatchEvents(). + * The lock is not taken when setting m_reading to false, as the main thread + * is not actively waiting for it to turn false. However, the lock is taken + * inside readAndDispatchEvents() before setting m_reading to true, + * as the event thread is actively waiting for it under the wait condition. + */ + + QAtomicInteger m_reading; + bool m_quitting; + QMutex m_mutex; + QWaitCondition m_cond; +}; + Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland"); // for general (uncategorized) Wayland platform logging struct wl_surface *QWaylandDisplay::createSurface(void *handle) @@ -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 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 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 m_eventThread; + wl_event_queue *m_frameEventQueue = nullptr; + QScopedPointer m_frameEventQueueThread; QtWayland::wl_compositor mCompositor; QScopedPointer mShm; QList mWaitingScreens; @@ -274,11 +273,9 @@ private: QWaylandInputDevice *mLastInputDevice = nullptr; QPointer mLastInputWindow; QPointer mLastKeyboardFocus; - QVector mActiveWindows; - QVector mExternalQueues; + QList mActiveWindows; struct wl_callback *mSyncCallback = nullptr; static const wl_callback_listener syncCallbackListener; - QReadWriteLock m_frameQueueLock; bool mClientSideInputContextRequested = !QPlatformInputContextFactory::requested().isNull(); diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 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(wl_proxy_create_wrapper(mSurface->object())); - wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mFrameQueue.queue); + wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mDisplay->frameEventQueue()); mFrameCallback = wl_surface_frame(wrappedSurface); wl_proxy_wrapper_destroy(wrappedSurface); wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); @@ -1203,6 +1215,8 @@ void QWaylandWindow::handleUpdate() // Start a timer for handling the case when the compositor stops sending frame callbacks. if (mFrameCallbackTimeout > 0) { QMetaObject::invokeMethod(this, [this] { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) { if (mFrameCallbackCheckIntervalTimerId < 0) mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 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