diff options
Diffstat (limited to 'src/client/qwaylandwindow.cpp')
-rw-r--r-- | src/client/qwaylandwindow.cpp | 1466 |
1 files changed, 1056 insertions, 410 deletions
diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index cecdbda92..081110f83 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -1,47 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the config.tests 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 "qwaylandwindow_p.h" #include "qwaylandbuffer_p.h" #include "qwaylanddisplay_p.h" +#include "qwaylandsurface_p.h" #include "qwaylandinputdevice_p.h" +#include "qwaylandfractionalscale_p.h" #include "qwaylandscreen_p.h" #include "qwaylandshellsurface_p.h" #include "qwaylandsubsurface_p.h" @@ -51,11 +17,7 @@ #include "qwaylanddecorationfactory_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandshellintegration_p.h" - -#if QT_CONFIG(wayland_datadevice) -#include "qwaylanddatadevice_p.h" -#endif - +#include "qwaylandviewport_p.h" #include <QtCore/QFileInfo> #include <QtCore/QPointer> @@ -64,48 +26,62 @@ #include <QGuiApplication> #include <qpa/qwindowsysteminterface.h> +#include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qwindow_p.h> #include <QtCore/QDebug> #include <QtCore/QThread> +#include <QtCore/private/qthread_p.h> -#include <wayland-client.h> +#include <QtWaylandClient/private/qwayland-fractional-scale-v1.h> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + namespace QtWaylandClient { Q_LOGGING_CATEGORY(lcWaylandBackingstore, "qt.qpa.wayland.backingstore") QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr; +QWaylandWindow *QWaylandWindow::mTopPopup = nullptr; -QWaylandWindow::QWaylandWindow(QWindow *window) +QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) : QPlatformWindow(window) - , mDisplay(waylandScreen()->display()) - , mFrameQueue(mDisplay->createEventQueue()) + , mDisplay(display) + , mSurfaceLock(QReadWriteLock::Recursive) + , mShellIntegration(display->shellIntegration()) , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) { + { + bool ok; + int frameCallbackTimeout = qEnvironmentVariableIntValue("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", &ok); + if (ok) + mFrameCallbackTimeout = frameCallbackTimeout; + } + + mScale = waylandScreen() ? waylandScreen()->scale() : 1; // fallback to 1 if we don't have a real screen + static WId id = 1; mWindowId = id++; - connect(qApp, &QGuiApplication::screenRemoved, this, &QWaylandWindow::handleScreenRemoved); initializeWlSurface(); + + connect(this, &QWaylandWindow::wlSurfaceCreated, this, + &QNativeInterface::Private::QWaylandWindow::surfaceCreated); + connect(this, &QWaylandWindow::wlSurfaceDestroyed, this, + &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed); } QWaylandWindow::~QWaylandWindow() { - mDisplay->handleWindowDestroyed(this); - delete mWindowDecoration; - if (isInitialized()) - reset(false); - - QList<QWaylandInputDevice *> inputDevices = mDisplay->inputDevices(); - for (int i = 0; i < inputDevices.size(); ++i) - inputDevices.at(i)->handleWindowDestroyed(this); + if (mSurface) + reset(); const QWindow *parent = window(); - foreach (QWindow *w, QGuiApplication::topLevelWindows()) { + const auto tlw = QGuiApplication::topLevelWindows(); + for (QWindow *w : tlw) { if (w->transientParent() == parent) QWindowSystemInterface::handleCloseEvent(w); } @@ -117,8 +93,10 @@ QWaylandWindow::~QWaylandWindow() void QWaylandWindow::ensureSize() { - if (mBackingStore) - mBackingStore->ensureSize(); + if (mBackingStore) { + setBackingStore(mBackingStore); + mBackingStore->recreateBackBufferIfNeeded(); + } } void QWaylandWindow::initWindow() @@ -126,53 +104,78 @@ void QWaylandWindow::initWindow() if (window()->type() == Qt::Desktop) return; - if (!isInitialized()) { + if (!mSurface) { initializeWlSurface(); - QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceCreated); - QGuiApplication::sendEvent(window(), &e); + } + + if (mDisplay->fractionalScaleManager() && qApp->highDpiScaleFactorRoundingPolicy() == Qt::HighDpiScaleFactorRoundingPolicy::PassThrough) { + mFractionalScale.reset(new QWaylandFractionalScale(mDisplay->fractionalScaleManager()->get_fractional_scale(mSurface->object()))); + + connect(mFractionalScale.data(), &QWaylandFractionalScale::preferredScaleChanged, + this, &QWaylandWindow::updateScale); } if (shouldCreateSubSurface()) { Q_ASSERT(!mSubSurfaceWindow); - QWaylandWindow *p = static_cast<QWaylandWindow *>(QPlatformWindow::parent()); - if (::wl_subsurface *ss = mDisplay->createSubSurface(this, p)) { - mSubSurfaceWindow = new QWaylandSubSurface(this, p, ss); + auto *parent = static_cast<QWaylandWindow *>(QPlatformWindow::parent()); + if (!parent->mSurface) + parent->initializeWlSurface(); + if (parent->wlSurface()) { + if (::wl_subsurface *subsurface = mDisplay->createSubSurface(this, parent)) + mSubSurfaceWindow = new QWaylandSubSurface(this, parent, subsurface); } } else if (shouldCreateShellSurface()) { Q_ASSERT(!mShellSurface); - Q_ASSERT(mDisplay->shellIntegration()); + Q_ASSERT(mShellIntegration); + mTransientParent = guessTransientParent(); + if (mTransientParent) { + if (window()->type() == Qt::Popup) { + if (mTopPopup && mTopPopup != mTransientParent) { + qCWarning(lcQpaWayland) << "Creating a popup with a parent," << mTransientParent->window() + << "which does not match the current topmost grabbing popup," + << mTopPopup->window() << "With some shell surface protocols, this" + << "is not allowed. The wayland QPA plugin is currently handling" + << "it by setting the parent to the topmost grabbing popup." + << "Note, however, that this may cause positioning errors and" + << "popups closing unxpectedly. Please fix the transient parent of the popup."; + mTransientParent = mTopPopup; + } + mTopPopup = this; + } + } - mShellSurface = mDisplay->shellIntegration()->createShellSurface(this); + mShellSurface = mShellIntegration->createShellSurface(this); if (mShellSurface) { + if (mTransientParent) { + if (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup) + mTransientParent->addChildPopup(this); + } + // Set initial surface title setWindowTitle(window()->title()); // The appId is the desktop entry identifier that should follow the - // reverse DNS convention (see http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html). - // According to xdg-shell the appId is only the name, without - // the .desktop suffix. + // reverse DNS convention (see + // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html). According + // to xdg-shell the appId is only the name, without the .desktop suffix. // - // If the application specifies the desktop file name use that - // removing the ".desktop" suffix, otherwise fall back to the - // executable name and prepend the reversed organization domain - // when available. + // If the application specifies the desktop file name use that, + // otherwise fall back to the executable name and prepend the + // reversed organization domain when available. if (!QGuiApplication::desktopFileName().isEmpty()) { - QString name = QGuiApplication::desktopFileName(); - if (name.endsWith(QLatin1String(".desktop"))) - name.chop(8); - mShellSurface->setAppId(name); + mShellSurface->setAppId(QGuiApplication::desktopFileName()); } else { - QFileInfo fi = QCoreApplication::instance()->applicationFilePath(); + QFileInfo fi = QFileInfo(QCoreApplication::instance()->applicationFilePath()); QStringList domainName = QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'), - QString::SkipEmptyParts); + Qt::SkipEmptyParts); if (domainName.isEmpty()) { mShellSurface->setAppId(fi.baseName()); } else { QString appId; - for (int i = 0; i < domainName.count(); ++i) + for (int i = 0; i < domainName.size(); ++i) appId.prepend(QLatin1Char('.')).prepend(domainName.at(i)); appId.append(fi.baseName()); mShellSurface->setAppId(appId); @@ -181,41 +184,75 @@ void QWaylandWindow::initWindow() // the user may have already set some window properties, so make sure to send them out for (auto it = m_properties.cbegin(); it != m_properties.cend(); ++it) mShellSurface->sendProperty(it.key(), it.value()); + + emit surfaceRoleCreated(); } else { qWarning("Could not create a shell surface object."); } } - mScale = waylandScreen()->scale(); + // The fractional scale manager check is needed to work around Gnome < 36 where viewports don't work + // Right now viewports are only necessary when a fractional scale manager is used + if (display()->viewporter() && display()->fractionalScaleManager()) { + mViewport.reset(new QWaylandViewport(display()->createViewport(this))); + } // Enable high-dpi rendering. Scale() returns the screen scale factor and will // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale() // to inform the compositor that high-resolution buffers will be provided. - if (mDisplay->compositorVersion() >= 3) - set_buffer_scale(scale()); + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(scale())); - if (QScreen *s = window()->screen()) - setOrientationMask(s->orientationUpdateMask()); setWindowFlags(window()->flags()); - if (window()->geometry().isEmpty()) - setGeometry_helper(QRect(QPoint(), QSize(500,500))); - else - setGeometry_helper(window()->geometry()); + QRect geometry = windowGeometry(); + QRect defaultGeometry = this->defaultGeometry(); + if (geometry.width() <= 0) + geometry.setWidth(defaultGeometry.width()); + if (geometry.height() <= 0) + geometry.setHeight(defaultGeometry.height()); + + setGeometry_helper(geometry); setMask(window()->mask()); if (mShellSurface) mShellSurface->requestWindowStates(window()->windowStates()); handleContentOrientationChange(window()->contentOrientation()); mFlags = window()->flags(); + + mSurface->commit(); } void QWaylandWindow::initializeWlSurface() { - init(mDisplay->createSurface(static_cast<QtWayland::wl_surface *>(this))); + Q_ASSERT(!mSurface); + { + QWriteLocker lock(&mSurfaceLock); + mSurface.reset(new QWaylandSurface(mDisplay)); + connect(mSurface.data(), &QWaylandSurface::screensChanged, + this, &QWaylandWindow::handleScreensChanged); + connect(mSurface.data(), &QWaylandSurface::preferredBufferScaleChanged, + this, &QWaylandWindow::updateScale); + connect(mSurface.data(), &QWaylandSurface::preferredBufferTransformChanged, + this, &QWaylandWindow::updateBufferTransform); + mSurface->m_window = this; + } + emit wlSurfaceCreated(); +} + +void QWaylandWindow::setShellIntegration(QWaylandShellIntegration *shellIntegration) +{ + Q_ASSERT(shellIntegration); + if (mShellSurface) { + qCWarning(lcQpaWayland) << "Cannot set shell integration while there's already a shell surface created"; + return; + } + mShellIntegration = shellIntegration; } bool QWaylandWindow::shouldCreateShellSurface() const { - if (!mDisplay->shellIntegration()) + if (!shellIntegration()) return false; if (shouldCreateSubSurface()) @@ -235,32 +272,82 @@ bool QWaylandWindow::shouldCreateSubSurface() const return QPlatformWindow::parent() != nullptr; } -void QWaylandWindow::reset(bool sendDestroyEvent) +void QWaylandWindow::beginFrame() +{ + mSurfaceLock.lockForRead(); +} + +void QWaylandWindow::endFrame() +{ + mSurfaceLock.unlock(); +} + +void QWaylandWindow::reset() { - if (isInitialized() && sendDestroyEvent) { - QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed); - QGuiApplication::sendEvent(window(), &e); + closeChildPopups(); + + if (mTopPopup == this) + mTopPopup = mTransientParent && (mTransientParent->window()->type() == Qt::Popup) ? mTransientParent : nullptr; + + if (mSurface) { + { + QWriteLocker lock(&mSurfaceLock); + invalidateSurface(); + if (mTransientParent) + mTransientParent->removeChildPopup(this); + delete mShellSurface; + mShellSurface = nullptr; + emit surfaceRoleDestroyed(); + delete mSubSurfaceWindow; + mSubSurfaceWindow = nullptr; + mTransientParent = nullptr; + mSurface.reset(); + mViewport.reset(); + mFractionalScale.reset(); + } + emit wlSurfaceDestroyed(); } - delete mShellSurface; - mShellSurface = nullptr; - delete mSubSurfaceWindow; - mSubSurfaceWindow = nullptr; - if (isInitialized()) - destroy(); - mScreens.clear(); - - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; + + { + QMutexLocker lock(&mFrameSyncMutex); + if (mFrameCallback) { + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + } + + mFrameCallbackElapsedTimer.invalidate(); + mWaitingForFrameCallback = false; + } + if (mFrameCallbackCheckIntervalTimerId != -1) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; } + mFrameCallbackTimedOut = false; + mWaitingToApplyConfigure = false; + mCanResize = true; + mResizeDirty = false; + + mOpaqueArea = QRegion(); mMask = QRegion(); + + mInputRegion = QRegion(); + mTransparentInputRegion = false; + + if (mQueuedBuffer) { + mQueuedBuffer->setBusy(false); + } mQueuedBuffer = nullptr; + mQueuedBufferDamage = QRegion(); + + mDisplay->handleWindowDestroyed(this); } QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface) { - return static_cast<QWaylandWindow *>(static_cast<QtWayland::wl_surface *>(wl_surface_get_user_data(surface))); + if (auto *s = QWaylandSurface::fromWlSurface(surface)) + return s->m_window; + return nullptr; } WId QWaylandWindow::winId() const @@ -287,25 +374,34 @@ void QWaylandWindow::setParent(const QPlatformWindow *parent) } } +QString QWaylandWindow::windowTitle() const +{ + return mWindowTitle; +} + void QWaylandWindow::setWindowTitle(const QString &title) { - if (mShellSurface) { - const QString separator = QString::fromUtf8(" \xe2\x80\x94 "); // unicode character U+2014, EM DASH - const QString formatted = formatWindowTitle(title, separator); + const QString separator = QString::fromUtf8(" \xe2\x80\x94 "); // unicode character U+2014, EM DASH + const QString formatted = formatWindowTitle(title, separator); - const int libwaylandMaxBufferSize = 4096; - // Some parts of the buffer is used for metadata, so subtract 100 to be on the safe side - const int maxLength = libwaylandMaxBufferSize - 100; + const int libwaylandMaxBufferSize = 4096; + // Some parts of the buffer is used for metadata, so subtract 100 to be on the safe side. + // Also, QString is in utf-16, which means that in the worst case each character will be + // three bytes when converted to utf-8 (which is what libwayland uses), so divide by three. + const int maxLength = libwaylandMaxBufferSize / 3 - 100; - auto truncated = QStringRef(&formatted).left(maxLength); - if (truncated.length() < formatted.length()) { - qCWarning(lcQpaWayland) << "Window titles longer than" << maxLength << "characters are not supported." - << "Truncating window title (from" << formatted.length() << "chars)"; - } - mShellSurface->setTitle(truncated.toString()); + auto truncated = QStringView{formatted}.left(maxLength); + if (truncated.size() < formatted.size()) { + qCWarning(lcQpaWayland) << "Window titles longer than" << maxLength << "characters are not supported." + << "Truncating window title (from" << formatted.size() << "chars)"; } - if (mWindowDecoration && window()->isVisible()) + mWindowTitle = truncated.toString(); + + if (mShellSurface) + mShellSurface->setTitle(mWindowTitle); + + if (mWindowDecorationEnabled && window()->isVisible()) mWindowDecoration->update(); } @@ -313,52 +409,137 @@ void QWaylandWindow::setWindowIcon(const QIcon &icon) { mWindowIcon = icon; - if (mWindowDecoration && window()->isVisible()) + if (mWindowDecorationEnabled && window()->isVisible()) mWindowDecoration->update(); } +QRect QWaylandWindow::defaultGeometry() const +{ + return QRect(QPoint(), QSize(500,500)); +} + void QWaylandWindow::setGeometry_helper(const QRect &rect) { - QPlatformWindow::setGeometry(QRect(rect.x(), rect.y(), - qBound(window()->minimumWidth(), rect.width(), window()->maximumWidth()), - qBound(window()->minimumHeight(), rect.height(), window()->maximumHeight()))); + QPlatformWindow::setGeometry(rect); + if (mViewport) + updateViewport(); if (mSubSurfaceWindow) { - QMargins m = QPlatformWindow::parent()->frameMargins(); + QMargins m = static_cast<QWaylandWindow *>(QPlatformWindow::parent())->clientSideMargins(); mSubSurfaceWindow->set_position(rect.x() + m.left(), rect.y() + m.top()); - mSubSurfaceWindow->parent()->window()->requestUpdate(); + + QWaylandWindow *parentWindow = mSubSurfaceWindow->parent(); + if (parentWindow && parentWindow->isExposed()) { + QRect parentExposeGeometry(QPoint(), parentWindow->geometry().size()); + parentWindow->sendExposeEvent(parentExposeGeometry); + } } } -void QWaylandWindow::setGeometry(const QRect &rect) +void QWaylandWindow::setGeometry(const QRect &r) { + auto rect = r; + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip) { + rect.moveTo(screen()->geometry().topLeft()); + } setGeometry_helper(rect); if (window()->isVisible() && rect.isValid()) { - if (mWindowDecoration) + if (mWindowDecorationEnabled) mWindowDecoration->update(); - if (mResizeAfterSwap && windowType() == Egl && mSentInitialResize) + if (mResizeAfterSwap && windowType() == Egl && mSentInitialResize) { + QMutexLocker lock(&mResizeLock); mResizeDirty = true; - else + } else { QWindowSystemInterface::handleGeometryChange(window(), geometry()); - + } mSentInitialResize = true; } QRect exposeGeometry(QPoint(), geometry().size()); - if (exposeGeometry != mLastExposeGeometry) + if (isExposed() && !mInResizeFromApplyConfigure && exposeGeometry != mLastExposeGeometry) sendExposeEvent(exposeGeometry); + + if (mShellSurface) { + mShellSurface->setWindowGeometry(windowContentGeometry()); + if (!qt_window_private(window())->positionAutomatic) + mShellSurface->setWindowPosition(windowGeometry().topLeft()); + } + + if (isOpaque() && mMask.isEmpty()) + setOpaqueArea(QRect(QPoint(0, 0), rect.size())); +} + +void QWaylandWindow::updateInputRegion() +{ + if (!mSurface) + return; + + const bool transparentInputRegion = mFlags.testFlag(Qt::WindowTransparentForInput); + + QRegion inputRegion; + if (!transparentInputRegion) + inputRegion = mMask; + + if (mInputRegion == inputRegion && mTransparentInputRegion == transparentInputRegion) + return; + + mInputRegion = inputRegion; + mTransparentInputRegion = transparentInputRegion; + + if (mInputRegion.isEmpty() && !mTransparentInputRegion) { + mSurface->set_input_region(nullptr); + } else { + struct ::wl_region *region = mDisplay->createRegion(mInputRegion); + mSurface->set_input_region(region); + wl_region_destroy(region); + } +} + +void QWaylandWindow::updateViewport() +{ + if (!surfaceSize().isEmpty()) + mViewport->setDestination(surfaceSize()); +} + +void QWaylandWindow::setGeometryFromApplyConfigure(const QPoint &globalPosition, const QSize &sizeWithMargins) +{ + QMargins margins = clientSideMargins(); + + QPoint positionWithoutMargins = globalPosition + QPoint(margins.left(), margins.top()); + int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left() + margins.right()), 1); + int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top() + margins.bottom()), 1); + + QRect geometry(positionWithoutMargins, QSize(widthWithoutMargins, heightWithoutMargins)); + + mInResizeFromApplyConfigure = true; + setGeometry(geometry); + mInResizeFromApplyConfigure = false; +} + +void QWaylandWindow::repositionFromApplyConfigure(const QPoint &globalPosition) +{ + QMargins margins = clientSideMargins(); + QPoint positionWithoutMargins = globalPosition + QPoint(margins.left(), margins.top()); + + QRect geometry(positionWithoutMargins, windowGeometry().size()); + mInResizeFromApplyConfigure = true; + setGeometry(geometry); + mInResizeFromApplyConfigure = false; } void QWaylandWindow::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset) { - QMargins margins = frameMargins(); - int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left()+margins.right()), 1); - int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top()+margins.bottom()), 1); + QMargins margins = clientSideMargins(); + int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left() + margins.right()), 1); + int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top() + margins.bottom()), 1); QRect geometry(windowGeometry().topLeft(), QSize(widthWithoutMargins, heightWithoutMargins)); mOffset += offset; + mInResizeFromApplyConfigure = true; setGeometry(geometry); + mInResizeFromApplyConfigure = false; } void QWaylandWindow::sendExposeEvent(const QRect &rect) @@ -370,42 +551,36 @@ void QWaylandWindow::sendExposeEvent(const QRect &rect) mLastExposeGeometry = rect; } - -static QVector<QPointer<QWaylandWindow>> activePopups; - -void QWaylandWindow::closePopups(QWaylandWindow *parent) +QPlatformScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const { - while (!activePopups.isEmpty()) { - auto popup = activePopups.takeLast(); - if (popup.isNull()) - continue; - if (popup.data() == parent) - return; - popup->reset(); + QReadLocker lock(&mSurfaceLock); + if (mSurface) { + if (auto *screen = mSurface->oldestEnteredScreen()) + return screen; } -} -QWaylandScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const -{ - return mScreens.isEmpty() ? waylandScreen() : mScreens.first(); + return QPlatformWindow::screen(); } void QWaylandWindow::setVisible(bool visible) { + // Workaround for issue where setVisible may be called with the same value twice + if (lastVisible == visible) + return; + lastVisible = visible; + if (visible) { - if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) - activePopups << this; initWindow(); - mDisplay->flushRequests(); - setGeometry(window()->geometry()); + setGeometry(windowGeometry()); // Don't flush the events here, or else the newly visible window may start drawing, but since // there was no frame before it will be stuck at the waitForFrameSync() in // QWaylandShmBackingStore::beginPaint(). + + if (mShellSurface) + mShellSurface->requestActivateOnShow(); } else { sendExposeEvent(QRect()); - if (window()->type() == Qt::Popup) - closePopups(this); reset(); } } @@ -426,23 +601,37 @@ void QWaylandWindow::lower() void QWaylandWindow::setMask(const QRegion &mask) { + QReadLocker locker(&mSurfaceLock); + if (!mSurface) + return; + if (mMask == mask) return; mMask = mask; - if (!isInitialized()) - return; + updateInputRegion(); - if (mMask.isEmpty()) { - set_input_region(nullptr); - } else { - struct ::wl_region *region = mDisplay->createRegion(mMask); - set_input_region(region); - wl_region_destroy(region); + if (isOpaque()) { + if (mMask.isEmpty()) + setOpaqueArea(QRect(QPoint(0, 0), geometry().size())); + else + setOpaqueArea(mMask); } +} - wl_surface::commit(); +void QWaylandWindow::setAlertState(bool enabled) +{ + if (mShellSurface) + mShellSurface->setAlertState(enabled); +} + +bool QWaylandWindow::isAlertState() const +{ + if (mShellSurface) + return mShellSurface->isAlertState(); + + return false; } void QWaylandWindow::applyConfigureWhenPossible() @@ -459,12 +648,24 @@ void QWaylandWindow::doApplyConfigure() if (!mWaitingToApplyConfigure) return; + Q_ASSERT_X(QThread::currentThreadId() == QThreadData::get2(thread())->threadId.loadRelaxed(), + "QWaylandWindow::doApplyConfigure", "not called from main thread"); + if (mShellSurface) mShellSurface->applyConfigure(); mWaitingToApplyConfigure = false; } +void QWaylandWindow::doApplyConfigureFromOtherThread() +{ + QMutexLocker lock(&mResizeLock); + if (!mCanResize || !mWaitingToApplyConfigure) + return; + doApplyConfigure(); + sendRecursiveExposeEvent(); +} + void QWaylandWindow::setCanResize(bool canResize) { QMutexLocker lock(&mResizeLock); @@ -475,8 +676,13 @@ void QWaylandWindow::setCanResize(bool canResize) QWindowSystemInterface::handleGeometryChange(window(), geometry()); } if (mWaitingToApplyConfigure) { - doApplyConfigure(); - sendExposeEvent(QRect(QPoint(), geometry().size())); + bool inGuiThread = QThread::currentThreadId() == QThreadData::get2(thread())->threadId.loadRelaxed(); + if (inGuiThread) { + doApplyConfigure(); + sendRecursiveExposeEvent(); + } else { + QMetaObject::invokeMethod(this, &QWaylandWindow::doApplyConfigureFromOtherThread, Qt::QueuedConnection); + } } else if (mResizeDirty) { mResizeDirty = false; sendExposeEvent(QRect(QPoint(), geometry().size())); @@ -492,67 +698,41 @@ void QWaylandWindow::applyConfigure() doApplyConfigure(); lock.unlock(); - sendExposeEvent(QRect(QPoint(), geometry().size())); + sendRecursiveExposeEvent(); QWindowSystemInterface::flushWindowSystemEvents(); } -void QWaylandWindow::surface_enter(wl_output *output) +void QWaylandWindow::sendRecursiveExposeEvent() { - QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); - auto addedScreen = QWaylandScreen::fromWlOutput(output); + if (!isExposed()) + sendExposeEvent(QRect()); + else + sendExposeEvent(QRect(QPoint(), geometry().size())); - if (mScreens.contains(addedScreen)) { - qWarning() << "Unexpected wl_surface.enter received for output with id:" - << wl_proxy_get_id(reinterpret_cast<wl_proxy *>(output)) - << "screen name:" << addedScreen->name() << "screen model:" << addedScreen->model(); - return; + for (QWaylandSubSurface *subSurface : std::as_const(mChildren)) { + auto subWindow = subSurface->window(); + subWindow->sendRecursiveExposeEvent(); } - - mScreens.append(addedScreen); - - QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); - if (oldScreen != newScreen) //currently this will only happen if the first wl_surface.enter is for a non-primary screen - handleScreenChanged(); } -void QWaylandWindow::surface_leave(wl_output *output) +void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { - QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); - auto *removedScreen = QWaylandScreen::fromWlOutput(output); - bool wasRemoved = mScreens.removeOne(removedScreen); - if (!wasRemoved) { - qWarning() << "Unexpected wl_surface.leave received for output with id:" - << wl_proxy_get_id(reinterpret_cast<wl_proxy *>(output)) - << "screen name:" << removedScreen->name() << "screen model:" << removedScreen->model(); + QReadLocker locker(&mSurfaceLock); + if (mSurface == nullptr) return; - } - - QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); - if (oldScreen != newScreen) - handleScreenChanged(); -} -void QWaylandWindow::handleScreenRemoved(QScreen *qScreen) -{ - QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); - bool wasRemoved = mScreens.removeOne(static_cast<QWaylandScreen *>(qScreen->handle())); - if (wasRemoved) { - QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); - if (oldScreen != newScreen) - handleScreenChanged(); - } -} - -void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) -{ - Q_ASSERT(!buffer->committed()); if (buffer) { + Q_ASSERT(!buffer->committed()); handleUpdate(); - buffer->setBusy(); - - QtWayland::wl_surface::attach(buffer->buffer(), x, y); + buffer->setBusy(true); + if (mSurface->version() >= WL_SURFACE_OFFSET_SINCE_VERSION) { + mSurface->offset(x, y); + mSurface->attach(buffer->buffer(), 0, 0); + } else { + mSurface->attach(buffer->buffer(), x, y); + } } else { - QtWayland::wl_surface::attach(nullptr, 0, 0); + mSurface->attach(nullptr, 0, 0); } } @@ -564,7 +744,20 @@ void QWaylandWindow::attachOffset(QWaylandBuffer *buffer) void QWaylandWindow::damage(const QRect &rect) { - damage(rect.x(), rect.y(), rect.width(), rect.height()); + QReadLocker locker(&mSurfaceLock); + if (mSurface == nullptr) + return; + + const qreal s = scale(); + if (mSurface->version() >= 4) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } else { + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } } void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage) @@ -572,21 +765,15 @@ void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage) if (isExposed()) { commit(buffer, damage); } else { + if (mQueuedBuffer) { + mQueuedBuffer->setBusy(false); + } mQueuedBuffer = buffer; + mQueuedBuffer->setBusy(true); mQueuedBufferDamage = damage; } } -void QWaylandWindow::handleExpose(const QRegion ®ion) -{ - QWindowSystemInterface::handleExposeEvent(window(), region); - if (mQueuedBuffer) { - commit(mQueuedBuffer, mQueuedBufferDamage); - mQueuedBuffer = nullptr; - mQueuedBufferDamage = QRegion(); - } -} - void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) { Q_ASSERT(isExposed()); @@ -594,57 +781,87 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) qCDebug(lcWaylandBackingstore) << "Buffer already committed, ignoring."; return; } - if (!isInitialized()) + + QReadLocker locker(&mSurfaceLock); + if (!mSurface) return; attachOffset(buffer); - for (const QRect &rect: damage) - wl_surface::damage(rect.x(), rect.y(), rect.width(), rect.height()); + if (mSurface->version() >= 4) { + const qreal s = scale(); + for (const QRect &rect : damage) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } + } else { + for (const QRect &rect: damage) + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } Q_ASSERT(!buffer->committed()); buffer->setCommitted(); - wl_surface::commit(); + mSurface->commit(); +} + +void QWaylandWindow::commit() +{ + QReadLocker locker(&mSurfaceLock); + if (mSurface != nullptr) + mSurface->commit(); } const wl_callback_listener QWaylandWindow::callbackListener = { [](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(); + window->handleFrameCallback(callback); } }; -void QWaylandWindow::handleFrameCallback() +void QWaylandWindow::handleFrameCallback(wl_callback* callback) { - bool wasExposed = isExposed(); - - if (mFrameCallbackTimerId != -1) { - killTimer(mFrameCallbackTimerId); - mFrameCallbackTimerId = -1; + QMutexLocker locker(&mFrameSyncMutex); + if (!mFrameCallback) { + // This means the callback is already unset by QWaylandWindow::reset. + // The wl_callback object will be destroyed there too. + return; } + Q_ASSERT(callback == mFrameCallback); + wl_callback_destroy(callback); + mFrameCallback = nullptr; mWaitingForFrameCallback = false; - mFrameCallbackTimedOut = false; + mFrameCallbackElapsedTimer.invalidate(); - if (!wasExposed && isExposed()) + // The rest can wait until we can run it on the correct thread + if (mWaitingForUpdateDelivery.testAndSetAcquire(false, true)) { + // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() + // in the single-threaded case. + QMetaObject::invokeMethod(this, &QWaylandWindow::doHandleFrameCallback, Qt::QueuedConnection); + } + mFrameSyncWait.notify_all(); +} + +void QWaylandWindow::doHandleFrameCallback() +{ + mWaitingForUpdateDelivery.storeRelease(false); + bool wasExposed = isExposed(); + mFrameCallbackTimedOut = false; + if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? sendExposeEvent(QRect(QPoint(), geometry().size())); if (wasExposed && hasPendingUpdateRequest()) deliverUpdateRequest(); -} -QMutex QWaylandWindow::mFrameSyncMutex; +} bool QWaylandWindow::waitForFrameSync(int timeout) { QMutexLocker locker(&mFrameSyncMutex); - if (!mWaitingForFrameCallback) - return true; - wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(mFrameCallback), mFrameQueue); - mDisplay->dispatchQueueWhile(mFrameQueue, [&]() { return mWaitingForFrameCallback; }, timeout); + 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"; @@ -653,21 +870,78 @@ bool QWaylandWindow::waitForFrameSync(int timeout) 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 { - if (mWindowDecoration) + if (mWindowDecorationEnabled) return mWindowDecoration->margins(); - return QPlatformWindow::frameMargins(); + else if (mShellSurface) + return mShellSurface->serverSideFrameMargins(); + else + return QPlatformWindow::frameMargins(); +} + +QMargins QWaylandWindow::clientSideMargins() const +{ + return mWindowDecorationEnabled ? mWindowDecoration->margins() : QMargins{}; +} + +void QWaylandWindow::setCustomMargins(const QMargins &margins) { + const QMargins oldMargins = mCustomMargins; + mCustomMargins = margins; + setGeometry(geometry().marginsRemoved(oldMargins).marginsAdded(margins)); +} + +/*! + * Size, with decorations (including including eventual shadows) in wl_surface coordinates + */ +QSize QWaylandWindow::surfaceSize() const +{ + return geometry().marginsAdded(clientSideMargins()).size(); +} + +QMargins QWaylandWindow::windowContentMargins() const +{ + QMargins shadowMargins; + + if (mWindowDecorationEnabled) + shadowMargins = mWindowDecoration->margins(QWaylandAbstractDecoration::ShadowsOnly); + + if (!mCustomMargins.isNull()) + shadowMargins += mCustomMargins; + + return shadowMargins; +} + +/*! + * Window geometry as defined by the xdg-shell spec (in wl_surface coordinates) + * topLeft is where the shadow stops and the decorations border start. + */ +QRect QWaylandWindow::windowContentGeometry() const +{ + const QMargins margins = windowContentMargins(); + return QRect(QPoint(margins.left(), margins.top()), surfaceSize().shrunkBy(margins)); +} + +/*! + * Converts from wl_surface coordinates to Qt window coordinates. Qt window + * coordinates start inside (not including) the window decorations, while + * wl_surface coordinates start at the first pixel of the buffer. Potentially, + * this should be in the window shadow, although we don't have those. So for + * now, it's the first pixel of the decorations. + */ +QPointF QWaylandWindow::mapFromWlSurface(const QPointF &surfacePosition) const +{ + const QMargins margins = clientSideMargins(); + return QPointF(surfacePosition.x() - margins.left(), surfacePosition.y() - margins.top()); +} + +wl_surface *QWaylandWindow::wlSurface() const +{ + QReadLocker locker(&mSurfaceLock); + return mSurface ? mSurface->object() : nullptr; } QWaylandShellSurface *QWaylandWindow::shellSurface() const @@ -675,6 +949,15 @@ QWaylandShellSurface *QWaylandWindow::shellSurface() const return mShellSurface; } +std::any QWaylandWindow::_surfaceRole() const +{ + if (mSubSurfaceWindow) + return mSubSurfaceWindow->object(); + if (mShellSurface) + return mShellSurface->surfaceRole(); + return {}; +} + QWaylandSubSurface *QWaylandWindow::subSurfaceWindow() const { return mSubSurfaceWindow; @@ -682,38 +965,59 @@ QWaylandSubSurface *QWaylandWindow::subSurfaceWindow() const QWaylandScreen *QWaylandWindow::waylandScreen() const { - return static_cast<QWaylandScreen *>(QPlatformWindow::screen()); + auto *platformScreen = QPlatformWindow::screen(); + Q_ASSERT(platformScreen); + if (platformScreen->isPlaceholder()) + return nullptr; + return static_cast<QWaylandScreen *>(platformScreen); } void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation) { - if (mDisplay->compositorVersion() < 2) + mLastReportedContentOrientation = orientation; + updateBufferTransform(); +} + +void QWaylandWindow::updateBufferTransform() +{ + QReadLocker locker(&mSurfaceLock); + if (mSurface == nullptr || mSurface->version() < 2) return; wl_output_transform transform; - bool isPortrait = window()->screen() && window()->screen()->primaryOrientation() == Qt::PortraitOrientation; - switch (orientation) { - case Qt::PrimaryOrientation: - transform = WL_OUTPUT_TRANSFORM_NORMAL; - break; - case Qt::LandscapeOrientation: - transform = isPortrait ? WL_OUTPUT_TRANSFORM_270 : WL_OUTPUT_TRANSFORM_NORMAL; - break; - case Qt::PortraitOrientation: - transform = isPortrait ? WL_OUTPUT_TRANSFORM_NORMAL : WL_OUTPUT_TRANSFORM_90; - break; - case Qt::InvertedLandscapeOrientation: - transform = isPortrait ? WL_OUTPUT_TRANSFORM_90 : WL_OUTPUT_TRANSFORM_180; - break; - case Qt::InvertedPortraitOrientation: - transform = isPortrait ? WL_OUTPUT_TRANSFORM_180 : WL_OUTPUT_TRANSFORM_270; - break; - default: - Q_UNREACHABLE(); + Qt::ScreenOrientation screenOrientation = Qt::PrimaryOrientation; + + if (mSurface->version() >= 6) { + const auto transform = mSurface->preferredBufferTransform().value_or(WL_OUTPUT_TRANSFORM_NORMAL); + if (auto screen = waylandScreen()) + screenOrientation = screen->toScreenOrientation(transform, Qt::PrimaryOrientation); + } else { + if (auto screen = window()->screen()) + screenOrientation = screen->primaryOrientation(); + } + + const bool isPortrait = (screenOrientation == Qt::PortraitOrientation); + + switch (mLastReportedContentOrientation) { + case Qt::PrimaryOrientation: + transform = WL_OUTPUT_TRANSFORM_NORMAL; + break; + case Qt::LandscapeOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_270 : WL_OUTPUT_TRANSFORM_NORMAL; + break; + case Qt::PortraitOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_NORMAL : WL_OUTPUT_TRANSFORM_90; + break; + case Qt::InvertedLandscapeOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_90 : WL_OUTPUT_TRANSFORM_180; + break; + case Qt::InvertedPortraitOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_180 : WL_OUTPUT_TRANSFORM_270; + break; + default: + Q_UNREACHABLE(); } - set_buffer_transform(transform); - // set_buffer_transform is double buffered, we need to commit. - wl_surface::commit(); + mSurface->set_buffer_transform(transform); } void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask) @@ -735,10 +1039,18 @@ void QWaylandWindow::setWindowFlags(Qt::WindowFlags flags) mFlags = flags; createDecoration(); + + QReadLocker locker(&mSurfaceLock); + updateInputRegion(); } bool QWaylandWindow::createDecoration() { + Q_ASSERT_X(QThread::currentThreadId() == QThreadData::get2(thread())->threadId.loadRelaxed(), + "QWaylandWindow::createDecoration", "not called from main thread"); + // TODO: client side decorations do not work with Vulkan backend. + if (window()->surfaceType() == QSurface::VulkanSurface) + return false; if (!mDisplay->supportsWindowDecoration()) return false; @@ -761,12 +1073,17 @@ bool QWaylandWindow::createDecoration() decoration = false; if (mSubSurfaceWindow) decoration = false; - if (mShellSurface && !mShellSurface->wantsDecorations()) + if (!mShellSurface || !mShellSurface->wantsDecorations()) decoration = false; - bool hadDecoration = mWindowDecoration; + bool hadDecoration = mWindowDecorationEnabled; if (decoration && !decorationPluginFailed) { - if (!mWindowDecoration) { + if (!mWindowDecorationEnabled) { + if (mWindowDecoration) { + delete mWindowDecoration; + mWindowDecoration = nullptr; + } + QStringList decorations = QWaylandDecorationFactory::keys(); if (decorations.empty()) { qWarning() << "No decoration plugins available. Running with no decorations."; @@ -784,6 +1101,22 @@ bool QWaylandWindow::createDecoration() } } + if (targetKey.isEmpty()) { + auto unixServices = dynamic_cast<QGenericUnixServices *>( + QGuiApplicationPrivate::platformIntegration()->services()); + const QByteArray currentDesktop = unixServices->desktopEnvironment(); + if (currentDesktop == "GNOME") { + if (decorations.contains("adwaita"_L1)) + targetKey = "adwaita"_L1; + else if (decorations.contains("gnome"_L1)) + targetKey = "gnome"_L1; + } else { + // Do not use Adwaita/GNOME decorations on other DEs + decorations.removeAll("adwaita"_L1); + decorations.removeAll("gnome"_L1); + } + } + if (targetKey.isEmpty()) targetKey = decorations.first(); // first come, first served. @@ -795,19 +1128,29 @@ bool QWaylandWindow::createDecoration() return false; } mWindowDecoration->setWaylandWindow(this); + mWindowDecorationEnabled = true; } } else { - delete mWindowDecoration; - mWindowDecoration = nullptr; + mWindowDecorationEnabled = false; } - if (hadDecoration != (bool)mWindowDecoration) { - foreach (QWaylandSubSurface *subsurf, mChildren) { + if (hadDecoration != mWindowDecorationEnabled) { + for (QWaylandSubSurface *subsurf : std::as_const(mChildren)) { QPoint pos = subsurf->window()->geometry().topLeft(); QMargins m = frameMargins(); subsurf->set_position(pos.x() + m.left(), pos.y() + m.top()); } - sendExposeEvent(QRect(QPoint(), geometry().size())); + setGeometry(geometry()); + + // creating a decoration changes our margins which in turn change size hints + propagateSizeHints(); + + // This is a special case where the buffer is recreated, but since + // the content rect remains the same, the widgets remain the same + // size and are not redrawn, leaving the new buffer empty. As a simple + // work-around, we trigger a full extra update whenever the client-side + // window decorations are toggled while the window is showing. + window()->requestUpdate(); } return mWindowDecoration; @@ -815,7 +1158,7 @@ bool QWaylandWindow::createDecoration() QWaylandAbstractDecoration *QWaylandWindow::decoration() const { - return mWindowDecoration; + return mWindowDecorationEnabled ? mWindowDecoration : nullptr; } static QWaylandWindow *closestShellSurfaceWindow(QWindow *window) @@ -831,6 +1174,11 @@ static QWaylandWindow *closestShellSurfaceWindow(QWindow *window) QWaylandWindow *QWaylandWindow::transientParent() const { + return mTransientParent; +} + +QWaylandWindow *QWaylandWindow::guessTransientParent() const +{ // Take the closest window with a shell surface, since the transient parent may be a // QWidgetWindow or some other window without a shell surface, which is then not able to // get mouse events. @@ -845,51 +1193,180 @@ QWaylandWindow *QWaylandWindow::transientParent() const void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e) { + if (e.type == QEvent::Leave) { + if (mWindowDecorationEnabled) { + if (mMouseEventsInContentArea) + QWindowSystemInterface::handleLeaveEvent(window()); + } else { + QWindowSystemInterface::handleLeaveEvent(window()); + } +#if QT_CONFIG(cursor) + restoreMouseCursor(inputDevice); +#endif + return; + } - if (mWindowDecoration) { + if (mWindowDecorationEnabled) { handleMouseEventWithDecoration(inputDevice, e); } else { switch (e.type) { - case QWaylandPointerEvent::Enter: + case QEvent::Enter: QWindowSystemInterface::handleEnterEvent(window(), e.local, e.global); break; - case QWaylandPointerEvent::Motion: - QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, e.local, e.global, e.buttons, e.modifiers); + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, e.local, e.global, e.buttons, e.button, e.type, e.modifiers); break; - case QWaylandPointerEvent::Wheel: - QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global, e.pixelDelta, e.angleDelta, e.modifiers); + case QEvent::Wheel: + QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global, + e.pixelDelta, e.angleDelta, e.modifiers, + e.phase, e.source, e.inverted); break; + default: + Q_UNREACHABLE(); } } #if QT_CONFIG(cursor) - if (e.type == QWaylandPointerEvent::Enter) { - QRect windowGeometry = window()->frameGeometry(); - windowGeometry.moveTopLeft({0, 0}); // convert to wayland surface coordinates - QRect contentGeometry = windowGeometry.marginsRemoved(frameMargins()); + if (e.type == QEvent::Enter) { + QRect contentGeometry = QRect(QPoint(), surfaceSize()).marginsRemoved(clientSideMargins()); if (contentGeometry.contains(e.local.toPoint())) restoreMouseCursor(inputDevice); } #endif } -void QWaylandWindow::handleMouseLeave(QWaylandInputDevice *inputDevice) +#ifndef QT_NO_GESTURES +void QWaylandWindow::handleSwipeGesture(QWaylandInputDevice *inputDevice, + const QWaylandPointerGestureSwipeEvent &e) { - if (mWindowDecoration) { - if (mMouseEventsInContentArea) { - QWindowSystemInterface::handleLeaveEvent(window()); - } - } else { - QWindowSystemInterface::handleLeaveEvent(window()); + switch (e.state) { + case Qt::GestureStarted: + if (mGestureState != GestureNotActive) + qCWarning(lcQpaWaylandInput) << "Unexpected GestureStarted while already active"; + + if (mWindowDecorationEnabled && !mMouseEventsInContentArea) { + // whole gesture sequence will be ignored + mGestureState = GestureActiveInDecoration; + return; + } + + mGestureState = GestureActiveInContentArea; + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::BeginNativeGesture, + e.local, e.global, e.fingers); + break; + case Qt::GestureUpdated: + if (mGestureState != GestureActiveInContentArea) + return; + + if (!e.delta.isNull()) { + QWindowSystemInterface::handleGestureEventWithValueAndDelta( + window(), e.timestamp, inputDevice->mTouchPadDevice, + Qt::PanNativeGesture, + 0, e.delta, e.local, e.global, e.fingers); + } + break; + case Qt::GestureFinished: + case Qt::GestureCanceled: + if (mGestureState == GestureActiveInDecoration) { + mGestureState = GestureNotActive; + return; + } + + if (mGestureState != GestureActiveInContentArea) + qCWarning(lcQpaWaylandInput) << "Unexpected" << (e.state == Qt::GestureFinished ? "GestureFinished" : "GestureCanceled"); + + mGestureState = GestureNotActive; + + // There's currently no way to expose cancelled gestures to the rest of Qt, so + // this part of information is lost. + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::EndNativeGesture, + e.local, e.global, e.fingers); + break; + default: + break; } -#if QT_CONFIG(cursor) - restoreMouseCursor(inputDevice); -#endif } -bool QWaylandWindow::touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::TouchPointState state, Qt::KeyboardModifiers mods) +void QWaylandWindow::handlePinchGesture(QWaylandInputDevice *inputDevice, + const QWaylandPointerGesturePinchEvent &e) { - if (!mWindowDecoration) + switch (e.state) { + case Qt::GestureStarted: + if (mGestureState != GestureNotActive) + qCWarning(lcQpaWaylandInput) << "Unexpected GestureStarted while already active"; + + if (mWindowDecorationEnabled && !mMouseEventsInContentArea) { + // whole gesture sequence will be ignored + mGestureState = GestureActiveInDecoration; + return; + } + + mGestureState = GestureActiveInContentArea; + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::BeginNativeGesture, + e.local, e.global, e.fingers); + break; + case Qt::GestureUpdated: + if (mGestureState != GestureActiveInContentArea) + return; + + if (!e.delta.isNull()) { + QWindowSystemInterface::handleGestureEventWithValueAndDelta( + window(), e.timestamp, inputDevice->mTouchPadDevice, + Qt::PanNativeGesture, + 0, e.delta, e.local, e.global, e.fingers); + } + if (e.rotation_delta != 0) { + QWindowSystemInterface::handleGestureEventWithRealValue(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::RotateNativeGesture, + e.rotation_delta, + e.local, e.global, e.fingers); + } + if (e.scale_delta != 0) { + QWindowSystemInterface::handleGestureEventWithRealValue(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::ZoomNativeGesture, + e.scale_delta, + e.local, e.global, e.fingers); + } + break; + case Qt::GestureFinished: + case Qt::GestureCanceled: + if (mGestureState == GestureActiveInDecoration) { + mGestureState = GestureNotActive; + return; + } + + if (mGestureState != GestureActiveInContentArea) + qCWarning(lcQpaWaylandInput) << "Unexpected" << (e.state == Qt::GestureFinished ? "GestureFinished" : "GestureCanceled"); + + mGestureState = GestureNotActive; + + // There's currently no way to expose cancelled gestures to the rest of Qt, so + // this part of information is lost. + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::EndNativeGesture, + e.local, e.global, e.fingers); + break; + default: + break; + } +} +#endif // #ifndef QT_NO_GESTURES + + +bool QWaylandWindow::touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) +{ + if (!mWindowDecorationEnabled) return false; return mWindowDecoration->handleTouch(inputDevice, local, global, state, mods); } @@ -905,16 +1382,14 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe return; } - QMargins marg = frameMargins(); + QMargins marg = clientSideMargins(); QRect windowRect(0 + marg.left(), 0 + marg.top(), - geometry().size().width() - marg.right(), - geometry().size().height() - marg.bottom()); + geometry().size().width(), + geometry().size().height()); if (windowRect.contains(e.local.toPoint()) || mMousePressedInContentArea != Qt::NoButton) { - QPointF localTranslated = e.local; + const QPointF localTranslated = mapFromWlSurface(e.local); QPointF globalTranslated = e.global; - localTranslated.setX(localTranslated.x() - marg.left()); - localTranslated.setY(localTranslated.y() - marg.top()); globalTranslated.setX(globalTranslated.x() - marg.left()); globalTranslated.setY(globalTranslated.y() - marg.top()); if (!mMouseEventsInContentArea) { @@ -925,15 +1400,23 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe } switch (e.type) { - case QWaylandPointerEvent::Enter: + case QEvent::Enter: QWindowSystemInterface::handleEnterEvent(window(), localTranslated, globalTranslated); break; - case QWaylandPointerEvent::Motion: - QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, localTranslated, globalTranslated, e.buttons, e.modifiers); + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, localTranslated, globalTranslated, e.buttons, e.button, e.type, e.modifiers); break; - case QWaylandPointerEvent::Wheel: - QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, localTranslated, globalTranslated, e.pixelDelta, e.angleDelta, e.modifiers); + case QEvent::Wheel: { + QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, + localTranslated, globalTranslated, + e.pixelDelta, e.angleDelta, e.modifiers, + e.phase, e.source, e.inverted); break; + } + default: + Q_UNREACHABLE(); } mMouseEventsInContentArea = true; @@ -946,45 +1429,93 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe } } -void QWaylandWindow::handleScreenChanged() +void QWaylandWindow::handleScreensChanged() { - QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); + QPlatformScreen *newScreen = calculateScreenFromSurfaceEvents(); + + if (newScreen == mLastReportedScreen) + return; + + if (!newScreen->isPlaceholder() && !newScreen->QPlatformScreen::screen()) + mDisplay->forceRoundTrip(); QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); - int scale = newScreen->scale(); - if (scale != mScale) { - mScale = scale; - if (isInitialized() && mDisplay->compositorVersion() >= 3) - set_buffer_scale(mScale); - ensureSize(); + mLastReportedScreen = newScreen; + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip + && geometry().topLeft() != newScreen->geometry().topLeft()) { + auto geometry = this->geometry(); + geometry.moveTo(newScreen->geometry().topLeft()); + setGeometry(geometry); + } + + updateScale(); + updateBufferTransform(); +} + +void QWaylandWindow::updateScale() +{ + if (mFractionalScale) { + auto preferredScale = mFractionalScale->preferredScale().value_or(1.0); + preferredScale = std::max(1.0, preferredScale); + Q_ASSERT(mViewport); + setScale(preferredScale); + return; + } + + if (mSurface && mSurface->version() >= 6) { + auto preferredScale = mSurface->preferredBufferScale().value_or(1); + preferredScale = std::max(1, preferredScale); + setScale(preferredScale); + return; + } + + int scale = mLastReportedScreen->isPlaceholder() ? 1 : static_cast<QWaylandScreen *>(mLastReportedScreen)->scale(); + setScale(scale); +} + +void QWaylandWindow::setScale(qreal newScale) +{ + if (qFuzzyCompare(mScale, newScale)) + return; + mScale = newScale; + + QWindowSystemInterface::handleWindowDevicePixelRatioChanged(window()); + if (mSurface) { + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(mScale)); + } + ensureSize(); + + if (isExposed()) { + // redraw at the new DPR + window()->requestUpdate(); + sendExposeEvent(QRect(QPoint(), geometry().size())); } } #if QT_CONFIG(cursor) void QWaylandWindow::setMouseCursor(QWaylandInputDevice *device, const QCursor &cursor) { - device->setCursor(cursor, waylandScreen()); + int fallbackBufferScale = int(devicePixelRatio()); + device->setCursor(&cursor, {}, fallbackBufferScale); } void QWaylandWindow::restoreMouseCursor(QWaylandInputDevice *device) { - setMouseCursor(device, window()->cursor()); + if (const QCursor *overrideCursor = QGuiApplication::overrideCursor()) + setMouseCursor(device, *overrideCursor); + else + setMouseCursor(device, window()->cursor()); } #endif void QWaylandWindow::requestActivateWindow() { - qCWarning(lcQpaWayland) << "Wayland does not support QWindow::requestActivate()"; -} - -void QWaylandWindow::unfocus() -{ -#if QT_CONFIG(clipboard) - QWaylandInputDevice *inputDevice = mDisplay->currentInputDevice(); - if (inputDevice && inputDevice->dataDevice()) { - inputDevice->dataDevice()->invalidateSelectionOffer(); - } -#endif + if (mShellSurface) + mShellSurface->requestActivate(); } bool QWaylandWindow::isExposed() const @@ -1009,14 +1540,14 @@ bool QWaylandWindow::isActive() const return mDisplay->isWindowActivated(this); } -int QWaylandWindow::scale() const +qreal QWaylandWindow::scale() const { - return mScale; + return devicePixelRatio(); } qreal QWaylandWindow::devicePixelRatio() const { - return mScale; + return qreal(mScale); } bool QWaylandWindow::setMouseGrabEnabled(bool grab) @@ -1030,10 +1561,28 @@ bool QWaylandWindow::setMouseGrabEnabled(bool grab) return true; } +QWaylandWindow::ToplevelWindowTilingStates QWaylandWindow::toplevelWindowTilingStates() const +{ + return mLastReportedToplevelWindowTilingStates; +} + +void QWaylandWindow::handleToplevelWindowTilingStatesChanged(ToplevelWindowTilingStates states) +{ + mLastReportedToplevelWindowTilingStates = states; +} + +Qt::WindowStates QWaylandWindow::windowStates() const +{ + return mLastReportedWindowStates; +} + void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states) { createDecoration(); - QWindowSystemInterface::handleWindowStateChanged(window(), states, mLastReportedWindowStates); + Qt::WindowStates statesWithoutActive = states & ~Qt::WindowActive; + Qt::WindowStates lastStatesWithoutActive = mLastReportedWindowStates & ~Qt::WindowActive; + QWindowSystemInterface::handleWindowStateChanged(window(), statesWithoutActive, + lastStatesWithoutActive); mLastReportedWindowStates = states; } @@ -1070,59 +1619,67 @@ QVariant QWaylandWindow::property(const QString &name, const QVariant &defaultVa return m_properties.value(name, defaultValue); } +#ifdef QT_PLATFORM_WINDOW_HAS_VIRTUAL_SET_BACKING_STORE +void QWaylandWindow::setBackingStore(QPlatformBackingStore *store) +{ + mBackingStore = dynamic_cast<QWaylandShmBackingStore *>(store); +} +#endif + void QWaylandWindow::timerEvent(QTimerEvent *event) { - if (event->timerId() == mFallbackUpdateTimerId) { - killTimer(mFallbackUpdateTimerId); - mFallbackUpdateTimerId = -1; - qCDebug(lcWaylandBackingstore) << "mFallbackUpdateTimer timed out"; + if (event->timerId() != mFrameCallbackCheckIntervalTimerId) + return; - if (!isExposed()) { - qCDebug(lcWaylandBackingstore) << "Fallback update timer: Window not exposed," - << "not delivering update request."; - return; - } + { + QMutexLocker lock(&mFrameSyncMutex); - if (mWaitingForUpdate && hasPendingUpdateRequest() && !mWaitingForFrameCallback) { - qCWarning(lcWaylandBackingstore) << "Delivering update request through fallback timer," - << "may not be in sync with display"; - deliverUpdateRequest(); + bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); + if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; + } + if (!mFrameCallbackElapsedTimer.isValid() || !callbackTimerExpired) { + return; } + mFrameCallbackElapsedTimer.invalidate(); } - 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()); - } + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + sendExposeEvent(QRect()); } void QWaylandWindow::requestUpdate() { + qCDebug(lcWaylandBackingstore) << "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 - 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; - } + // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log + // here so we can get this information when debugging update/frame callback issues. + // Continue as nothing happened, though. + if (mWaitingForUpdate) + qCDebug(lcWaylandBackingstore) << "requestUpdate called twice without committing anything"; // 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) + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } + if (hasPendingUpdateRequest()) deliverUpdateRequest(); }, Qt::QueuedConnection); } @@ -1132,43 +1689,42 @@ void QWaylandWindow::requestUpdate() // 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? + qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread(); - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - } + // TODO: Should sync subsurfaces avoid requesting frame callbacks? + QReadLocker lock(&mSurfaceLock); + if (!mSurface) + return; - 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); - } + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; - mFrameCallback = frame(); + struct ::wl_surface *wrappedSurface = reinterpret_cast<struct ::wl_surface *>(wl_proxy_create_wrapper(mSurface->object())); + 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); 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); + if (mFrameCallbackTimeout > 0) { + QMetaObject::invokeMethod(this, [this] { + QMutexLocker locker(&mFrameSyncMutex); + + if (mWaitingForFrameCallback) { + if (mFrameCallbackCheckIntervalTimerId < 0) + mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); + mFrameCallbackElapsedTimer.start(); + } + }, Qt::QueuedConnection); + } } void QWaylandWindow::deliverUpdateRequest() { + qCDebug(lcWaylandBackingstore) << "deliverUpdateRequest"; mWaitingForUpdate = true; QPlatformWindow::deliverUpdateRequest(); } @@ -1178,14 +1734,104 @@ void QWaylandWindow::addAttachOffset(const QPoint point) mOffset += point; } -bool QtWaylandClient::QWaylandWindow::startSystemMove(const QPoint &pos) +void QWaylandWindow::propagateSizeHints() +{ + if (mShellSurface) + mShellSurface->propagateSizeHints(); +} + +bool QWaylandWindow::startSystemResize(Qt::Edges edges) +{ + if (auto *seat = display()->lastInputDevice()) { + bool rc = mShellSurface && mShellSurface->resize(seat, edges); + seat->handleEndDrag(); + return rc; + } + return false; +} + +bool QtWaylandClient::QWaylandWindow::startSystemMove() { - Q_UNUSED(pos); - if (auto seat = display()->lastInputDevice()) - return mShellSurface && mShellSurface->move(seat); + if (auto seat = display()->lastInputDevice()) { + bool rc = mShellSurface && mShellSurface->move(seat); + seat->handleEndDrag(); + return rc; + } return false; } +bool QWaylandWindow::isOpaque() const +{ + return window()->requestedFormat().alphaBufferSize() <= 0; +} + +void QWaylandWindow::setOpaqueArea(const QRegion &opaqueArea) +{ + const QRegion translatedOpaqueArea = opaqueArea.translated(clientSideMargins().left(), clientSideMargins().top()); + + if (translatedOpaqueArea == mOpaqueArea || !mSurface) + return; + + mOpaqueArea = translatedOpaqueArea; + + struct ::wl_region *region = mDisplay->createRegion(translatedOpaqueArea); + mSurface->set_opaque_region(region); + wl_region_destroy(region); +} + +void QWaylandWindow::requestXdgActivationToken(uint serial) +{ + mShellSurface->requestXdgActivationToken(serial); +} + +void QWaylandWindow::setXdgActivationToken(const QString &token) +{ + mShellSurface->setXdgActivationToken(token); +} + +void QWaylandWindow::addChildPopup(QWaylandWindow *child) +{ + if (mShellSurface) + mShellSurface->attachPopup(child->shellSurface()); + mChildPopups.append(child); +} + +void QWaylandWindow::removeChildPopup(QWaylandWindow *child) +{ + if (mShellSurface) + mShellSurface->detachPopup(child->shellSurface()); + mChildPopups.removeAll(child); +} + +void QWaylandWindow::closeChildPopups() { + while (!mChildPopups.isEmpty()) { + auto popup = mChildPopups.takeLast(); + popup->reset(); + } +} + +void QWaylandWindow::reinit() +{ + if (window()->isVisible()) { + initWindow(); + if (hasPendingUpdateRequest()) + deliverUpdateRequest(); + } +} + +bool QWaylandWindow::windowEvent(QEvent *event) +{ + if (event->type() == QEvent::ApplicationPaletteChange + || event->type() == QEvent::ApplicationFontChange) { + if (mWindowDecorationEnabled && window()->isVisible()) + mWindowDecoration->update(); + } + + return QPlatformWindow::windowEvent(event); +} + } QT_END_NAMESPACE + +#include "moc_qwaylandwindow_p.cpp" |