qtbase/src/plugins/platforms/wayland/qwaylandwindow.cpp
Vlad Zahorodnii 2c9580bf63 Client: Fix initialization of QWaylandWindow::mScale
QWaylandWindow initializes mScale to the scale factor of the currently
assigned screen when it's constructed. However, it doesn't keep track of
subsequent screen changes that may occur afterwards before
QWindow::setVisible(true). For example

    window.setScreen(foo);
    window.create();
    window.setScreen(bar);
    window.show();

In that case, the value of mScale will correspond to the scale factor
of output "foo" when show() is called.

A better place to initialize mScale is when the show() function is
called. Furthermore, since there's wl_surface.preferred_buffer_scale and
fractional_scale_v1.preferred_scale events, QWaylandWindow could stop
guessing the scale factor and rely on the compositor telling us that
information. However, it's important that the compositor sends the
preferred buffer scale before the first configure event arrives.

Fixes: QTBUG-124839
Pick-to: 6.7
Change-Id: I842aaa352d9cb1e53158f64f2ec0cd3734f7ecf3
Reviewed-by: Liang Qi <liang.qi@qt.io>
Reviewed-by: David Edmundson <davidedmundson@kde.org>
2024-05-24 14:23:57 +03:00

1842 lines
60 KiB
C++

// 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"
#include "qwaylandabstractdecoration_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>
#include <QtCore/QRegularExpression>
#include <QtGui/QWindow>
#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)
, 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;
}
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()
{
delete mWindowDecoration;
if (mSurface)
reset();
const QWindow *parent = window();
const auto tlw = QGuiApplication::topLevelWindows();
for (QWindow *w : tlw) {
if (w->transientParent() == parent)
QWindowSystemInterface::handleCloseEvent(w);
}
if (mMouseGrab == this) {
mMouseGrab = nullptr;
}
}
void QWaylandWindow::ensureSize()
{
if (mBackingStore) {
setBackingStore(mBackingStore);
mBackingStore->recreateBackBufferIfNeeded();
}
}
void QWaylandWindow::initWindow()
{
if (window()->type() == Qt::Desktop)
return;
if (!mSurface) {
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(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 = 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.
//
// 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()) {
mShellSurface->setAppId(QGuiApplication::desktopFileName());
} else {
QFileInfo fi = QFileInfo(QCoreApplication::instance()->applicationFilePath());
QStringList domainName =
QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'),
Qt::SkipEmptyParts);
if (domainName.isEmpty()) {
mShellSurface->setAppId(fi.baseName());
} else {
QString appId;
for (int i = 0; i < domainName.size(); ++i)
appId.prepend(QLatin1Char('.')).prepend(domainName.at(i));
appId.append(fi.baseName());
mShellSurface->setAppId(appId);
}
}
// 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 (mViewport)
updateViewport();
else if (mSurface->version() >= 3)
mSurface->set_buffer_scale(std::ceil(scale()));
setWindowFlags(window()->flags());
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()
{
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 (!shellIntegration())
return false;
if (shouldCreateSubSurface())
return false;
if (window()->inherits("QShapedPixmapWindow"))
return false;
if (qEnvironmentVariableIsSet("QT_WAYLAND_USE_BYPASSWINDOWMANAGERHINT"))
return !(window()->flags() & Qt::BypassWindowManagerHint);
return true;
}
bool QWaylandWindow::shouldCreateSubSurface() const
{
return QPlatformWindow::parent() != nullptr;
}
void QWaylandWindow::beginFrame()
{
mSurfaceLock.lockForRead();
}
void QWaylandWindow::endFrame()
{
mSurfaceLock.unlock();
}
void QWaylandWindow::reset()
{
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();
}
{
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;
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)
{
if (auto *s = QWaylandSurface::fromWlSurface(surface))
return s->m_window;
return nullptr;
}
WId QWaylandWindow::winId() const
{
return mWindowId;
}
void QWaylandWindow::setParent(const QPlatformWindow *parent)
{
if (!window()->isVisible())
return;
QWaylandWindow *oldparent = mSubSurfaceWindow ? mSubSurfaceWindow->parent() : nullptr;
if (oldparent == parent)
return;
if (mSubSurfaceWindow && parent) { // new parent, but we were a subsurface already
delete mSubSurfaceWindow;
QWaylandWindow *p = const_cast<QWaylandWindow *>(static_cast<const QWaylandWindow *>(parent));
mSubSurfaceWindow = new QWaylandSubSurface(this, p, mDisplay->createSubSurface(this, p));
} else { // we're changing role, need to make a new wl_surface
reset();
initWindow();
}
}
QString QWaylandWindow::windowTitle() const
{
return mWindowTitle;
}
void QWaylandWindow::setWindowTitle(const QString &title)
{
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)";
}
mWindowTitle = truncated.toString();
if (mShellSurface)
mShellSurface->setTitle(mWindowTitle);
if (mWindowDecorationEnabled && window()->isVisible())
mWindowDecoration->update();
}
void QWaylandWindow::setWindowIcon(const QIcon &icon)
{
mWindowIcon = icon;
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(rect);
if (mViewport)
updateViewport();
if (mSubSurfaceWindow) {
QMargins m = static_cast<QWaylandWindow *>(QPlatformWindow::parent())->clientSideMargins();
mSubSurfaceWindow->set_position(rect.x() + m.left(), rect.y() + m.top());
QWaylandWindow *parentWindow = mSubSurfaceWindow->parent();
if (parentWindow && parentWindow->isExposed()) {
QRect parentExposeGeometry(QPoint(), parentWindow->geometry().size());
parentWindow->sendExposeEvent(parentExposeGeometry);
}
}
}
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 (mWindowDecorationEnabled)
mWindowDecoration->update();
if (mResizeAfterSwap && windowType() == Egl && mSentInitialResize) {
QMutexLocker lock(&mResizeLock);
mResizeDirty = true;
} else {
QWindowSystemInterface::handleGeometryChange(window(), geometry());
}
mSentInitialResize = true;
}
QRect exposeGeometry(QPoint(), geometry().size());
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 = 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)
{
if (!(mShellSurface && mShellSurface->handleExpose(rect)))
QWindowSystemInterface::handleExposeEvent(window(), rect);
else
qCDebug(lcQpaWayland) << "sendExposeEvent: intercepted by shell extension, not sending";
mLastExposeGeometry = rect;
}
QPlatformScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const
{
QReadLocker lock(&mSurfaceLock);
if (mSurface) {
if (auto *screen = mSurface->oldestEnteredScreen())
return screen;
}
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) {
initWindow();
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());
reset();
}
}
void QWaylandWindow::raise()
{
if (mShellSurface)
mShellSurface->raise();
}
void QWaylandWindow::lower()
{
if (mShellSurface)
mShellSurface->lower();
}
void QWaylandWindow::setMask(const QRegion &mask)
{
QReadLocker locker(&mSurfaceLock);
if (!mSurface)
return;
if (mMask == mask)
return;
mMask = mask;
updateInputRegion();
if (isOpaque()) {
if (mMask.isEmpty())
setOpaqueArea(QRect(QPoint(0, 0), geometry().size()));
else
setOpaqueArea(mMask);
}
}
void QWaylandWindow::setAlertState(bool enabled)
{
if (mShellSurface)
mShellSurface->setAlertState(enabled);
}
bool QWaylandWindow::isAlertState() const
{
if (mShellSurface)
return mShellSurface->isAlertState();
return false;
}
void QWaylandWindow::applyConfigureWhenPossible()
{
QMutexLocker resizeLocker(&mResizeLock);
if (!mWaitingToApplyConfigure) {
mWaitingToApplyConfigure = true;
QMetaObject::invokeMethod(this, "applyConfigure", Qt::QueuedConnection);
}
}
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);
mCanResize = canResize;
if (canResize) {
if (mResizeDirty) {
QWindowSystemInterface::handleGeometryChange(window(), geometry());
}
if (mWaitingToApplyConfigure) {
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()));
}
}
}
void QWaylandWindow::applyConfigure()
{
QMutexLocker lock(&mResizeLock);
if (mCanResize || !mSentInitialResize)
doApplyConfigure();
lock.unlock();
sendRecursiveExposeEvent();
QWindowSystemInterface::flushWindowSystemEvents();
}
void QWaylandWindow::sendRecursiveExposeEvent()
{
if (!isExposed())
sendExposeEvent(QRect());
else
sendExposeEvent(QRect(QPoint(), geometry().size()));
for (QWaylandSubSurface *subSurface : std::as_const(mChildren)) {
auto subWindow = subSurface->window();
subWindow->sendRecursiveExposeEvent();
}
}
void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y)
{
QReadLocker locker(&mSurfaceLock);
if (mSurface == nullptr)
return;
if (buffer) {
Q_ASSERT(!buffer->committed());
handleUpdate();
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);
}
}
void QWaylandWindow::attachOffset(QWaylandBuffer *buffer)
{
attach(buffer, mOffset.x(), mOffset.y());
mOffset = QPoint();
}
void QWaylandWindow::damage(const QRect &rect)
{
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)
{
if (isExposed()) {
commit(buffer, damage);
} else {
if (mQueuedBuffer) {
mQueuedBuffer->setBusy(false);
}
mQueuedBuffer = buffer;
mQueuedBuffer->setBusy(true);
mQueuedBufferDamage = damage;
}
}
void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage)
{
Q_ASSERT(isExposed());
if (buffer->committed()) {
qCDebug(lcWaylandBackingstore) << "Buffer already committed, ignoring.";
return;
}
QReadLocker locker(&mSurfaceLock);
if (!mSurface)
return;
attachOffset(buffer);
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();
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(time);
auto *window = static_cast<QWaylandWindow*>(data);
window->handleFrameCallback(callback);
}
};
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.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();
}
bool QWaylandWindow::waitForFrameSync(int 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";
mFrameCallbackTimedOut = true;
mWaitingForUpdate = false;
sendExposeEvent(QRect());
}
return !mWaitingForFrameCallback;
}
QMargins QWaylandWindow::frameMargins() const
{
if (mWindowDecorationEnabled)
return mWindowDecoration->margins();
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
{
return mShellSurface;
}
std::any QWaylandWindow::_surfaceRole() const
{
if (mSubSurfaceWindow)
return mSubSurfaceWindow->object();
if (mShellSurface)
return mShellSurface->surfaceRole();
return {};
}
QWaylandSubSurface *QWaylandWindow::subSurfaceWindow() const
{
return mSubSurfaceWindow;
}
QWaylandScreen *QWaylandWindow::waylandScreen() const
{
auto *platformScreen = QPlatformWindow::screen();
Q_ASSERT(platformScreen);
if (platformScreen->isPlaceholder())
return nullptr;
return static_cast<QWaylandScreen *>(platformScreen);
}
void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation)
{
mLastReportedContentOrientation = orientation;
updateBufferTransform();
}
void QWaylandWindow::updateBufferTransform()
{
QReadLocker locker(&mSurfaceLock);
if (mSurface == nullptr || mSurface->version() < 2)
return;
wl_output_transform transform;
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);
}
void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask)
{
if (mShellSurface)
mShellSurface->setContentOrientationMask(mask);
}
void QWaylandWindow::setWindowState(Qt::WindowStates states)
{
if (mShellSurface)
mShellSurface->requestWindowStates(states);
}
void QWaylandWindow::setWindowFlags(Qt::WindowFlags flags)
{
if (mShellSurface)
mShellSurface->setWindowFlags(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;
static bool decorationPluginFailed = false;
bool decoration = false;
switch (window()->type()) {
case Qt::Window:
case Qt::Widget:
case Qt::Dialog:
case Qt::Tool:
case Qt::Drawer:
decoration = true;
break;
default:
break;
}
if (mFlags & Qt::FramelessWindowHint)
decoration = false;
if (mFlags & Qt::BypassWindowManagerHint)
decoration = false;
if (mSubSurfaceWindow)
decoration = false;
if (!mShellSurface || !mShellSurface->wantsDecorations())
decoration = false;
bool hadDecoration = mWindowDecorationEnabled;
if (decoration && !decorationPluginFailed) {
if (!mWindowDecorationEnabled) {
if (mWindowDecoration) {
delete mWindowDecoration;
mWindowDecoration = nullptr;
}
QStringList decorations = QWaylandDecorationFactory::keys();
if (decorations.empty()) {
qWarning() << "No decoration plugins available. Running with no decorations.";
decorationPluginFailed = true;
return false;
}
QString targetKey;
QByteArray decorationPluginName = qgetenv("QT_WAYLAND_DECORATION");
if (!decorationPluginName.isEmpty()) {
targetKey = QString::fromLocal8Bit(decorationPluginName);
if (!decorations.contains(targetKey)) {
qWarning() << "Requested decoration " << targetKey << " not found, falling back to default";
targetKey = QString(); // fallthrough
}
}
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.
mWindowDecoration = QWaylandDecorationFactory::create(targetKey, QStringList());
if (!mWindowDecoration) {
qWarning() << "Could not create decoration from factory! Running with no decorations.";
decorationPluginFailed = true;
return false;
}
mWindowDecoration->setWaylandWindow(this);
mWindowDecorationEnabled = true;
}
} else {
mWindowDecorationEnabled = false;
}
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());
}
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;
}
QWaylandAbstractDecoration *QWaylandWindow::decoration() const
{
return mWindowDecorationEnabled ? mWindowDecoration : nullptr;
}
static QWaylandWindow *closestShellSurfaceWindow(QWindow *window)
{
while (window) {
auto w = static_cast<QWaylandWindow *>(window->handle());
if (w && w->shellSurface())
return w;
window = window->transientParent() ? window->transientParent() : window->parent();
}
return nullptr;
}
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 (window()->type() == Qt::Popup) {
if (mTopPopup)
return mTopPopup;
}
if (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup)
return display()->lastInputWindow();
return nullptr;
}
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 (mWindowDecorationEnabled) {
handleMouseEventWithDecoration(inputDevice, e);
} else {
switch (e.type) {
case QEvent::Enter:
QWindowSystemInterface::handleEnterEvent(window(), e.local, e.global);
break;
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 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 == QEvent::Enter) {
QRect contentGeometry = QRect(QPoint(), surfaceSize()).marginsRemoved(clientSideMargins());
if (contentGeometry.contains(e.local.toPoint()))
restoreMouseCursor(inputDevice);
}
#endif
}
#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)
{
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);
}
void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e)
{
if (mMousePressedInContentArea == Qt::NoButton &&
mWindowDecoration->handleMouse(inputDevice, e.local, e.global, e.buttons, e.modifiers)) {
if (mMouseEventsInContentArea) {
QWindowSystemInterface::handleLeaveEvent(window());
mMouseEventsInContentArea = false;
}
return;
}
QMargins marg = clientSideMargins();
QRect windowRect(0 + marg.left(),
0 + marg.top(),
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;
globalTranslated.setX(globalTranslated.x() - marg.left());
globalTranslated.setY(globalTranslated.y() - marg.top());
if (!mMouseEventsInContentArea) {
#if QT_CONFIG(cursor)
restoreMouseCursor(inputDevice);
#endif
QWindowSystemInterface::handleEnterEvent(window());
}
switch (e.type) {
case QEvent::Enter:
QWindowSystemInterface::handleEnterEvent(window(), localTranslated, globalTranslated);
break;
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 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;
mMousePressedInContentArea = e.buttons;
} else {
if (mMouseEventsInContentArea) {
QWindowSystemInterface::handleLeaveEvent(window());
mMouseEventsInContentArea = false;
}
}
}
void QWaylandWindow::handleScreensChanged()
{
QPlatformScreen *newScreen = calculateScreenFromSurfaceEvents();
if (newScreen == mLastReportedScreen)
return;
if (!newScreen->isPlaceholder() && !newScreen->QPlatformScreen::screen())
mDisplay->forceRoundTrip();
QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen());
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 (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()));
}
}
#if QT_CONFIG(cursor)
void QWaylandWindow::setMouseCursor(QWaylandInputDevice *device, const QCursor &cursor)
{
int fallbackBufferScale = int(devicePixelRatio());
device->setCursor(&cursor, {}, fallbackBufferScale);
}
void QWaylandWindow::restoreMouseCursor(QWaylandInputDevice *device)
{
if (const QCursor *overrideCursor = QGuiApplication::overrideCursor())
setMouseCursor(device, *overrideCursor);
else
setMouseCursor(device, window()->cursor());
}
#endif
void QWaylandWindow::requestActivateWindow()
{
if (mShellSurface)
mShellSurface->requestActivate();
}
bool QWaylandWindow::isExposed() const
{
if (!window()->isVisible())
return false;
if (mFrameCallbackTimedOut)
return false;
if (mShellSurface)
return mShellSurface->isExposed();
if (mSubSurfaceWindow)
return mSubSurfaceWindow->parent()->isExposed();
return !(shouldCreateShellSurface() || shouldCreateSubSurface());
}
bool QWaylandWindow::isActive() const
{
return mDisplay->isWindowActivated(this);
}
qreal QWaylandWindow::scale() const
{
return devicePixelRatio();
}
qreal QWaylandWindow::devicePixelRatio() const
{
return mScale.value_or(waylandScreen() ? waylandScreen()->scale() : 1);
}
bool QWaylandWindow::setMouseGrabEnabled(bool grab)
{
if (window()->type() != Qt::Popup) {
qWarning("This plugin supports grabbing the mouse only for popup windows");
return false;
}
mMouseGrab = grab ? this : nullptr;
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();
Qt::WindowStates statesWithoutActive = states & ~Qt::WindowActive;
Qt::WindowStates lastStatesWithoutActive = mLastReportedWindowStates & ~Qt::WindowActive;
QWindowSystemInterface::handleWindowStateChanged(window(), statesWithoutActive,
lastStatesWithoutActive);
mLastReportedWindowStates = states;
}
void QWaylandWindow::sendProperty(const QString &name, const QVariant &value)
{
m_properties.insert(name, value);
QWaylandNativeInterface *nativeInterface = static_cast<QWaylandNativeInterface *>(
QGuiApplication::platformNativeInterface());
nativeInterface->emitWindowPropertyChanged(this, name);
if (mShellSurface)
mShellSurface->sendProperty(name, value);
}
void QWaylandWindow::setProperty(const QString &name, const QVariant &value)
{
m_properties.insert(name, value);
QWaylandNativeInterface *nativeInterface = static_cast<QWaylandNativeInterface *>(
QGuiApplication::platformNativeInterface());
nativeInterface->emitWindowPropertyChanged(this, name);
}
QVariantMap QWaylandWindow::properties() const
{
return m_properties;
}
QVariant QWaylandWindow::property(const QString &name)
{
return m_properties.value(name);
}
QVariant QWaylandWindow::property(const QString &name, const QVariant &defaultValue)
{
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;
{
QMutexLocker lock(&mFrameSyncMutex);
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()
{
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
{
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
// 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
{
QMutexLocker locker(&mFrameSyncMutex);
if (mWaitingForFrameCallback)
return;
}
if (hasPendingUpdateRequest())
deliverUpdateRequest();
}, Qt::QueuedConnection);
}
// Should be called whenever we commit a buffer (directly through wl_surface.commit or indirectly
// with eglSwapBuffers) to know when it's time to commit the next one.
// Can be called from the render thread (without locking anything) so make sure to not make races in this method.
void QWaylandWindow::handleUpdate()
{
qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread();
// TODO: Should sync subsurfaces avoid requesting frame callbacks?
QReadLocker lock(&mSurfaceLock);
if (!mSurface)
return;
QMutexLocker locker(&mFrameSyncMutex);
if (mWaitingForFrameCallback)
return;
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;
// 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);
mFrameCallbackElapsedTimer.start();
}
}, Qt::QueuedConnection);
}
}
void QWaylandWindow::deliverUpdateRequest()
{
qCDebug(lcWaylandBackingstore) << "deliverUpdateRequest";
mWaitingForUpdate = true;
QPlatformWindow::deliverUpdateRequest();
}
void QWaylandWindow::addAttachOffset(const QPoint point)
{
mOffset += point;
}
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()
{
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"