diff options
Diffstat (limited to 'src/client/qwaylanddisplay.cpp')
-rw-r--r-- | src/client/qwaylanddisplay.cpp | 907 |
1 files changed, 647 insertions, 260 deletions
diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index e759c5d64..e6ed0772f 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwaylanddisplay_p.h" @@ -60,6 +24,9 @@ #include <wayland-cursor.h> #endif #include "qwaylandhardwareintegration_p.h" +#include "qwaylandtextinputv1_p.h" +#include "qwaylandtextinputv2_p.h" +#include "qwaylandtextinputv3_p.h" #include "qwaylandinputcontext_p.h" #include "qwaylandinputmethodcontext_p.h" @@ -68,14 +35,23 @@ #include "qwaylandclientbufferintegration_p.h" #include "qwaylandextendedsurface_p.h" +#include "qwaylandpointergestures_p.h" #include "qwaylandsubsurface_p.h" #include "qwaylandtouch_p.h" +#if QT_CONFIG(tabletevent) #include "qwaylandtabletv2_p.h" +#endif #include "qwaylandqtkey_p.h" +#include <QtWaylandClient/private/qwayland-text-input-unstable-v1.h> #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h> +#include <QtWaylandClient/private/qwayland-text-input-unstable-v3.h> #include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h> #include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h> +#include <QtWaylandClient/private/qwayland-fractional-scale-v1.h> +#include <QtWaylandClient/private/qwayland-viewporter.h> +#include <QtWaylandClient/private/qwayland-cursor-shape-v1.h> +#include <QtWaylandClient/private/qwayland-xdg-toplevel-drag-v1.h> #include <QtCore/private/qcore_unix_p.h> @@ -93,18 +69,205 @@ 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. + */ + if (m_quitting) + return; + + for (;;) { + if (dispatchQueuePending() < 0) { + Q_EMIT waylandError(); + m_quitting = true; + 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(); + void waylandError(); + +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()) { + if (!m_reading.loadRelaxed()) + break; + + 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) { - struct wl_surface *surface = mCompositor.create_surface(); + struct wl_surface *surface = mGlobals.compositor->create_surface(); wl_surface_set_user_data(surface, handle); return surface; } struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) { - struct ::wl_region *region = mCompositor.create_region(); + struct ::wl_region *region = mGlobals.compositor->create_region(); for (const QRect &rect : qregion) wl_region_add(region, rect.x(), rect.y(), rect.width(), rect.height()); @@ -114,7 +277,7 @@ struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) ::wl_subsurface *QWaylandDisplay::createSubSurface(QWaylandWindow *window, QWaylandWindow *parent) { - if (!mSubCompositor) { + if (!mGlobals.subCompositor) { qCWarning(lcQpaWayland) << "Can't create subsurface, not supported by the compositor."; return nullptr; } @@ -123,7 +286,18 @@ struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) Q_ASSERT(parent->wlSurface()); Q_ASSERT(window->wlSurface()); - return mSubCompositor->get_subsurface(window->wlSurface(), parent->wlSurface()); + return mGlobals.subCompositor->get_subsurface(window->wlSurface(), parent->wlSurface()); +} + +::wp_viewport *QWaylandDisplay::createViewport(QWaylandWindow *window) +{ + if (!mGlobals.viewporter) { + qCWarning(lcQpaWayland) << "Can't create wp_viewport, not supported by the compositor."; + return nullptr; + } + + Q_ASSERT(window->wlSurface()); + return mGlobals.viewporter->get_viewport(window->wlSurface()); } QWaylandShellIntegration *QWaylandDisplay::shellIntegration() const @@ -138,7 +312,7 @@ QWaylandClientBufferIntegration * QWaylandDisplay::clientBufferIntegration() con QWaylandWindowManagerIntegration *QWaylandDisplay::windowManagerIntegration() const { - return mWindowManagerIntegration.data(); + return mGlobals.windowManagerIntegration.get(); } QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) @@ -147,52 +321,85 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) qRegisterMetaType<uint32_t>("uint32_t"); mDisplay = wl_display_connect(nullptr); - if (!mDisplay) { + if (mDisplay) { + setupConnection(); + } else { qErrnoWarning(errno, "Failed to create wl_display"); - return; } + mWaylandTryReconnect = qEnvironmentVariableIsSet("QT_WAYLAND_RECONNECT"); +} + +void QWaylandDisplay::setupConnection() +{ struct ::wl_registry *registry = wl_display_get_registry(mDisplay); init(registry); - mWindowManagerIntegration.reset(new QWaylandWindowManagerIntegration(this)); - #if QT_CONFIG(xkbcommon) mXkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); 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(); - } + if (mWaylandInputContextRequested) + checkTextInputProtocol(); } QWaylandDisplay::~QWaylandDisplay(void) { + if (m_eventThread) + m_eventThread->stop(); + + if (m_frameEventQueueThread) + m_frameEventQueueThread->stop(); + if (mSyncCallback) wl_callback_destroy(mSyncCallback); - qDeleteAll(qExchange(mInputDevices, {})); + qDeleteAll(std::exchange(mInputDevices, {})); - for (QWaylandScreen *screen : qExchange(mScreens, {})) { + for (QWaylandScreen *screen : std::exchange(mScreens, {})) { QWindowSystemInterface::handleScreenRemoved(screen); } qDeleteAll(mWaitingScreens); -#if QT_CONFIG(wayland_datadevice) - delete mDndSelectionHandler.take(); -#endif #if QT_CONFIG(cursor) mCursorThemes.clear(); #endif + + if (m_frameEventQueue) + wl_event_queue_destroy(m_frameEventQueue); + + // Reset the globals manually since they need to be destroyed before the wl_display + mGlobals = {}; + + if (object()) + wl_registry_destroy(object()); + if (mDisplay) 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. +bool QWaylandDisplay::initialize() +{ + if (!isInitialized()) + return false; + + forceRoundTrip(); + + emit connected(); + + if (!mWaitingScreens.isEmpty()) { + // Give wl_output.done and zxdg_output_v1.done events a chance to arrive + forceRoundTrip(); + } + if (mWaylandInputContextRequested) + mTextInputManagerIndex = INT_MAX; + + return qEnvironmentVariableIntValue("QT_WAYLAND_DONT_CHECK_SHELL_INTEGRATION") || shellIntegration(); +} + void QWaylandDisplay::ensureScreen() { if (!mScreens.empty() || mPlaceholderScreen) @@ -205,104 +412,183 @@ void QWaylandDisplay::ensureScreen() Q_ASSERT(!QGuiApplication::screens().empty()); } -void QWaylandDisplay::checkError() const +void QWaylandDisplay::reconnect() { - int ecode = wl_display_get_error(mDisplay); - if ((ecode == EPIPE || ecode == ECONNRESET)) { - // special case this to provide a nicer error - qFatal("The Wayland connection broke. Did the Wayland compositor die?"); - } else { - qFatal("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + qCWarning(lcQpaWayland) << "Attempting wayland reconnect"; + m_eventThread->stop(); + m_frameEventQueueThread->stop(); + m_eventThread->wait(); + m_frameEventQueueThread->wait(); + + qDeleteAll(mWaitingScreens); + mWaitingScreens.clear(); + + while (!mScreens.isEmpty()) { + auto screen = mScreens.takeLast(); + ensureScreen(); + QWindowSystemInterface::handleScreenRemoved(screen); } -} -void QWaylandDisplay::flushRequests() -{ - if (wl_display_prepare_read(mDisplay) == 0) { - wl_display_read_events(mDisplay); + mCursorThemes.clear(); + mCursor.reset(); + + mGlobals = GlobalHolder(); + + mWaylandIntegration->reset(); + + qDeleteAll(std::exchange(mInputDevices, {})); + mLastInputDevice = nullptr; + + for (const RegistryGlobal &global : mRegistryGlobals) { + emit globalRemoved(global); } + mRegistryGlobals.clear(); - if (wl_display_dispatch_pending(mDisplay) < 0) - checkError(); + mLastInputSerial = 0; + mLastInputWindow.clear(); + mLastKeyboardFocus.clear(); + mActiveWindows.clear(); - { - 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); + const auto windows = QGuiApplication::allWindows(); + for (auto window : windows) { + if (auto waylandWindow = static_cast<QWaylandWindow *>(window->handle())) + waylandWindow->closeChildPopups(); + } + // Remove windows that do not need to be recreated and now closed popups + QList<QWaylandWindow *> recreateWindows; + for (auto window : std::as_const(windows)) { + auto waylandWindow = static_cast<QWaylandWindow*>(window->handle()); + if (waylandWindow && waylandWindow->wlSurface()) { + waylandWindow->reset(); + recreateWindows.push_back(waylandWindow); } } - wl_display_flush(mDisplay); -} + if (mSyncCallback) { + wl_callback_destroy(mSyncCallback); + mSyncCallback = nullptr; + } -void QWaylandDisplay::blockingReadEvents() -{ - if (wl_display_dispatch(mDisplay) < 0) - checkError(); + if (object()) + wl_registry_destroy(object()); + mDisplay = wl_display_connect(nullptr); + if (!mDisplay) + _exit(1); + + setupConnection(); + initialize(); + + if (m_frameEventQueue) + wl_event_queue_destroy(m_frameEventQueue); + initEventThread(); + + auto needsRecreate = [](QPlatformWindow *window) { + return window && !static_cast<QWaylandWindow *>(window)->wlSurface(); + }; + auto window = recreateWindows.begin(); + while (!recreateWindows.isEmpty()) { + if (!needsRecreate((*window)->QPlatformWindow::parent()) && !needsRecreate((*window)->transientParent())) { + (*window)->reinit(); + window = recreateWindows.erase(window); + } else { + ++window; + } + if (window == recreateWindows.end()) + window = recreateWindows.begin(); + } + + mWaylandIntegration->reconfigureInputContext(); } -void QWaylandDisplay::destroyFrameQueue(const QWaylandDisplay::FrameQueue &q) +void QWaylandDisplay::flushRequests() { - 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); + connect(m_eventThread.get(), &EventThread::waylandError, this, + &QWaylandDisplay::checkWaylandError, Qt::QueuedConnection); + m_eventThread->start(); + + // 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(); } -wl_event_queue *QWaylandDisplay::createEventQueue() +void QWaylandDisplay::checkWaylandError() { - return wl_display_create_queue(mDisplay); + int ecode = wl_display_get_error(mDisplay); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + if (mWaylandTryReconnect) { + reconnect(); + return; + } + } else { + qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + } + _exit(-1); } -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); + if (wl_display_dispatch(mDisplay) < 0) { + int ecode = wl_display_get_error(mDisplay); + if ((ecode == EPIPE || ecode == ECONNRESET)) + qWarning("The Wayland connection broke during blocking read event. Did the Wayland compositor die?"); else - wl_display_cancel_read(mDisplay); - - if (wl_display_dispatch_queue_pending(mDisplay, queue) < 0) - checkError(); + qWarning("The Wayland connection experienced a fatal error during blocking read event: %s", strerror(ecode)); + _exit(-1); + } +} - if (!condition()) - break; +void QWaylandDisplay::checkTextInputProtocol() +{ + QStringList tips, timps; // for text input protocols and text input manager protocols + // zwp_text_input_v2 is preferred over zwp_text_input_v3 because: + // - Currently, v3 is not as feature rich as v2. + // - While v2 is not upstreamed, it is well supported by KWin since Plasma 5 and Plasma + // Mobile uses some v2 only. + tips << QLatin1String(QtWayland::qt_text_input_method_v1::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_v2::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_v3::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_v1::interface()->name); + timps << QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_manager_v3::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name); + + QString tiProtocols = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL")); + qCDebug(lcQpaWayland) << "QT_WAYLAND_TEXT_INPUT_PROTOCOL=" << tiProtocols; + QStringList keys; + if (!tiProtocols.isEmpty()) { + keys = tiProtocols.split(QLatin1Char(';')); + QList<QString>::iterator it = keys.begin(); + while (it != keys.end()) { + if (tips.contains(*it)) + mTextInputManagerList.append(timps.at(tips.indexOf(*it))); + else + qCDebug(lcQpaWayland) << "text input: unknown protocol - " << *it; + ++it; + } } + if (mTextInputManagerList.isEmpty()) // fallback + mTextInputManagerList = timps; } QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const { - for (auto screen : qAsConst(mScreens)) { + for (auto screen : std::as_const(mScreens)) { if (screen->output() == output) return screen; } @@ -322,83 +608,183 @@ void QWaylandDisplay::handleScreenInitialized(QWaylandScreen *screen) } } -void QWaylandDisplay::waitForScreens() +template <typename T, auto f> +struct WithDestructor : public T { - flushRequests(); - - while (true) { - bool screensReady = !mScreens.isEmpty(); - - for (int ii = 0; screensReady && ii < mScreens.count(); ++ii) { - if (mScreens.at(ii)->geometry() == QRect(0, 0, 0, 0)) - screensReady = false; - } - - if (!screensReady) - blockingReadEvents(); - else - return; + using T::T; + ~WithDestructor() + { + f(this->object()); } -} +}; void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uint32_t version) { struct ::wl_registry *registry = object(); - if (interface == QStringLiteral("wl_output")) { - mWaitingScreens << new QWaylandScreen(this, version, id); - } else if (interface == QStringLiteral("wl_compositor")) { - mCompositorVersion = qMin((int)version, 4); - mCompositor.init(registry, id, mCompositorVersion); - } else if (interface == QStringLiteral("wl_shm")) { - mShm.reset(new QWaylandShm(this, version, id)); - } else if (interface == QStringLiteral("wl_seat")) { + static QStringList interfaceBlacklist = qEnvironmentVariable("QT_WAYLAND_DISABLED_INTERFACES").split(u','); + if (interfaceBlacklist.contains(interface)) { + return; + } + + if (interface == QLatin1String(QtWayland::wl_output::interface()->name)) { + mWaitingScreens << mWaylandIntegration->createPlatformScreen(this, version, id); + } else if (interface == QLatin1String(QtWayland::wl_compositor::interface()->name)) { + mGlobals.compositor.reset( + new WithDestructor<QtWayland::wl_compositor, wl_compositor_destroy>( + registry, id, qMin((int)version, 6))); + } else if (interface == QLatin1String(QWaylandShm::interface()->name)) { + mGlobals.shm.reset(new QWaylandShm(this, version, id)); + } else if (interface == QLatin1String(QWaylandInputDevice::interface()->name)) { QWaylandInputDevice *inputDevice = mWaylandIntegration->createInputDevice(this, version, id); mInputDevices.append(inputDevice); #if QT_CONFIG(wayland_datadevice) - } else if (interface == QStringLiteral("wl_data_device_manager")) { - mDndSelectionHandler.reset(new QWaylandDataDeviceManager(this, id)); + } else if (interface == QLatin1String(QWaylandDataDeviceManager::interface()->name)) { + mGlobals.dndSelectionHandler.reset(new QWaylandDataDeviceManager(this, version, id)); +#endif + } else if (interface == QLatin1String(QtWayland::qt_surface_extension::interface()->name)) { + mGlobals.surfaceExtension.reset( + new WithDestructor<QtWayland::qt_surface_extension, qt_surface_extension_destroy>( + registry, id, 1)); + } else if (interface == QLatin1String(QtWayland::wl_subcompositor::interface()->name)) { + mGlobals.subCompositor.reset( + new WithDestructor<QtWayland::wl_subcompositor, wl_subcompositor_destroy>(registry, + id, 1)); + } else if (interface == QLatin1String(QWaylandTouchExtension::interface()->name)) { + mGlobals.touchExtension.reset(new QWaylandTouchExtension(this, id)); + } else if (interface == QLatin1String(QWaylandQtKeyExtension::interface()->name)) { + mGlobals.qtKeyExtension.reset(new QWaylandQtKeyExtension(this, id)); +#if QT_CONFIG(tabletevent) + } else if (interface == QLatin1String(QWaylandTabletManagerV2::interface()->name)) { + mGlobals.tabletManager.reset(new QWaylandTabletManagerV2(this, id, qMin(1, int(version)))); #endif - } else if (interface == QStringLiteral("qt_surface_extension")) { - mWindowExtension.reset(new QtWayland::qt_surface_extension(registry, id, 1)); - } else if (interface == QStringLiteral("wl_subcompositor")) { - mSubCompositor.reset(new QtWayland::wl_subcompositor(registry, id, 1)); - } else if (interface == QStringLiteral("qt_touch_extension")) { - mTouchExtension.reset(new QWaylandTouchExtension(this, id)); - } else if (interface == QStringLiteral("zqt_key_v1")) { - mQtKeyExtension.reset(new QWaylandQtKeyExtension(this, id)); - } else if (interface == QStringLiteral("zwp_tablet_manager_v2")) { - mTabletManager.reset(new QWaylandTabletManagerV2(this, id, qMin(1, int(version)))); + } else if (interface == QLatin1String(QWaylandPointerGestures::interface()->name)) { + mGlobals.pointerGestures.reset(new QWaylandPointerGestures(this, id, 1)); #if QT_CONFIG(wayland_client_primary_selection) - } else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { - mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); + } else if (interface == QLatin1String(QWaylandPrimarySelectionDeviceManagerV1::interface()->name)) { + mGlobals.primarySelectionManager.reset( + new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setPrimarySelectionDevice( + mGlobals.primarySelectionManager->createDevice(inputDevice)); #endif - } else if (interface == QStringLiteral("qt_text_input_method_manager_v1") && !mClientSideInputContextRequested) { - mTextInputMethodManager.reset(new QtWayland::qt_text_input_method_manager_v1(registry, id, 1)); - for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) - inputDevice->setTextInputMethod(new QWaylandTextInputMethod(this, mTextInputMethodManager->get_text_input_method(inputDevice->wl_seat()))); + } else if (interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register qt_text_input_method_manager_v1"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputManagerv1.reset(); + mGlobals.textInputManagerv2.reset(); + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(nullptr); + } + + mGlobals.textInputMethodManager.reset( + new WithDestructor<QtWayland::qt_text_input_method_manager_v1, + qt_text_input_method_manager_v1_destroy>(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(new QWaylandTextInputMethod( + this, + mGlobals.textInputMethodManager->get_text_input_method( + inputDevice->wl_seat()))); + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v1"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputMethodManager.reset(); + mGlobals.textInputManagerv2.reset(); + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + + mGlobals.textInputManagerv1.reset( + new WithDestructor<QtWayland::zwp_text_input_manager_v1, + zwp_text_input_manager_v1_destroy>(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) { + auto textInput = + new QWaylandTextInputv1(this, mGlobals.textInputManagerv1->create_text_input()); + textInput->setSeat(inputDevice->wl_seat()); + inputDevice->setTextInput(textInput); + } + + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v2"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputMethodManager.reset(); + mGlobals.textInputManagerv1.reset(); + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + + mGlobals.textInputManagerv2.reset( + new WithDestructor<QtWayland::zwp_text_input_manager_v2, + zwp_text_input_manager_v2_destroy>(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(new QWaylandTextInputv2( + this, mGlobals.textInputManagerv2->get_text_input(inputDevice->wl_seat()))); mWaylandIntegration->reconfigureInputContext(); - } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { - mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); - for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) - inputDevice->setTextInput(new QWaylandTextInput(this, mTextInputManager->get_text_input(inputDevice->wl_seat()))); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v3::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v3"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputMethodManager.reset(); + mGlobals.textInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + mGlobals.textInputManagerv3.reset( + new WithDestructor<QtWayland::zwp_text_input_manager_v3, + zwp_text_input_manager_v3_destroy>(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(new QWaylandTextInputv3( + this, mGlobals.textInputManagerv3->get_text_input(inputDevice->wl_seat()))); + mWaylandIntegration->reconfigureInputContext(); - } else if (interface == QStringLiteral("qt_hardware_integration")) { + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + }else if (interface == QLatin1String(QWaylandHardwareIntegration::interface()->name)) { bool disableHardwareIntegration = qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_HW_INTEGRATION"); if (!disableHardwareIntegration) { - mHardwareIntegration.reset(new QWaylandHardwareIntegration(registry, id)); + mGlobals.hardwareIntegration.reset(new QWaylandHardwareIntegration(registry, id)); // make a roundtrip here since we need to receive the events sent by // qt_hardware_integration before creating windows forceRoundTrip(); } - } else if (interface == QLatin1String("zxdg_output_manager_v1")) { - mXdgOutputManager.reset(new QWaylandXdgOutputManagerV1(this, id, version)); - for (auto *screen : qAsConst(mWaitingScreens)) + } else if (interface == QLatin1String(QWaylandXdgOutputManagerV1::interface()->name)) { + mGlobals.xdgOutputManager.reset(new QWaylandXdgOutputManagerV1(this, id, version)); + for (auto *screen : std::as_const(mWaitingScreens)) screen->initXdgOutput(xdgOutputManager()); - forceRoundTrip(); + } else if (interface == QLatin1String(QtWayland::wp_fractional_scale_manager_v1::interface()->name)) { + mGlobals.fractionalScaleManager.reset( + new WithDestructor<QtWayland::wp_fractional_scale_manager_v1, + wp_fractional_scale_manager_v1_destroy>(registry, id, 1)); + } else if (interface == QLatin1String("wp_viewporter")) { + mGlobals.viewporter.reset( + new WithDestructor<QtWayland::wp_viewporter, wp_viewporter_destroy>( + registry, id, qMin(1u, version))); + } else if (interface == QLatin1String(QtWayland::wp_cursor_shape_manager_v1::interface()->name)) { + mGlobals.cursorShapeManager.reset(new WithDestructor<QtWayland::wp_cursor_shape_manager_v1, + wp_cursor_shape_manager_v1_destroy>( + registry, id, std::min(1u, version))); + } else if ( + interface == QLatin1String(QtWayland::xdg_toplevel_drag_manager_v1::interface()->name)) { + mGlobals.xdgToplevelDragManager.reset( + new WithDestructor<QtWayland::xdg_toplevel_drag_manager_v1, + xdg_toplevel_drag_manager_v1_destroy>(registry, id, 1)); + } else if (interface == QLatin1String(QtWayland::qt_windowmanager::interface()->name)) { + mGlobals.windowManagerIntegration.reset( + new QWaylandWindowManagerIntegration(this, id, version)); } - mGlobals.append(RegistryGlobal(id, interface, version, registry)); + mRegistryGlobals.append(RegistryGlobal(id, interface, version, registry)); + emit globalAdded(mRegistryGlobals.back()); const auto copy = mRegistryListeners; // be prepared for listeners unregistering on notification for (Listener l : copy) @@ -407,10 +793,10 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin void QWaylandDisplay::registry_global_remove(uint32_t id) { - for (int i = 0, ie = mGlobals.count(); i != ie; ++i) { - RegistryGlobal &global = mGlobals[i]; + for (int i = 0, ie = mRegistryGlobals.size(); i != ie; ++i) { + RegistryGlobal &global = mRegistryGlobals[i]; if (global.id == id) { - if (global.interface == QStringLiteral("wl_output")) { + if (global.interface == QLatin1String(QtWayland::wl_output::interface()->name)) { for (auto *screen : mWaitingScreens) { if (screen->outputId() == id) { mWaitingScreens.removeOne(screen); @@ -419,7 +805,7 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) } } - for (QWaylandScreen *screen : qAsConst(mScreens)) { + for (QWaylandScreen *screen : std::as_const(mScreens)) { if (screen->outputId() == id) { mScreens.removeOne(screen); // If this is the last screen, we have to add a fake screen, or Qt will break. @@ -429,19 +815,38 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) } } } - if (global.interface == QStringLiteral("zwp_text_input_manager_v2")) { - mTextInputManager.reset(); - for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name)) { + mGlobals.textInputManagerv1.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) inputDevice->setTextInput(nullptr); mWaylandIntegration->reconfigureInputContext(); } - if (global.interface == QStringLiteral("qt_text_input_method_manager_v1")) { - mTextInputMethodManager.reset(); - for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name)) { + mGlobals.textInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v3::interface()->name)) { + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } + if (global.interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name)) { + mGlobals.textInputMethodManager.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) inputDevice->setTextInputMethod(nullptr); mWaylandIntegration->reconfigureInputContext(); } - mGlobals.removeAt(i); +#if QT_CONFIG(wayland_client_primary_selection) + if (global.interface == QLatin1String(QtWayland::zwp_primary_selection_device_manager_v1::interface()->name)) { + mGlobals.primarySelectionManager.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setPrimarySelectionDevice(nullptr); + } +#endif + emit globalRemoved(mRegistryGlobals.takeAt(i)); break; } } @@ -449,7 +854,7 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) bool QWaylandDisplay::hasRegistryGlobal(QStringView interfaceName) const { - for (const RegistryGlobal &global : mGlobals) + for (const RegistryGlobal &global : mRegistryGlobals) if (global.interface == interfaceName) return true; @@ -460,71 +865,22 @@ void QWaylandDisplay::addRegistryListener(RegistryListener listener, void *data) { Listener l = { listener, data }; mRegistryListeners.append(l); - for (int i = 0, ie = mGlobals.count(); i != ie; ++i) - (*l.listener)(l.data, mGlobals[i].registry, mGlobals[i].id, mGlobals[i].interface, mGlobals[i].version); + for (int i = 0, ie = mRegistryGlobals.size(); i != ie; ++i) + (*l.listener)(l.data, mRegistryGlobals[i].registry, mRegistryGlobals[i].id, + mRegistryGlobals[i].interface, mRegistryGlobals[i].version); } void QWaylandDisplay::removeListener(RegistryListener listener, void *data) { - std::remove_if(mRegistryListeners.begin(), mRegistryListeners.end(), [=](Listener l){ + auto iter = std::remove_if(mRegistryListeners.begin(), mRegistryListeners.end(), [=](Listener l){ return (l.listener == listener && l.data == data); }); + mRegistryListeners.erase(iter, mRegistryListeners.end()); } -uint32_t QWaylandDisplay::currentTimeMillisec() -{ - //### we throw away the time information - struct timeval tv; - int ret = gettimeofday(&tv, nullptr); - if (ret == 0) - return tv.tv_sec*1000 + tv.tv_usec/1000; - return 0; -} - -static void -sync_callback(void *data, struct wl_callback *callback, uint32_t serial) -{ - Q_UNUSED(serial); - bool *done = static_cast<bool *>(data); - - *done = true; - - // If the wl_callback done event is received after the condition check in the while loop in - // forceRoundTrip(), but before the call to processEvents, the call to processEvents may block - // forever if no more events are posted (eventhough the callback is handled in response to the - // aboutToBlock signal). Hence, we wake up the event dispatcher so forceRoundTrip may return. - // (QTBUG-64696) - if (auto *dispatcher = QThread::currentThread()->eventDispatcher()) - dispatcher->wakeUp(); - - wl_callback_destroy(callback); -} - -static const struct wl_callback_listener sync_listener = { - sync_callback -}; - void QWaylandDisplay::forceRoundTrip() { - // wl_display_roundtrip() works on the main queue only, - // but we use a separate one, so basically reimplement it here - int ret = 0; - bool done = false; - wl_callback *callback = wl_display_sync(mDisplay); - wl_callback_add_listener(callback, &sync_listener, &done); - flushRequests(); - if (QThread::currentThread()->eventDispatcher()) { - while (!done && ret >= 0) { - QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::WaitForMoreEvents); - ret = wl_display_dispatch_pending(mDisplay); - } - } else { - while (!done && ret >= 0) - ret = wl_display_dispatch(mDisplay); - } - - if (ret == -1 && !done) - wl_callback_destroy(callback); + wl_display_roundtrip(mDisplay); } bool QWaylandDisplay::supportsWindowDecoration() const @@ -535,7 +891,12 @@ bool QWaylandDisplay::supportsWindowDecoration() const if (disabled) return false; - static bool integrationSupport = clientBufferIntegration() && clientBufferIntegration()->supportsWindowDecoration(); + // Don't initialize client buffer integration just to check whether it can have a decoration. + if (!mWaylandIntegration->mClientBufferIntegrationInitialized) + return true; + + // We can do software-rendered decorations, only disable them if the integration explicitly says it can't. + static bool integrationSupport = !clientBufferIntegration() || clientBufferIntegration()->supportsWindowDecoration(); return integrationSupport; } @@ -577,6 +938,9 @@ void QWaylandDisplay::handleWindowDeactivated(QWaylandWindow *window) mActiveWindows.removeOne(window); + if (QCoreApplication::closingDown()) + return; + if (auto *decoration = window->decoration()) decoration->update(); } @@ -588,14 +952,10 @@ void QWaylandDisplay::handleKeyboardFocusChanged(QWaylandInputDevice *inputDevic if (mLastKeyboardFocus == keyboardFocus) return; - if (mWaylandIntegration->mShellIntegration) { - mWaylandIntegration->mShellIntegration->handleKeyboardFocusChanged(keyboardFocus, mLastKeyboardFocus); - } else { - if (keyboardFocus) - handleWindowActivated(keyboardFocus); - if (mLastKeyboardFocus) - handleWindowDeactivated(mLastKeyboardFocus); - } + if (keyboardFocus) + handleWindowActivated(keyboardFocus); + if (mLastKeyboardFocus) + handleWindowDeactivated(mLastKeyboardFocus); mLastKeyboardFocus = keyboardFocus; } @@ -613,7 +973,20 @@ void QWaylandDisplay::handleWaylandSync() // handleWindowActivated() calls immediately. QWindow *activeWindow = mActiveWindows.empty() ? nullptr : mActiveWindows.last()->window(); if (activeWindow != QGuiApplication::focusWindow()) - QWindowSystemInterface::handleWindowActivated(activeWindow); + QWindowSystemInterface::handleFocusWindowChanged(activeWindow); + + if (!activeWindow) { + if (lastInputDevice()) { +#if QT_CONFIG(clipboard) + if (auto *dataDevice = lastInputDevice()->dataDevice()) + dataDevice->invalidateSelectionOffer(); +#endif +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *device = lastInputDevice()->primarySelectionDevice()) + device->invalidateSelectionOffer(); +#endif + } + } } const wl_callback_listener QWaylandDisplay::syncCallbackListener = { @@ -637,7 +1010,18 @@ void QWaylandDisplay::requestWaylandSync() QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const { - return mInputDevices.isEmpty() ? 0 : mInputDevices.first(); + return mInputDevices.isEmpty() ? nullptr : mInputDevices.first(); +} + +bool QWaylandDisplay::isKeyboardAvailable() const +{ + return std::any_of( + mInputDevices.constBegin(), mInputDevices.constEnd(), + [](const QWaylandInputDevice *device) { return device->keyboard() != nullptr; }); +} + +bool QWaylandDisplay::isWaylandInputContextRequested() const { + return mWaylandInputContextRequested; } #if QT_CONFIG(cursor) @@ -645,7 +1029,7 @@ QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const QWaylandCursor *QWaylandDisplay::waylandCursor() { if (!mCursor) - mCursor.reset(new QWaylandCursor(this)); + mCursor.reset(mWaylandIntegration->createPlatformCursor(this)); return mCursor.data(); } @@ -682,3 +1066,6 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int p } // namespace QtWaylandClient QT_END_NAMESPACE + +#include "qwaylanddisplay.moc" +#include "moc_qwaylanddisplay_p.cpp" |