diff options
Diffstat (limited to 'src/client/qwaylandwindow.cpp')
-rw-r--r-- | src/client/qwaylandwindow.cpp | 1152 |
1 files changed, 868 insertions, 284 deletions
diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 130dbab36..6b40cd28c 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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" @@ -43,15 +7,17 @@ #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" #include "qwaylandabstractdecoration_p.h" -#include "qwaylandwindowmanagerintegration_p.h" +#include "qwaylandplatformservices_p.h" #include "qwaylandnativeinterface_p.h" #include "qwaylanddecorationfactory_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandshellintegration_p.h" +#include "qwaylandviewport_p.h" #include <QtCore/QFileInfo> #include <QtCore/QPointer> @@ -60,23 +26,31 @@ #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 <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, QWaylandDisplay *display) : QPlatformWindow(window) , mDisplay(display) - , mFrameQueue(mDisplay->createFrameQueue()) + , mSurfaceLock(QReadWriteLock::Recursive) + , mShellIntegration(display->shellIntegration()) , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) { { @@ -86,18 +60,18 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) mFrameCallbackTimeout = frameCallbackTimeout; } - mScale = waylandScreen() ? waylandScreen()->scale() : 1; // fallback to 1 if we don't have a real screen - static WId id = 1; mWindowId = id++; initializeWlSurface(); + + connect(this, &QWaylandWindow::wlSurfaceCreated, this, + &QNativeInterface::Private::QWaylandWindow::surfaceCreated); + connect(this, &QWaylandWindow::wlSurfaceDestroyed, this, + &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed); } QWaylandWindow::~QWaylandWindow() { - mDisplay->destroyFrameQueue(mFrameQueue); - mDisplay->handleWindowDestroyed(this); - delete mWindowDecoration; if (mSurface) @@ -117,8 +91,10 @@ QWaylandWindow::~QWaylandWindow() void QWaylandWindow::ensureSize() { - if (mBackingStore) - mBackingStore->ensureSize(); + if (mBackingStore) { + setBackingStore(mBackingStore); + mBackingStore->recreateBackBufferIfNeeded(); + } } void QWaylandWindow::initWindow() @@ -130,39 +106,65 @@ void QWaylandWindow::initWindow() initializeWlSurface(); } + 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); 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('.'), Qt::SkipEmptyParts); @@ -171,7 +173,7 @@ void QWaylandWindow::initWindow() 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); @@ -180,27 +182,43 @@ 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."); } } + // 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) - mSurface->set_buffer_scale(scale()); + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(scale())); 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() @@ -211,14 +229,28 @@ void QWaylandWindow::initializeWlSurface() 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()) @@ -238,31 +270,76 @@ bool QWaylandWindow::shouldCreateSubSurface() const return QPlatformWindow::parent() != nullptr; } +void QWaylandWindow::beginFrame() +{ + mSurfaceLock.lockForRead(); +} + +void QWaylandWindow::endFrame() +{ + mSurfaceLock.unlock(); +} + void QWaylandWindow::reset() { - delete mShellSurface; - mShellSurface = nullptr; - delete mSubSurfaceWindow; - mSubSurfaceWindow = nullptr; + closeChildPopups(); + + if (mTopPopup == this) + mTopPopup = mTransientParent && (mTransientParent->window()->type() == Qt::Popup) ? mTransientParent : nullptr; - invalidateSurface(); 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(); - QWriteLocker lock(&mSurfaceLock); - mSurface.reset(); } - 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; } - mFrameCallbackElapsedTimer.invalidate(); - mWaitingForFrameCallback = false; mFrameCallbackTimedOut = false; + mWaitingToApplyConfigure = false; + mCanResize = true; + mResizeDirty = false; + mScale = std::nullopt; + 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) @@ -296,27 +373,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 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 = QStringView{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()); + 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. + // 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 = 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(); } @@ -324,52 +408,129 @@ 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 (isExposed() && !mInResizeFromApplyConfigure && exposeGeometry != mLastExposeGeometry) sendExposeEvent(exposeGeometry); - if (mShellSurface) + if (mShellSurface) { mShellSurface->setWindowGeometry(windowContentGeometry()); + if (!qt_window_private(window())->positionAutomatic) + mShellSurface->setWindowPosition(windowGeometry().topLeft()); + } if (isOpaque() && mMask.isEmpty()) - setOpaqueArea(rect); + 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(); + 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)); @@ -389,28 +550,13 @@ void QWaylandWindow::sendExposeEvent(const QRect &rect) mLastExposeGeometry = rect; } - -static QList<QPointer<QWaylandWindow>> activePopups; - -void QWaylandWindow::closePopups(QWaylandWindow *parent) -{ - while (!activePopups.isEmpty()) { - auto popup = activePopups.takeLast(); - if (popup.isNull()) - continue; - if (popup.data() == parent) - return; - popup->reset(); - } -} - QPlatformScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const { + QReadLocker lock(&mSurfaceLock); if (mSurface) { if (auto *screen = mSurface->oldestEnteredScreen()) return screen; } - return QPlatformWindow::screen(); } @@ -422,18 +568,17 @@ void QWaylandWindow::setVisible(bool visible) 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()); - closePopups(this); reset(); } } @@ -454,29 +599,37 @@ void QWaylandWindow::lower() void QWaylandWindow::setMask(const QRegion &mask) { + QReadLocker locker(&mSurfaceLock); + if (!mSurface) + return; + if (mMask == mask) return; mMask = mask; - if (!mSurface) - return; - - if (mMask.isEmpty()) { - mSurface->set_input_region(nullptr); + updateInputRegion(); - if (isOpaque()) + if (isOpaque()) { + if (mMask.isEmpty()) setOpaqueArea(QRect(QPoint(0, 0), geometry().size())); - } else { - struct ::wl_region *region = mDisplay->createRegion(mMask); - mSurface->set_input_region(region); - wl_region_destroy(region); - - if (isOpaque()) + else setOpaqueArea(mMask); } +} - mSurface->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() @@ -493,12 +646,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); @@ -509,8 +674,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())); @@ -532,11 +702,12 @@ void QWaylandWindow::applyConfigure() void QWaylandWindow::sendRecursiveExposeEvent() { - if (!window()->isVisible()) - return; - sendExposeEvent(QRect(QPoint(), geometry().size())); + if (!isExposed()) + sendExposeEvent(QRect()); + else + sendExposeEvent(QRect(QPoint(), geometry().size())); - for (QWaylandSubSurface *subSurface : qAsConst(mChildren)) { + for (QWaylandSubSurface *subSurface : std::as_const(mChildren)) { auto subWindow = subSurface->window(); subWindow->sendRecursiveExposeEvent(); } @@ -544,12 +715,20 @@ void QWaylandWindow::sendRecursiveExposeEvent() void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { - Q_ASSERT(!buffer->committed()); + QReadLocker locker(&mSurfaceLock); + if (mSurface == nullptr) + return; + if (buffer) { + Q_ASSERT(!buffer->committed()); handleUpdate(); - buffer->setBusy(); - - mSurface->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 { mSurface->attach(nullptr, 0, 0); } @@ -563,11 +742,20 @@ void QWaylandWindow::attachOffset(QWaylandBuffer *buffer) void QWaylandWindow::damage(const QRect &rect) { - const int s = scale(); - if (mDisplay->compositorVersion() >= 4) - mSurface->damage_buffer(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()); - else + 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) @@ -575,21 +763,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()); @@ -597,14 +779,21 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) qCDebug(lcWaylandBackingstore) << "Buffer already committed, ignoring."; return; } + + QReadLocker locker(&mSurfaceLock); if (!mSurface) return; attachOffset(buffer); - if (mDisplay->compositorVersion() >= 4) { - const int s = scale(); - for (const QRect &rect: damage) - mSurface->damage_buffer(s * rect.x(), s * rect.y(), s * rect.width(), s * 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()); @@ -616,47 +805,61 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) void QWaylandWindow::commit() { - mSurface->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); - window->handleFrameCallback(); + window->handleFrameCallback(callback); } }; -void QWaylandWindow::handleFrameCallback() +void QWaylandWindow::handleFrameCallback(wl_callback* callback) { + 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; mFrameCallbackElapsedTimer.invalidate(); // The rest can wait until we can run it on the correct thread - if (!mWaitingForUpdateDelivery) { - auto doHandleExpose = [this]() { - bool wasExposed = isExposed(); - mFrameCallbackTimedOut = false; - if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? - sendExposeEvent(QRect(QPoint(), geometry().size())); - if (wasExposed && hasPendingUpdateRequest()) - deliverUpdateRequest(); - - mWaitingForUpdateDelivery = false; - }; - + if (mWaitingForUpdateDelivery.testAndSetAcquire(false, true)) { // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() // in the single-threaded case. - mWaitingForUpdateDelivery = true; - QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); + 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(); + } bool QWaylandWindow::waitForFrameSync(int timeout) { - QMutexLocker locker(mFrameQueue.mutex); - mDisplay->dispatchQueueWhile(mFrameQueue.queue, [&]() { return mWaitingForFrameCallback; }, timeout); + QMutexLocker locker(&mFrameSyncMutex); + + QDeadlineTimer deadline(timeout); + while (mWaitingForFrameCallback && mFrameSyncWait.wait(&mFrameSyncMutex, deadline)) { } if (mWaitingForFrameCallback) { qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; @@ -670,9 +873,24 @@ bool QWaylandWindow::waitForFrameSync(int timeout) 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; + propagateSizeHints(); + setGeometry(geometry().marginsRemoved(oldMargins).marginsAdded(margins)); } /*! @@ -680,7 +898,20 @@ QMargins QWaylandWindow::frameMargins() const */ QSize QWaylandWindow::surfaceSize() const { - return geometry().marginsAdded(frameMargins()).size(); + 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; } /*! @@ -689,7 +920,8 @@ QSize QWaylandWindow::surfaceSize() const */ QRect QWaylandWindow::windowContentGeometry() const { - return QRect(QPoint(), surfaceSize()); + const QMargins margins = windowContentMargins(); + return QRect(QPoint(margins.left(), margins.top()), surfaceSize().shrunkBy(margins)); } /*! @@ -701,12 +933,13 @@ QRect QWaylandWindow::windowContentGeometry() const */ QPointF QWaylandWindow::mapFromWlSurface(const QPointF &surfacePosition) const { - const QMargins margins = frameMargins(); + const QMargins margins = clientSideMargins(); return QPointF(surfacePosition.x() - margins.left(), surfacePosition.y() - margins.top()); } -wl_surface *QWaylandWindow::wlSurface() +wl_surface *QWaylandWindow::wlSurface() const { + QReadLocker locker(&mSurfaceLock); return mSurface ? mSurface->object() : nullptr; } @@ -715,6 +948,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; @@ -731,33 +973,50 @@ QWaylandScreen *QWaylandWindow::waylandScreen() const 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(); } mSurface->set_buffer_transform(transform); - // set_buffer_transform is double buffered, we need to commit. - mSurface->commit(); } void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask) @@ -779,10 +1038,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; @@ -805,12 +1072,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."; @@ -828,6 +1100,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. @@ -839,19 +1127,29 @@ bool QWaylandWindow::createDecoration() return false; } mWindowDecoration->setWaylandWindow(this); + mWindowDecorationEnabled = true; } } else { - delete mWindowDecoration; - mWindowDecoration = nullptr; + mWindowDecorationEnabled = false; } - if (hadDecoration != (bool)mWindowDecoration) { - for (QWaylandSubSurface *subsurf : qAsConst(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; @@ -859,7 +1157,7 @@ bool QWaylandWindow::createDecoration() QWaylandAbstractDecoration *QWaylandWindow::decoration() const { - return mWindowDecoration; + return mWindowDecorationEnabled ? mWindowDecoration : nullptr; } static QWaylandWindow *closestShellSurfaceWindow(QWindow *window) @@ -875,14 +1173,24 @@ 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. if (auto transientParent = closestShellSurfaceWindow(window()->transientParent())) return transientParent; - if (QGuiApplication::focusWindow() && (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup)) - return closestShellSurfaceWindow(QGuiApplication::focusWindow()); + if (window()->type() == Qt::Popup) { + if (mTopPopup) + return mTopPopup; + } + + if (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup) + return display()->lastInputWindow(); return nullptr; } @@ -890,7 +1198,7 @@ QWaylandWindow *QWaylandWindow::transientParent() const void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e) { if (e.type == QEvent::Leave) { - if (mWindowDecoration) { + if (mWindowDecorationEnabled) { if (mMouseEventsInContentArea) QWindowSystemInterface::handleLeaveEvent(window()); } else { @@ -902,7 +1210,7 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan return; } - if (mWindowDecoration) { + if (mWindowDecorationEnabled) { handleMouseEventWithDecoration(inputDevice, e); } else { switch (e.type) { @@ -917,7 +1225,7 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan case QEvent::Wheel: QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global, e.pixelDelta, e.angleDelta, e.modifiers, - e.phase, e.source, false); + e.phase, e.source, e.inverted); break; default: Q_UNREACHABLE(); @@ -926,16 +1234,143 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan #if QT_CONFIG(cursor) if (e.type == QEvent::Enter) { - QRect contentGeometry = windowContentGeometry().marginsRemoved(frameMargins()); + QRect contentGeometry = QRect(QPoint(), surfaceSize()).marginsRemoved(clientSideMargins()); if (contentGeometry.contains(e.local.toPoint())) restoreMouseCursor(inputDevice); } #endif } -bool QWaylandWindow::touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::TouchPointState state, Qt::KeyboardModifiers mods) +#ifndef QT_NO_GESTURES +void QWaylandWindow::handleSwipeGesture(QWaylandInputDevice *inputDevice, + const QWaylandPointerGestureSwipeEvent &e) +{ + 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; + } +} + +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); } @@ -951,11 +1386,11 @@ 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) { const QPointF localTranslated = mapFromWlSurface(e.local); QPointF globalTranslated = e.global; @@ -981,7 +1416,7 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, localTranslated, globalTranslated, e.pixelDelta, e.angleDelta, e.modifiers, - e.phase, e.source, false); + e.phase, e.source, e.inverted); break; } default: @@ -1002,18 +1437,63 @@ void QWaylandWindow::handleScreensChanged() { QPlatformScreen *newScreen = calculateScreenFromSurfaceEvents(); - if (newScreen == mLastReportedScreen) + if (newScreen->screen() == window()->screen()) return; QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); - mLastReportedScreen = newScreen; - int scale = newScreen->isPlaceholder() ? 1 : static_cast<QWaylandScreen *>(newScreen)->scale(); - if (scale != mScale) { - mScale = scale; - if (mSurface && mDisplay->compositorVersion() >= 3) - mSurface->set_buffer_scale(mScale); - ensureSize(); + 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 = screen()->isPlaceholder() ? 1 : static_cast<QWaylandScreen *>(screen())->scale(); + setScale(scale); +} + +void QWaylandWindow::setScale(qreal newScale) +{ + if (mScale.has_value() && qFuzzyCompare(mScale.value(), newScale)) + return; + mScale = newScale; + + QWindowSystemInterface::handleWindowDevicePixelRatioChanged(window()); + if (mSurface) { + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(newScale)); + } + ensureSize(); + + if (isExposed()) { + // redraw at the new DPR + window()->requestUpdate(); + sendExposeEvent(QRect(QPoint(), geometry().size())); } } @@ -1026,13 +1506,17 @@ void QWaylandWindow::setMouseCursor(QWaylandInputDevice *device, const QCursor & 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()"; + if (mShellSurface) + mShellSurface->requestActivate(); } bool QWaylandWindow::isExposed() const @@ -1057,14 +1541,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 mScale.value_or(waylandScreen() ? waylandScreen()->scale() : 1); } bool QWaylandWindow::setMouseGrabEnabled(bool grab) @@ -1078,10 +1562,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; } @@ -1118,24 +1620,36 @@ 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() != mFrameCallbackCheckIntervalTimerId) return; - bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); - if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { - killTimer(mFrameCallbackCheckIntervalTimerId); - mFrameCallbackCheckIntervalTimerId = -1; - } - if (mFrameCallbackElapsedTimer.isValid() && callbackTimerExpired) { - mFrameCallbackElapsedTimer.invalidate(); + { + QMutexLocker lock(&mFrameSyncMutex); - qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; - mFrameCallbackTimedOut = true; - mWaitingForUpdate = false; - sendExposeEvent(QRect()); + bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); + if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; + } + if (!mFrameCallbackElapsedTimer.isValid() || !callbackTimerExpired) { + return; + } + mFrameCallbackElapsedTimer.invalidate(); } + + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + sendExposeEvent(QRect()); } void QWaylandWindow::requestUpdate() @@ -1144,8 +1658,11 @@ void QWaylandWindow::requestUpdate() Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA // If we have a frame callback all is good and will be taken care of there - if (mWaitingForFrameCallback) - return; + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log @@ -1158,7 +1675,12 @@ void QWaylandWindow::requestUpdate() // so use invokeMethod to delay the delivery a bit. QMetaObject::invokeMethod(this, [this] { // Things might have changed in the meantime - if (hasPendingUpdateRequest() && !mWaitingForFrameCallback) + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } + if (hasPendingUpdateRequest()) deliverUpdateRequest(); }, Qt::QueuedConnection); } @@ -1169,19 +1691,18 @@ void QWaylandWindow::requestUpdate() void QWaylandWindow::handleUpdate() { qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread(); + // TODO: Should sync subsurfaces avoid requesting frame callbacks? QReadLocker lock(&mSurfaceLock); if (!mSurface) return; - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - } + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; - QMutexLocker locker(mFrameQueue.mutex); struct ::wl_surface *wrappedSurface = reinterpret_cast<struct ::wl_surface *>(wl_proxy_create_wrapper(mSurface->object())); - wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mFrameQueue.queue); + wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mDisplay->frameEventQueue()); mFrameCallback = wl_surface_frame(wrappedSurface); wl_proxy_wrapper_destroy(wrappedSurface); wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); @@ -1191,6 +1712,8 @@ void QWaylandWindow::handleUpdate() // Start a timer for handling the case when the compositor stops sending frame callbacks. if (mFrameCallbackTimeout > 0) { QMetaObject::invokeMethod(this, [this] { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) { if (mFrameCallbackCheckIntervalTimerId < 0) mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); @@ -1220,15 +1743,21 @@ void QWaylandWindow::propagateSizeHints() bool QWaylandWindow::startSystemResize(Qt::Edges edges) { - if (auto *seat = display()->lastInputDevice()) - return mShellSurface && mShellSurface->resize(seat, edges); + if (auto *seat = display()->lastInputDevice()) { + bool rc = mShellSurface && mShellSurface->resize(seat, edges); + seat->handleEndDrag(); + return rc; + } return false; } bool QtWaylandClient::QWaylandWindow::startSystemMove() { - 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; } @@ -1239,16 +1768,71 @@ bool QWaylandWindow::isOpaque() const void QWaylandWindow::setOpaqueArea(const QRegion &opaqueArea) { - if (opaqueArea == mOpaqueArea || !mSurface) + const QRegion translatedOpaqueArea = opaqueArea.translated(clientSideMargins().left(), clientSideMargins().top()); + + if (translatedOpaqueArea == mOpaqueArea || !mSurface) return; - mOpaqueArea = opaqueArea; + mOpaqueArea = translatedOpaqueArea; - struct ::wl_region *region = mDisplay->createRegion(opaqueArea); + 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" |