summaryrefslogtreecommitdiffstats
path: root/src/client/qwaylandwindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/qwaylandwindow.cpp')
-rw-r--r--src/client/qwaylandwindow.cpp1152
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 &region)
-{
- 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"