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