1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwaylandwindow_p.h"
5
6#include "qwaylandbuffer_p.h"
7#include "qwaylandcursor_p.h"
8#include "qwaylanddisplay_p.h"
9#include "qwaylandsurface_p.h"
10#include "qwaylandinputdevice_p.h"
11#include "qwaylandfractionalscale_p.h"
12#include "qwaylandscreen_p.h"
13#include "qwaylandshellsurface_p.h"
14#include "qwaylandsubsurface_p.h"
15#include "qwaylandabstractdecoration_p.h"
16#include "qwaylandplatformservices_p.h"
17#include "qwaylandnativeinterface_p.h"
18#include "qwaylanddecorationfactory_p.h"
19#include "qwaylandshmbackingstore_p.h"
20#include "qwaylandshellintegration_p.h"
21#include "qwaylandviewport_p.h"
22#include "qwaylandcolormanagement_p.h"
23
24#include <QtCore/QFileInfo>
25#include <QtCore/QPointer>
26#include <QtCore/QRegularExpression>
27#include <QtGui/QWindow>
28
29#include <QGuiApplication>
30#include <qpa/qwindowsysteminterface.h>
31#include <QtGui/private/qguiapplication_p.h>
32#include <QtGui/private/qwindow_p.h>
33
34#include <QtCore/QDebug>
35#include <QtCore/QThread>
36#include <QtCore/private/qthread_p.h>
37
38#include <QtWaylandClient/private/qwayland-fractional-scale-v1.h>
39
40QT_BEGIN_NAMESPACE
41
42using namespace Qt::StringLiterals;
43
44namespace QtWaylandClient {
45
46Q_LOGGING_CATEGORY(lcWaylandBackingstore, "qt.qpa.wayland.backingstore")
47
48QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr;
49QWaylandWindow *QWaylandWindow::mTopPopup = nullptr;
50
51QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display)
52 : QPlatformWindow(window)
53 , mDisplay(display)
54 , mSurfaceLock(QReadWriteLock::Recursive)
55 , mShellIntegration(display->shellIntegration())
56 , mSurfaceFormat(window->requestedFormat())
57{
58 {
59 bool ok;
60 int frameCallbackTimeout = qEnvironmentVariableIntValue(varName: "QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", ok: &ok);
61 if (ok)
62 mFrameCallbackTimeout = frameCallbackTimeout;
63 }
64
65 initializeWlSurface();
66 mFlags = window->flags();
67
68 setWindowIcon(window->icon());
69
70 connect(this, &QWaylandWindow::wlSurfaceCreated, this,
71 &QNativeInterface::Private::QWaylandWindow::surfaceCreated);
72 connect(this, &QWaylandWindow::wlSurfaceDestroyed, this,
73 &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed);
74}
75
76QWaylandWindow::~QWaylandWindow()
77{
78 mWindowDecoration.reset();
79 reset();
80
81 const QWindow *parent = window();
82 const auto tlw = QGuiApplication::topLevelWindows();
83 for (QWindow *w : tlw) {
84 if (w->transientParent() == parent)
85 QWindowSystemInterface::handleCloseEvent(w);
86 }
87
88 if (mMouseGrab == this) {
89 mMouseGrab = nullptr;
90 }
91}
92
93void QWaylandWindow::ensureSize()
94{
95 if (mBackingStore) {
96 setBackingStore(mBackingStore);
97 mBackingStore->recreateBackBufferIfNeeded();
98 }
99}
100
101void QWaylandWindow::initWindow()
102{
103 resetFrameCallback();
104
105 if (window()->type() == Qt::Desktop)
106 return;
107
108 if (shouldCreateSubSurface()) {
109 Q_ASSERT(!mSubSurfaceWindow);
110
111 auto *parent = static_cast<QWaylandWindow *>(QPlatformWindow::parent());
112 if (!parent->mSurface)
113 parent->initializeWlSurface();
114 if (parent->wlSurface()) {
115 if (::wl_subsurface *subsurface = mDisplay->createSubSurface(this, parent))
116 mSubSurfaceWindow = new QWaylandSubSurface(this, parent, subsurface);
117 }
118 } else if (shouldCreateShellSurface()) {
119 Q_ASSERT(!mShellSurface);
120 Q_ASSERT(mShellIntegration);
121 mTransientParent = guessTransientParent();
122 if (mTransientParent) {
123 if (window()->type() == Qt::Popup) {
124 if (mTopPopup && mTopPopup != mTransientParent) {
125 qCWarning(lcQpaWayland) << "Creating a popup with a parent," << mTransientParent->window()
126 << "which does not match the current topmost grabbing popup,"
127 << mTopPopup->window() << "With some shell surface protocols, this"
128 << "is not allowed. The wayland QPA plugin is currently handling"
129 << "it by setting the parent to the topmost grabbing popup."
130 << "Note, however, that this may cause positioning errors and"
131 << "popups closing unxpectedly. Please fix the transient parent of the popup.";
132 mTransientParent = mTopPopup;
133 }
134 mTopPopup = this;
135 }
136 }
137
138 mShellSurface = mShellIntegration->createShellSurface(window: this);
139 if (mShellSurface) {
140 if (mTransientParent) {
141 if (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup || window()->type() == Qt::Tool)
142 mTransientParent->addChildPopup(child: this);
143 }
144
145 // Set initial surface title
146 setWindowTitle(window()->title());
147 mShellSurface->setIcon(mWindowIcon);
148
149 // The appId is the desktop entry identifier that should follow the
150 // reverse DNS convention (see
151 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html). According
152 // to xdg-shell the appId is only the name, without the .desktop suffix.
153 //
154 // If the application specifies the desktop file name use that,
155 // otherwise fall back to the executable name and prepend the
156 // reversed organization domain when available.
157 if (!QGuiApplication::desktopFileName().isEmpty()) {
158 mShellSurface->setAppId(QGuiApplication::desktopFileName());
159 } else {
160 QFileInfo fi = QFileInfo(QCoreApplication::instance()->applicationFilePath());
161 QStringList domainName =
162 QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'),
163 Qt::SkipEmptyParts);
164
165 if (domainName.isEmpty()) {
166 mShellSurface->setAppId(fi.baseName());
167 } else {
168 QString appId;
169 for (int i = 0; i < domainName.size(); ++i)
170 appId.prepend(QLatin1Char('.')).prepend(s: domainName.at(i));
171 appId.append(s: fi.baseName());
172 mShellSurface->setAppId(appId);
173 }
174 }
175 // the user may have already set some window properties, so make sure to send them out
176 for (auto it = m_properties.cbegin(); it != m_properties.cend(); ++it)
177 mShellSurface->sendProperty(name: it.key(), value: it.value());
178
179 emit surfaceRoleCreated();
180 } else {
181 qWarning(msg: "Could not create a shell surface object.");
182 }
183 }
184
185 createDecoration();
186 updateInputRegion();
187
188 // Enable high-dpi rendering. Scale() returns the screen scale factor and will
189 // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale()
190 // to inform the compositor that high-resolution buffers will be provided.
191 if (mViewport)
192 updateViewport();
193 else if (mSurface->version() >= 3)
194 mSurface->set_buffer_scale(std::ceil(x: scale()));
195
196 QRect geometry = windowGeometry();
197 QRect defaultGeometry = this->defaultGeometry();
198 if (geometry.width() <= 0)
199 geometry.setWidth(defaultGeometry.width());
200 if (geometry.height() <= 0)
201 geometry.setHeight(defaultGeometry.height());
202
203 setGeometry_helper(geometry);
204 setMask(window()->mask());
205 if (mShellSurface) {
206 mShellSurface->requestWindowStates(states: window()->windowStates());
207 mShellSurface->setWindowFlags(mFlags);
208 }
209 handleContentOrientationChange(orientation: window()->contentOrientation());
210
211 if (mShellSurface && mShellSurface->commitSurfaceRole())
212 mSurface->commit();
213}
214
215void QWaylandWindow::setPendingImageDescription()
216{
217 mColorManagementSurface->setImageDescription(mPendingImageDescription.get());
218}
219
220void QWaylandWindow::initializeWlSurface()
221{
222 Q_ASSERT(!mSurface);
223 {
224 QWriteLocker lock(&mSurfaceLock);
225 mSurface.reset(other: new QWaylandSurface(mDisplay));
226 connect(mSurface.data(), &QWaylandSurface::screensChanged,
227 this, &QWaylandWindow::handleScreensChanged);
228 connect(mSurface.data(), &QWaylandSurface::preferredBufferScaleChanged,
229 this, &QWaylandWindow::updateScale);
230 connect(mSurface.data(), &QWaylandSurface::preferredBufferTransformChanged,
231 this, &QWaylandWindow::updateBufferTransform);
232 mSurface->m_window = this;
233 }
234 emit wlSurfaceCreated();
235
236 if (mDisplay->fractionalScaleManager() && qApp->highDpiScaleFactorRoundingPolicy() == Qt::HighDpiScaleFactorRoundingPolicy::PassThrough) {
237 mFractionalScale.reset(other: new QWaylandFractionalScale(mDisplay->fractionalScaleManager()->get_fractional_scale(mSurface->object())));
238
239 connect(mFractionalScale.data(), &QWaylandFractionalScale::preferredScaleChanged,
240 this, &QWaylandWindow::updateScale);
241 }
242 // The fractional scale manager check is needed to work around Gnome < 36 where viewports don't work
243 // Right now viewports are only necessary when a fractional scale manager is used
244 if (display()->viewporter() && display()->fractionalScaleManager()) {
245 mViewport.reset(other: new QWaylandViewport(display()->createViewport(window: this)));
246 }
247
248 QColorSpace requestedColorSpace = window()->requestedFormat().colorSpace();
249 if (requestedColorSpace != QColorSpace{} && mDisplay->colorManager()) {
250 // TODO try a similar (same primaries + supported transfer function) color space if this fails?
251 mPendingImageDescription = mDisplay->colorManager()->createImageDescription(requestedColorSpace);
252 if (mPendingImageDescription) {
253 if (!mColorManagementSurface)
254 mColorManagementSurface = std::make_unique<ColorManagementSurface>(mDisplay->colorManager()->get_surface(surface()));
255 connect(mPendingImageDescription.get(), &ImageDescription::ready, this, &QWaylandWindow::setPendingImageDescription, Qt::SingleShotConnection);
256 mSurfaceFormat.setColorSpace(requestedColorSpace);
257 } else {
258 qCWarning(lcQpaWayland) << "couldn't create image description for requested color space" << requestedColorSpace;
259 }
260 }
261}
262
263void QWaylandWindow::setFormat(const QSurfaceFormat &format)
264{
265 const auto colorSpace = mSurfaceFormat.colorSpace();
266 mSurfaceFormat = format;
267 mSurfaceFormat.setColorSpace(colorSpace);
268}
269
270void QWaylandWindow::setShellIntegration(QWaylandShellIntegration *shellIntegration)
271{
272 Q_ASSERT(shellIntegration);
273 if (mShellSurface) {
274 qCWarning(lcQpaWayland) << "Cannot set shell integration while there's already a shell surface created";
275 return;
276 }
277 mShellIntegration = shellIntegration;
278}
279
280bool QWaylandWindow::shouldCreateShellSurface() const
281{
282 if (!shellIntegration())
283 return false;
284
285 if (shouldCreateSubSurface())
286 return false;
287
288 if (window()->inherits("QShapedPixmapWindow"))
289 return false;
290
291 if (qEnvironmentVariableIsSet(varName: "QT_WAYLAND_USE_BYPASSWINDOWMANAGERHINT"))
292 return !(window()->flags() & Qt::BypassWindowManagerHint);
293
294 return true;
295}
296
297bool QWaylandWindow::shouldCreateSubSurface() const
298{
299 return QPlatformWindow::parent() != nullptr;
300}
301
302void QWaylandWindow::beginFrame()
303{
304 mSurfaceLock.lockForRead();
305 mInFrameRender = true;
306}
307
308void QWaylandWindow::endFrame()
309{
310 mSurfaceLock.unlock();
311 mInFrameRender = false;
312}
313
314void QWaylandWindow::reset()
315{
316 resetSurfaceRole();
317
318 if (mSurface) {
319 {
320 QWriteLocker lock(&mSurfaceLock);
321 invalidateSurface();
322 mSurface.reset();
323 mViewport.reset();
324 mFractionalScale.reset();
325 mColorManagementSurface.reset();
326 mPendingImageDescription.reset();
327 }
328 emit wlSurfaceDestroyed();
329 }
330
331
332 mScale = std::nullopt;
333 mOpaqueArea = QRegion();
334 mMask = QRegion();
335
336 mInputRegion = QRegion();
337 mTransparentInputRegion = false;
338
339 mDisplay->handleWindowDestroyed(window: this);
340}
341
342void QWaylandWindow::resetSurfaceRole()
343{
344 // Old Reset
345 closeChildPopups();
346
347 if (mTopPopup == this)
348 mTopPopup = mTransientParent && (mTransientParent->window()->type() == Qt::Popup) ? mTransientParent : nullptr;
349 if (mTransientParent)
350 mTransientParent->removeChildPopup(child: this);
351 mTransientParent = nullptr;
352 delete std::exchange(mShellSurface, nullptr);
353 delete std::exchange(mSubSurfaceWindow, nullptr);
354 emit surfaceRoleDestroyed();
355
356 resetFrameCallback();
357 mInFrameRender = false;
358 mWaitingToApplyConfigure = false;
359 mExposed = false;
360}
361
362void QWaylandWindow::resetFrameCallback()
363{
364 {
365 QMutexLocker lock(&mFrameSyncMutex);
366 if (mFrameCallback) {
367 wl_callback_destroy(mFrameCallback);
368 mFrameCallback = nullptr;
369 }
370 mFrameCallbackElapsedTimer.invalidate();
371 mWaitingForFrameCallback = false;
372 }
373 if (mFrameCallbackCheckIntervalTimerId != -1) {
374 killTimer(mFrameCallbackCheckIntervalTimerId);
375 mFrameCallbackCheckIntervalTimerId = -1;
376 }
377 mFrameCallbackTimedOut = false;
378}
379
380QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface)
381{
382 if (auto *s = QWaylandSurface::fromWlSurface(surface))
383 return s->m_window;
384 return nullptr;
385}
386
387WId QWaylandWindow::winId() const
388{
389 return reinterpret_cast<WId>(wlSurface());
390}
391
392void QWaylandWindow::setParent(const QPlatformWindow *parent)
393{
394 if (lastParent == parent)
395 return;
396
397 if (mSubSurfaceWindow && parent) { // new parent, but we were a subsurface already
398 delete mSubSurfaceWindow;
399 QWaylandWindow *p = const_cast<QWaylandWindow *>(static_cast<const QWaylandWindow *>(parent));
400 mSubSurfaceWindow = new QWaylandSubSurface(this, p, mDisplay->createSubSurface(this, p));
401 } else if ((!lastParent && parent) || (lastParent && !parent)) {
402 // we're changing role, need to make a new wl_surface
403 reset();
404 initializeWlSurface();
405 if (window()->isVisible()) {
406 initWindow();
407 }
408 }
409 lastParent = parent;
410}
411
412QString QWaylandWindow::windowTitle() const
413{
414 return mWindowTitle;
415}
416
417void QWaylandWindow::setWindowTitle(const QString &title)
418{
419 const QString separator = QString::fromUtf8(" \xe2\x80\x94 "); // unicode character U+2014, EM DASH
420 const QString formatted = formatWindowTitle(title, separator);
421
422 const int libwaylandMaxBufferSize = 4096;
423 // Some parts of the buffer is used for metadata, so subtract 100 to be on the safe side.
424 // Also, QString is in utf-16, which means that in the worst case each character will be
425 // three bytes when converted to utf-8 (which is what libwayland uses), so divide by three.
426 const int maxLength = libwaylandMaxBufferSize / 3 - 100;
427
428 auto truncated = QStringView{formatted}.left(n: maxLength);
429 if (truncated.size() < formatted.size()) {
430 qCWarning(lcQpaWayland) << "Window titles longer than" << maxLength << "characters are not supported."
431 << "Truncating window title (from" << formatted.size() << "chars)";
432 }
433
434 mWindowTitle = truncated.toString();
435
436 if (mShellSurface)
437 mShellSurface->setTitle(mWindowTitle);
438
439 if (mWindowDecorationEnabled && window()->isVisible())
440 mWindowDecoration->update();
441}
442
443void QWaylandWindow::setWindowIcon(const QIcon &icon)
444{
445 mWindowIcon = icon;
446
447 if (mWindowDecorationEnabled && window()->isVisible())
448 mWindowDecoration->update();
449 if (mShellSurface)
450 mShellSurface->setIcon(icon);
451}
452
453QRect QWaylandWindow::defaultGeometry() const
454{
455 return QRect(QPoint(), QSize(500,500));
456}
457
458void QWaylandWindow::setGeometry_helper(const QRect &rect)
459{
460 QPlatformWindow::setGeometry(rect);
461 if (mViewport)
462 updateViewport();
463
464 if (mSubSurfaceWindow) {
465 QMargins m = static_cast<QWaylandWindow *>(QPlatformWindow::parent())->clientSideMargins();
466 mSubSurfaceWindow->set_position(rect.x() + m.left(), rect.y() + m.top());
467
468 QWaylandWindow *parentWindow = mSubSurfaceWindow->parent();
469 if (parentWindow && parentWindow->isExposed()) {
470 QRect parentExposeGeometry(QPoint(), parentWindow->geometry().size());
471 parentWindow->sendExposeEvent(rect: parentExposeGeometry);
472 }
473 }
474}
475
476void QWaylandWindow::setGeometry(const QRect &r)
477{
478 auto rect = r;
479 if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup
480 && window()->type() != Qt::ToolTip && window()->type() != Qt::Tool) {
481 rect.moveTo(screen()->geometry().topLeft());
482 }
483 setGeometry_helper(rect);
484
485 if (mShellSurface) {
486 if (!mInResizeFromApplyConfigure) {
487 const QRect frameGeometry = r.marginsAdded(margins: clientSideMargins()).marginsRemoved(margins: windowContentMargins());
488 if (qt_window_private(window())->positionAutomatic)
489 mShellSurface->setWindowSize(frameGeometry.size());
490 else
491 mShellSurface->setWindowGeometry(frameGeometry);
492 }
493 }
494
495 if (mShellSurface)
496 mShellSurface->setContentGeometry(windowContentGeometry());
497
498 if (isOpaque() && mMask.isEmpty())
499 setOpaqueArea(QRect(QPoint(0, 0), rect.size()));
500
501
502 if (window()->isVisible() && rect.isValid()) {
503 ensureSize();
504 if (mWindowDecorationEnabled)
505 mWindowDecoration->update();
506
507 QWindowSystemInterface::handleGeometryChange<QWindowSystemInterface::SynchronousDelivery>(window(), geometry());
508 mSentInitialResize = true;
509 }
510
511 // Wayland has no concept of areas being exposed or not, only the entire window, when our geometry changes, we need to flag the new area as exposed
512 // On other platforms (X11) the expose event would be received deferred from the X server
513 // we want our behaviour to match, and only run after control has returned to the event loop
514 QMetaObject::invokeMethod(this, &QWaylandWindow::synthesizeExposeOnGeometryChange, Qt::QueuedConnection);
515}
516
517void QWaylandWindow::synthesizeExposeOnGeometryChange()
518{
519 if (!isExposed())
520 return;
521 QRect exposeGeometry(QPoint(), geometry().size());
522 if (exposeGeometry == mLastExposeGeometry)
523 return;
524
525 sendExposeEvent(rect: exposeGeometry);
526}
527
528void QWaylandWindow::updateInputRegion()
529{
530 if (!mSurface)
531 return;
532
533 const bool transparentInputRegion = mFlags.testFlag(flag: Qt::WindowTransparentForInput);
534
535 QRegion inputRegion;
536 if (!transparentInputRegion)
537 inputRegion = mMask;
538
539 if (mInputRegion == inputRegion && mTransparentInputRegion == transparentInputRegion)
540 return;
541
542 mInputRegion = inputRegion;
543 mTransparentInputRegion = transparentInputRegion;
544
545 if (mInputRegion.isEmpty() && !mTransparentInputRegion) {
546 mSurface->set_input_region(nullptr);
547 } else {
548 struct ::wl_region *region = mDisplay->createRegion(mInputRegion);
549 mSurface->set_input_region(region);
550 wl_region_destroy(region);
551 }
552}
553
554void QWaylandWindow::updateViewport()
555{
556 if (!surfaceSize().isEmpty())
557 mViewport->setDestination(surfaceSize());
558}
559
560void QWaylandWindow::setGeometryFromApplyConfigure(const QPoint &globalPosition, const QSize &sizeWithMargins)
561{
562 QMargins margins = clientSideMargins();
563
564 QPoint positionWithoutMargins = globalPosition + QPoint(margins.left(), margins.top());
565 int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left() + margins.right()), 1);
566 int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top() + margins.bottom()), 1);
567
568 QRect geometry(positionWithoutMargins, QSize(widthWithoutMargins, heightWithoutMargins));
569
570 mInResizeFromApplyConfigure = true;
571 setGeometry(geometry);
572 mInResizeFromApplyConfigure = false;
573}
574
575void QWaylandWindow::repositionFromApplyConfigure(const QPoint &globalPosition)
576{
577 QMargins margins = clientSideMargins();
578 QPoint positionWithoutMargins = globalPosition + QPoint(margins.left(), margins.top());
579
580 QRect geometry(positionWithoutMargins, windowGeometry().size());
581 mInResizeFromApplyConfigure = true;
582 setGeometry(geometry);
583 mInResizeFromApplyConfigure = false;
584}
585
586void QWaylandWindow::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset)
587{
588 QMargins margins = clientSideMargins();
589 int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left() + margins.right()), 1);
590 int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top() + margins.bottom()), 1);
591 QRect geometry(windowGeometry().topLeft(), QSize(widthWithoutMargins, heightWithoutMargins));
592
593 mOffset += offset;
594 mInResizeFromApplyConfigure = true;
595 setGeometry(geometry);
596 mInResizeFromApplyConfigure = false;
597}
598
599void QWaylandWindow::sendExposeEvent(const QRect &rect)
600{
601 static bool sQtTestMode = qEnvironmentVariableIsSet(varName: "QT_QTESTLIB_RUNNING");
602 mLastExposeGeometry = rect;
603
604 if (sQtTestMode) {
605 mExposeEventNeedsAttachedBuffer = true;
606 }
607 QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), rect);
608
609 /**
610 * If an expose is not handled by application code, explicitly attach a buffer
611 * This primarily is a workaround for Qt unit tests using QWindow directly and
612 * wanting focus.
613 */
614 if (mExposed && mExposeEventNeedsAttachedBuffer && !rect.isNull()) {
615 auto buffer = new QWaylandShmBuffer(mDisplay, rect.size(), QImage::Format_ARGB32);
616 buffer->image()->fill(Qt::transparent);
617 buffer->setDeleteOnRelease(true);
618 commit(buffer, QRegion());
619 }
620}
621
622QPlatformScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const
623{
624 QReadLocker lock(&mSurfaceLock);
625 if (mSurface) {
626 if (auto *screen = mSurface->oldestEnteredScreen())
627 return screen;
628 }
629 return QPlatformWindow::screen();
630}
631
632void QWaylandWindow::setVisible(bool visible)
633{
634 // Workaround for issue where setVisible may be called with the same value twice
635 if (lastVisible == visible)
636 return;
637 lastVisible = visible;
638
639 if (visible) {
640 setGeometry(windowGeometry());
641 initWindow();
642 updateExposure();
643 if (mShellSurface)
644 mShellSurface->requestActivateOnShow();
645 } else {
646 // make sure isExposed is false during the next event dispatch
647 mExposed = false;
648 sendExposeEvent(rect: QRect());
649 resetSurfaceRole();
650 mSurface->attach(nullptr, 0, 0);
651 mSurface->commit();
652 }
653}
654
655
656void QWaylandWindow::raise()
657{
658 if (mShellSurface)
659 mShellSurface->raise();
660}
661
662
663void QWaylandWindow::lower()
664{
665 if (mShellSurface)
666 mShellSurface->lower();
667}
668
669void QWaylandWindow::setMask(const QRegion &mask)
670{
671 QReadLocker locker(&mSurfaceLock);
672 if (!mSurface)
673 return;
674
675 if (mMask == mask)
676 return;
677
678 mMask = mask;
679
680 updateInputRegion();
681
682 if (isOpaque()) {
683 if (mMask.isEmpty())
684 setOpaqueArea(QRect(QPoint(0, 0), geometry().size()));
685 else
686 setOpaqueArea(mMask);
687 }
688}
689
690void QWaylandWindow::setAlertState(bool enabled)
691{
692 if (mShellSurface)
693 mShellSurface->setAlertState(enabled);
694}
695
696bool QWaylandWindow::isAlertState() const
697{
698 if (mShellSurface)
699 return mShellSurface->isAlertState();
700
701 return false;
702}
703
704void QWaylandWindow::applyConfigureWhenPossible()
705{
706 if (!mWaitingToApplyConfigure) {
707 mWaitingToApplyConfigure = true;
708 QMetaObject::invokeMethod(this, "applyConfigure", Qt::QueuedConnection);
709 }
710}
711
712void QWaylandWindow::applyConfigure()
713{
714 if (!mWaitingToApplyConfigure)
715 return;
716
717 Q_ASSERT_X(QThread::currentThreadId() == QThreadData::get2(thread())->threadId.loadRelaxed(),
718 "QWaylandWindow::applyConfigure", "not called from main thread");
719
720 // If we're mid paint, use an exposeEvent to flush the current frame.
721 // When this completes we know that no other frames will be rendering.
722 // This could be improved in future as we 're blocking for not just the frame to finish but one additional extra frame.
723 if (mInFrameRender)
724 QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), QRect(QPoint(0, 0), geometry().size()));
725 if (mShellSurface)
726 mShellSurface->applyConfigure();
727
728 mWaitingToApplyConfigure = false;
729 if (mExposed)
730 sendExposeEvent(rect: QRect(QPoint(), geometry().size()));
731 else
732 // we still need to commit the configured ack for a hidden surface
733 commit();
734}
735
736void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y)
737{
738 QReadLocker locker(&mSurfaceLock);
739 if (mSurface == nullptr)
740 return;
741
742 if (buffer) {
743 Q_ASSERT(!buffer->committed());
744 handleUpdate();
745 buffer->setBusy(true);
746 if (mSurface->version() >= WL_SURFACE_OFFSET_SINCE_VERSION) {
747 mSurface->offset(x, y);
748 mSurface->attach(buffer->buffer(), 0, 0);
749 } else {
750 mSurface->attach(buffer->buffer(), x, y);
751 }
752 } else {
753 mSurface->attach(nullptr, 0, 0);
754 }
755}
756
757void QWaylandWindow::attachOffset(QWaylandBuffer *buffer)
758{
759 attach(buffer, x: mOffset.x(), y: mOffset.y());
760 mOffset = QPoint();
761}
762
763void QWaylandWindow::damage(const QRect &rect)
764{
765 QReadLocker locker(&mSurfaceLock);
766 if (mSurface == nullptr)
767 return;
768
769 const qreal s = scale();
770 if (mSurface->version() >= 4) {
771 const QRect bufferRect =
772 QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height())
773 .toAlignedRect();
774 mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(),
775 bufferRect.height());
776 } else {
777 mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height());
778 }
779}
780
781void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage)
782{
783 if (isExposed()) {
784 commit(buffer, damage);
785 } else {
786 buffer->setBusy(false);
787 }
788}
789
790bool QWaylandWindow::allowsIndependentThreadedRendering() const
791{
792 return !mWaitingToApplyConfigure;
793}
794
795void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage)
796{
797 Q_ASSERT(isExposed());
798 if (buffer->committed()) {
799 mSurface->commit();
800 qCDebug(lcWaylandBackingstore) << "Buffer already committed, not attaching.";
801 return;
802 }
803
804 QReadLocker locker(&mSurfaceLock);
805 if (!mSurface)
806 return;
807
808 attachOffset(buffer);
809 if (mSurface->version() >= 4) {
810 const qreal s = scale();
811 for (const QRect &rect : damage) {
812 const QRect bufferRect =
813 QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height())
814 .toAlignedRect();
815 mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(),
816 bufferRect.height());
817 }
818 } else {
819 for (const QRect &rect: damage)
820 mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height());
821 }
822 Q_ASSERT(!buffer->committed());
823 buffer->setCommitted();
824 mSurface->commit();
825}
826
827void QWaylandWindow::commit()
828{
829 QReadLocker locker(&mSurfaceLock);
830 if (mSurface != nullptr)
831 mSurface->commit();
832}
833
834const wl_callback_listener QWaylandWindow::callbackListener = {
835 [](void *data, wl_callback *callback, uint32_t time) {
836 Q_UNUSED(time);
837 auto *window = static_cast<QWaylandWindow*>(data);
838 window->handleFrameCallback(callback);
839 }
840};
841
842void QWaylandWindow::handleFrameCallback(wl_callback* callback)
843{
844 QMutexLocker locker(&mFrameSyncMutex);
845 if (!mFrameCallback) {
846 // This means the callback is already unset by QWaylandWindow::reset.
847 // The wl_callback object will be destroyed there too.
848 return;
849 }
850 Q_ASSERT(callback == mFrameCallback);
851 wl_callback_destroy(callback);
852 mFrameCallback = nullptr;
853
854 mWaitingForFrameCallback = false;
855 mFrameCallbackElapsedTimer.invalidate();
856
857 // The rest can wait until we can run it on the correct thread
858 if (mWaitingForUpdateDelivery.testAndSetAcquire(expectedValue: false, newValue: true)) {
859 // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync()
860 // in the single-threaded case.
861 QMetaObject::invokeMethod(this, &QWaylandWindow::doHandleFrameCallback, Qt::QueuedConnection);
862 }
863 mFrameSyncWait.notify_all();
864}
865
866void QWaylandWindow::doHandleFrameCallback()
867{
868 mWaitingForUpdateDelivery.storeRelease(newValue: false);
869 bool wasExposed = isExposed();
870 mFrameCallbackTimedOut = false;
871 // Did setting mFrameCallbackTimedOut make the window exposed?
872 updateExposure();
873 if (wasExposed && hasPendingUpdateRequest())
874 deliverUpdateRequest();
875
876}
877
878bool QWaylandWindow::waitForFrameSync(int timeout)
879{
880 QMutexLocker locker(&mFrameSyncMutex);
881
882 QDeadlineTimer deadline(timeout);
883 while (mWaitingForFrameCallback && mFrameSyncWait.wait(lockedMutex: &mFrameSyncMutex, deadline)) { }
884
885 if (mWaitingForFrameCallback) {
886 qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed";
887 mFrameCallbackTimedOut = true;
888 mWaitingForUpdate = false;
889 QMetaObject::invokeMethod(this, &QWaylandWindow::updateExposure, Qt::QueuedConnection);
890 }
891
892 return !mWaitingForFrameCallback;
893}
894
895QMargins QWaylandWindow::frameMargins() const
896{
897 if (mWindowDecorationEnabled)
898 return mWindowDecoration->margins();
899 else if (mShellSurface)
900 return mShellSurface->serverSideFrameMargins();
901 else
902 return QPlatformWindow::frameMargins();
903}
904
905QMargins QWaylandWindow::clientSideMargins() const
906{
907 return mWindowDecorationEnabled ? mWindowDecoration->margins() : QMargins{};
908}
909
910void QWaylandWindow::setCustomMargins(const QMargins &margins) {
911 const QMargins oldMargins = mCustomMargins;
912 mCustomMargins = margins;
913 propagateSizeHints();
914 setGeometry(geometry().marginsRemoved(oldMargins).marginsAdded(margins));
915}
916
917/*!
918 * Size, with decorations (including including eventual shadows) in wl_surface coordinates
919 */
920QSize QWaylandWindow::surfaceSize() const
921{
922 return geometry().marginsAdded(clientSideMargins()).size();
923}
924
925QMargins QWaylandWindow::windowContentMargins() const
926{
927 QMargins shadowMargins;
928
929 if (mWindowDecorationEnabled)
930 shadowMargins = mWindowDecoration->margins(marginsType: QWaylandAbstractDecoration::ShadowsOnly);
931
932 if (!mCustomMargins.isNull())
933 shadowMargins += mCustomMargins;
934
935 return shadowMargins;
936}
937
938/*!
939 * Window geometry as defined by the xdg-shell spec (in wl_surface coordinates)
940 * topLeft is where the shadow stops and the decorations border start.
941 */
942QRect QWaylandWindow::windowContentGeometry() const
943{
944 const QMargins margins = windowContentMargins();
945 return QRect(QPoint(margins.left(), margins.top()), surfaceSize().shrunkBy(m: margins));
946}
947
948/*!
949 * Converts from wl_surface coordinates to Qt window coordinates. Qt window
950 * coordinates start inside (not including) the window decorations, while
951 * wl_surface coordinates start at the first pixel of the buffer. Potentially,
952 * this should be in the window shadow, although we don't have those. So for
953 * now, it's the first pixel of the decorations.
954 */
955QPointF QWaylandWindow::mapFromWlSurface(const QPointF &surfacePosition) const
956{
957 const QMargins margins = clientSideMargins();
958 return QPointF(surfacePosition.x() - margins.left(), surfacePosition.y() - margins.top());
959}
960
961wl_surface *QWaylandWindow::wlSurface() const
962{
963 QReadLocker locker(&mSurfaceLock);
964 return mSurface ? mSurface->object() : nullptr;
965}
966
967QWaylandShellSurface *QWaylandWindow::shellSurface() const
968{
969 return mShellSurface;
970}
971
972std::any QWaylandWindow::_surfaceRole() const
973{
974 if (mSubSurfaceWindow)
975 return mSubSurfaceWindow->object();
976 if (mShellSurface)
977 return mShellSurface->surfaceRole();
978 return {};
979}
980
981QWaylandSubSurface *QWaylandWindow::subSurfaceWindow() const
982{
983 return mSubSurfaceWindow;
984}
985
986QWaylandScreen *QWaylandWindow::waylandScreen() const
987{
988 auto *platformScreen = QPlatformWindow::screen();
989 Q_ASSERT(platformScreen);
990 if (platformScreen->isPlaceholder())
991 return nullptr;
992 return static_cast<QWaylandScreen *>(platformScreen);
993}
994
995void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation)
996{
997 mLastReportedContentOrientation = orientation;
998 updateBufferTransform();
999}
1000
1001void QWaylandWindow::updateBufferTransform()
1002{
1003 QReadLocker locker(&mSurfaceLock);
1004 if (mSurface == nullptr || mSurface->version() < 2)
1005 return;
1006
1007 wl_output_transform transform;
1008 Qt::ScreenOrientation screenOrientation = Qt::PrimaryOrientation;
1009
1010 if (mSurface->version() >= 6) {
1011 const auto transform = mSurface->preferredBufferTransform().value_or(WL_OUTPUT_TRANSFORM_NORMAL);
1012 if (auto screen = waylandScreen())
1013 screenOrientation = screen->toScreenOrientation(wlTransform: transform, fallback: Qt::PrimaryOrientation);
1014 } else {
1015 if (auto screen = window()->screen())
1016 screenOrientation = screen->primaryOrientation();
1017 }
1018
1019 const bool isPortrait = (screenOrientation == Qt::PortraitOrientation);
1020
1021 switch (mLastReportedContentOrientation) {
1022 case Qt::PrimaryOrientation:
1023 transform = WL_OUTPUT_TRANSFORM_NORMAL;
1024 break;
1025 case Qt::LandscapeOrientation:
1026 transform = isPortrait ? WL_OUTPUT_TRANSFORM_270 : WL_OUTPUT_TRANSFORM_NORMAL;
1027 break;
1028 case Qt::PortraitOrientation:
1029 transform = isPortrait ? WL_OUTPUT_TRANSFORM_NORMAL : WL_OUTPUT_TRANSFORM_90;
1030 break;
1031 case Qt::InvertedLandscapeOrientation:
1032 transform = isPortrait ? WL_OUTPUT_TRANSFORM_90 : WL_OUTPUT_TRANSFORM_180;
1033 break;
1034 case Qt::InvertedPortraitOrientation:
1035 transform = isPortrait ? WL_OUTPUT_TRANSFORM_180 : WL_OUTPUT_TRANSFORM_270;
1036 break;
1037 default:
1038 Q_UNREACHABLE();
1039 }
1040 mSurface->set_buffer_transform(transform);
1041}
1042
1043void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask)
1044{
1045 if (mShellSurface)
1046 mShellSurface->setContentOrientationMask(mask);
1047}
1048
1049void QWaylandWindow::setWindowState(Qt::WindowStates states)
1050{
1051 if (mShellSurface)
1052 mShellSurface->requestWindowStates(states);
1053}
1054
1055void QWaylandWindow::setWindowFlags(Qt::WindowFlags flags)
1056{
1057 const bool wasPopup = mFlags.testFlag(flag: Qt::Popup);
1058 const bool isPopup = flags.testFlag(flag: Qt::Popup);
1059
1060 mFlags = flags;
1061 // changing role is not allowed on XdgShell on the same wl_surface
1062 if (wasPopup != isPopup) {
1063 reset();
1064 initializeWlSurface();
1065 if (window()->isVisible()) {
1066 initWindow();
1067 }
1068 } else {
1069 if (mShellSurface)
1070 mShellSurface->setWindowFlags(flags);
1071 }
1072
1073 createDecoration();
1074
1075 QReadLocker locker(&mSurfaceLock);
1076 updateInputRegion();
1077}
1078
1079Qt::WindowFlags QWaylandWindow::windowFlags() const
1080{
1081 return mFlags;
1082}
1083
1084bool QWaylandWindow::createDecoration()
1085{
1086 Q_ASSERT_X(QThread::currentThreadId() == QThreadData::get2(thread())->threadId.loadRelaxed(),
1087 "QWaylandWindow::createDecoration", "not called from main thread");
1088 // TODO: client side decorations do not work with Vulkan backend.
1089 if (window()->surfaceType() == QSurface::VulkanSurface)
1090 return false;
1091 if (!mDisplay->supportsWindowDecoration())
1092 return false;
1093
1094 static bool decorationPluginFailed = false;
1095 bool decoration = false;
1096 switch (window()->type()) {
1097 case Qt::Window:
1098 case Qt::Widget:
1099 case Qt::Dialog:
1100 case Qt::Tool:
1101 case Qt::Drawer:
1102 decoration = true;
1103 break;
1104 default:
1105 break;
1106 }
1107 if (mFlags & Qt::FramelessWindowHint)
1108 decoration = false;
1109 if (mFlags & Qt::BypassWindowManagerHint)
1110 decoration = false;
1111 if (mSubSurfaceWindow)
1112 decoration = false;
1113 if (!mShellSurface || !mShellSurface->wantsDecorations())
1114 decoration = false;
1115
1116 bool hadDecoration = mWindowDecorationEnabled;
1117 if (decoration && !decorationPluginFailed) {
1118 if (!mWindowDecorationEnabled) {
1119 if (mWindowDecoration) {
1120 mWindowDecoration.reset();
1121 }
1122
1123 QStringList decorations = QWaylandDecorationFactory::keys();
1124 if (decorations.empty()) {
1125 qWarning() << "No decoration plugins available. Running with no decorations.";
1126 decorationPluginFailed = true;
1127 return false;
1128 }
1129
1130 QString targetKey;
1131 QByteArray decorationPluginName = qgetenv(varName: "QT_WAYLAND_DECORATION");
1132 if (!decorationPluginName.isEmpty()) {
1133 targetKey = QString::fromLocal8Bit(decorationPluginName);
1134 if (!decorations.contains(str: targetKey)) {
1135 qWarning() << "Requested decoration " << targetKey << " not found, falling back to default";
1136 targetKey = QString(); // fallthrough
1137 }
1138 }
1139
1140 if (targetKey.isEmpty()) {
1141 auto unixServices = dynamic_cast<QDesktopUnixServices *>(
1142 QGuiApplicationPrivate::platformIntegration()->services());
1143 const QList<QByteArray> desktopNames = unixServices->desktopEnvironment().split(sep: ':');
1144 if (desktopNames.contains("GNOME")) {
1145 if (decorations.contains(str: "adwaita"_L1))
1146 targetKey = "adwaita"_L1;
1147 else if (decorations.contains(str: "gnome"_L1))
1148 targetKey = "gnome"_L1;
1149 } else {
1150 // Do not use Adwaita/GNOME decorations on other DEs
1151 decorations.removeAll("adwaita"_L1);
1152 decorations.removeAll("gnome"_L1);
1153 }
1154 }
1155
1156 if (targetKey.isEmpty())
1157 targetKey = decorations.first(); // first come, first served.
1158
1159 mWindowDecoration.reset(p: QWaylandDecorationFactory::create(name: targetKey, args: QStringList()));
1160 if (!mWindowDecoration) {
1161 qWarning() << "Could not create decoration from factory! Running with no decorations.";
1162 decorationPluginFailed = true;
1163 return false;
1164 }
1165 mWindowDecoration->setWaylandWindow(this);
1166 mWindowDecorationEnabled = true;
1167 }
1168 } else {
1169 mWindowDecorationEnabled = false;
1170 }
1171
1172 if (hadDecoration != mWindowDecorationEnabled) {
1173 for (QWaylandSubSurface *subsurf : std::as_const(mChildren)) {
1174 QPoint pos = subsurf->window()->geometry().topLeft();
1175 QMargins m = frameMargins();
1176 subsurf->set_position(pos.x() + m.left(), pos.y() + m.top());
1177 }
1178 setGeometry(geometry());
1179
1180 // creating a decoration changes our margins which in turn change size hints
1181 propagateSizeHints();
1182
1183 // This is a special case where the buffer is recreated, but since
1184 // the content rect remains the same, the widgets remain the same
1185 // size and are not redrawn, leaving the new buffer empty. As a simple
1186 // work-around, we trigger a full extra update whenever the client-side
1187 // window decorations are toggled while the window is showing.
1188 window()->requestUpdate();
1189 }
1190
1191 return mWindowDecoration.get();
1192}
1193
1194QWaylandAbstractDecoration *QWaylandWindow::decoration() const
1195{
1196 return mWindowDecorationEnabled ? mWindowDecoration.get() : nullptr;
1197}
1198
1199static QWaylandWindow *closestShellSurfaceWindow(QWindow *window)
1200{
1201 while (window) {
1202 auto w = static_cast<QWaylandWindow *>(window->handle());
1203 if (w && w->shellSurface())
1204 return w;
1205 window = window->transientParent() ? window->transientParent() : window->parent();
1206 }
1207 return nullptr;
1208}
1209
1210QWaylandWindow *QWaylandWindow::transientParent() const
1211{
1212 return mTransientParent;
1213}
1214
1215QWaylandWindow *QWaylandWindow::guessTransientParent() const
1216{
1217 // Take the closest window with a shell surface, since the transient parent may be a
1218 // QWidgetWindow or some other window without a shell surface, which is then not able to
1219 // get mouse events.
1220 if (auto transientParent = closestShellSurfaceWindow(window()->transientParent()))
1221 return transientParent;
1222
1223 if (window()->type() == Qt::Popup) {
1224 if (mTopPopup)
1225 return mTopPopup;
1226 }
1227
1228 if (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup) {
1229 if (auto lastInputWindow = display()->lastInputWindow())
1230 return closestShellSurfaceWindow(lastInputWindow->window());
1231 }
1232
1233 return nullptr;
1234}
1235
1236void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e)
1237{
1238 // There's currently no way to get info about the actual hardware device in use.
1239 // At least we get the correct seat.
1240 const QPointingDevice *device = QPointingDevice::primaryPointingDevice(seatName: inputDevice->seatname());
1241 if (e.type == QEvent::Leave) {
1242 if (mWindowDecorationEnabled) {
1243 if (mMouseEventsInContentArea)
1244 QWindowSystemInterface::handleLeaveEvent(window());
1245 } else {
1246 QWindowSystemInterface::handleLeaveEvent(window());
1247 }
1248#if QT_CONFIG(cursor)
1249 restoreMouseCursor(device: inputDevice);
1250#endif
1251 return;
1252 }
1253
1254#if QT_CONFIG(cursor)
1255 if (e.type == QEvent::Enter) {
1256 restoreMouseCursor(device: inputDevice);
1257 }
1258#endif
1259
1260 if (mWindowDecorationEnabled) {
1261 handleMouseEventWithDecoration(inputDevice, e);
1262 } else {
1263 switch (e.type) {
1264 case QEvent::Enter:
1265 QWindowSystemInterface::handleEnterEvent(window(), e.local, e.global);
1266#if QT_CONFIG(cursor)
1267 mDisplay->waylandCursor()->setPosFromEnterEvent(e.global.toPoint());
1268#endif
1269 break;
1270 case QEvent::MouseButtonPress:
1271 case QEvent::MouseButtonRelease:
1272 case QEvent::MouseMove:
1273 QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, device, e.local, e.global, e.buttons, e.button, e.type, e.modifiers);
1274 break;
1275 case QEvent::Wheel:
1276 QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, device, e.local, e.global,
1277 e.pixelDelta, e.angleDelta, e.modifiers,
1278 e.phase, e.source, e.inverted);
1279 break;
1280 default:
1281 Q_UNREACHABLE();
1282 }
1283 }
1284}
1285
1286#ifndef QT_NO_GESTURES
1287void QWaylandWindow::handleSwipeGesture(QWaylandInputDevice *inputDevice,
1288 const QWaylandPointerGestureSwipeEvent &e)
1289{
1290 switch (e.state) {
1291 case Qt::GestureStarted:
1292 if (mGestureState != GestureNotActive)
1293 qCWarning(lcQpaWaylandInput) << "Unexpected GestureStarted while already active";
1294
1295 if (mWindowDecorationEnabled && !mMouseEventsInContentArea) {
1296 // whole gesture sequence will be ignored
1297 mGestureState = GestureActiveInDecoration;
1298 return;
1299 }
1300
1301 mGestureState = GestureActiveInContentArea;
1302 QWindowSystemInterface::handleGestureEvent(window: window(), timestamp: e.timestamp,
1303 device: inputDevice->mTouchPadDevice,
1304 type: Qt::BeginNativeGesture,
1305 local: e.local, global: e.global, fingerCount: e.fingers);
1306 break;
1307 case Qt::GestureUpdated:
1308 if (mGestureState != GestureActiveInContentArea)
1309 return;
1310
1311 if (!e.delta.isNull()) {
1312 QWindowSystemInterface::handleGestureEventWithValueAndDelta(
1313 window: window(), timestamp: e.timestamp, device: inputDevice->mTouchPadDevice,
1314 type: Qt::PanNativeGesture,
1315 value: 0, delta: e.delta, local: e.local, global: e.global, fingerCount: e.fingers);
1316 }
1317 break;
1318 case Qt::GestureFinished:
1319 case Qt::GestureCanceled:
1320 if (mGestureState == GestureActiveInDecoration) {
1321 mGestureState = GestureNotActive;
1322 return;
1323 }
1324
1325 if (mGestureState != GestureActiveInContentArea)
1326 qCWarning(lcQpaWaylandInput) << "Unexpected" << (e.state == Qt::GestureFinished ? "GestureFinished" : "GestureCanceled");
1327
1328 mGestureState = GestureNotActive;
1329
1330 // There's currently no way to expose cancelled gestures to the rest of Qt, so
1331 // this part of information is lost.
1332 QWindowSystemInterface::handleGestureEvent(window: window(), timestamp: e.timestamp,
1333 device: inputDevice->mTouchPadDevice,
1334 type: Qt::EndNativeGesture,
1335 local: e.local, global: e.global, fingerCount: e.fingers);
1336 break;
1337 default:
1338 break;
1339 }
1340}
1341
1342void QWaylandWindow::handlePinchGesture(QWaylandInputDevice *inputDevice,
1343 const QWaylandPointerGesturePinchEvent &e)
1344{
1345 switch (e.state) {
1346 case Qt::GestureStarted:
1347 if (mGestureState != GestureNotActive)
1348 qCWarning(lcQpaWaylandInput) << "Unexpected GestureStarted while already active";
1349
1350 if (mWindowDecorationEnabled && !mMouseEventsInContentArea) {
1351 // whole gesture sequence will be ignored
1352 mGestureState = GestureActiveInDecoration;
1353 return;
1354 }
1355
1356 mGestureState = GestureActiveInContentArea;
1357 QWindowSystemInterface::handleGestureEvent(window: window(), timestamp: e.timestamp,
1358 device: inputDevice->mTouchPadDevice,
1359 type: Qt::BeginNativeGesture,
1360 local: e.local, global: e.global, fingerCount: e.fingers);
1361 break;
1362 case Qt::GestureUpdated:
1363 if (mGestureState != GestureActiveInContentArea)
1364 return;
1365
1366 if (!e.delta.isNull()) {
1367 QWindowSystemInterface::handleGestureEventWithValueAndDelta(
1368 window: window(), timestamp: e.timestamp, device: inputDevice->mTouchPadDevice,
1369 type: Qt::PanNativeGesture,
1370 value: 0, delta: e.delta, local: e.local, global: e.global, fingerCount: e.fingers);
1371 }
1372 if (e.rotation_delta != 0) {
1373 QWindowSystemInterface::handleGestureEventWithRealValue(window: window(), timestamp: e.timestamp,
1374 device: inputDevice->mTouchPadDevice,
1375 type: Qt::RotateNativeGesture,
1376 value: e.rotation_delta,
1377 local: e.local, global: e.global, fingerCount: e.fingers);
1378 }
1379 if (e.scale_delta != 0) {
1380 QWindowSystemInterface::handleGestureEventWithRealValue(window: window(), timestamp: e.timestamp,
1381 device: inputDevice->mTouchPadDevice,
1382 type: Qt::ZoomNativeGesture,
1383 value: e.scale_delta,
1384 local: e.local, global: e.global, fingerCount: e.fingers);
1385 }
1386 break;
1387 case Qt::GestureFinished:
1388 case Qt::GestureCanceled:
1389 if (mGestureState == GestureActiveInDecoration) {
1390 mGestureState = GestureNotActive;
1391 return;
1392 }
1393
1394 if (mGestureState != GestureActiveInContentArea)
1395 qCWarning(lcQpaWaylandInput) << "Unexpected" << (e.state == Qt::GestureFinished ? "GestureFinished" : "GestureCanceled");
1396
1397 mGestureState = GestureNotActive;
1398
1399 // There's currently no way to expose cancelled gestures to the rest of Qt, so
1400 // this part of information is lost.
1401 QWindowSystemInterface::handleGestureEvent(window: window(), timestamp: e.timestamp,
1402 device: inputDevice->mTouchPadDevice,
1403 type: Qt::EndNativeGesture,
1404 local: e.local, global: e.global, fingerCount: e.fingers);
1405 break;
1406 default:
1407 break;
1408 }
1409}
1410#endif // #ifndef QT_NO_GESTURES
1411
1412
1413bool QWaylandWindow::touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods)
1414{
1415 if (!mWindowDecorationEnabled)
1416 return false;
1417 return mWindowDecoration->handleTouch(inputDevice, local, global, state, mods);
1418}
1419
1420bool QWaylandWindow::handleTabletEventDecoration(QWaylandInputDevice *inputDevice,
1421 const QPointF &local, const QPointF &global,
1422 Qt::MouseButtons buttons,
1423 Qt::KeyboardModifiers modifiers)
1424{
1425 if (!mWindowDecorationEnabled)
1426 return false;
1427 return mWindowDecoration->handleMouse(inputDevice, local, global, b: buttons, mods: modifiers);
1428}
1429
1430void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e)
1431{
1432 // There's currently no way to get info about the actual hardware device in use.
1433 // At least we get the correct seat.
1434 const QPointingDevice *device = QPointingDevice::primaryPointingDevice(seatName: inputDevice->seatname());
1435 if (mMousePressedInContentArea == Qt::NoButton &&
1436 mWindowDecoration->handleMouse(inputDevice, local: e.local, global: e.global, b: e.buttons, mods: e.modifiers)) {
1437 if (mMouseEventsInContentArea) {
1438 QWindowSystemInterface::handleLeaveEvent(window());
1439 mMouseEventsInContentArea = false;
1440 }
1441 return;
1442 }
1443
1444 QMargins marg = clientSideMargins();
1445 QRect windowRect(0 + marg.left(),
1446 0 + marg.top(),
1447 geometry().size().width(),
1448 geometry().size().height());
1449 if (windowRect.contains(p: e.local.toPoint()) || mMousePressedInContentArea != Qt::NoButton) {
1450 const QPointF localTranslated = mapFromWlSurface(surfacePosition: e.local);
1451 QPointF globalTranslated = e.global;
1452 globalTranslated.setX(globalTranslated.x() - marg.left());
1453 globalTranslated.setY(globalTranslated.y() - marg.top());
1454 if (!mMouseEventsInContentArea) {
1455#if QT_CONFIG(cursor)
1456 restoreMouseCursor(device: inputDevice);
1457#endif
1458 QWindowSystemInterface::handleEnterEvent(window());
1459 }
1460
1461 switch (e.type) {
1462 case QEvent::Enter:
1463 QWindowSystemInterface::handleEnterEvent(window(), localTranslated, globalTranslated);
1464#if QT_CONFIG(cursor)
1465 mDisplay->waylandCursor()->setPosFromEnterEvent(e.global.toPoint());
1466#endif
1467 break;
1468 case QEvent::MouseButtonPress:
1469 case QEvent::MouseButtonRelease:
1470 case QEvent::MouseMove:
1471 QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, device, localTranslated, globalTranslated, e.buttons, e.button, e.type, e.modifiers);
1472 break;
1473 case QEvent::Wheel: {
1474 QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, device,
1475 localTranslated, globalTranslated,
1476 e.pixelDelta, e.angleDelta, e.modifiers,
1477 e.phase, e.source, e.inverted);
1478 break;
1479 }
1480 default:
1481 Q_UNREACHABLE();
1482 }
1483
1484 mMouseEventsInContentArea = true;
1485 mMousePressedInContentArea = e.buttons;
1486 } else {
1487 if (mMouseEventsInContentArea) {
1488 QWindowSystemInterface::handleLeaveEvent(window());
1489 mMouseEventsInContentArea = false;
1490 }
1491 }
1492}
1493
1494void QWaylandWindow::handleScreensChanged()
1495{
1496 QPlatformScreen *newScreen = calculateScreenFromSurfaceEvents();
1497
1498 if (!newScreen || newScreen->screen() == window()->screen())
1499 return;
1500
1501 QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(window(), newScreen->QPlatformScreen::screen());
1502
1503 if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup
1504 && window()->type() != Qt::ToolTip && window()->type() != Qt::Tool
1505 && geometry().topLeft() != newScreen->geometry().topLeft()) {
1506 auto geometry = this->geometry();
1507 geometry.moveTo(newScreen->geometry().topLeft());
1508 setGeometry(geometry);
1509 }
1510
1511 updateScale();
1512 updateBufferTransform();
1513}
1514
1515void QWaylandWindow::updateScale()
1516{
1517 if (mFractionalScale) {
1518 qreal preferredScale = mFractionalScale->preferredScale().value_or(1.0);
1519 preferredScale = std::max<qreal>(1.0, preferredScale);
1520 Q_ASSERT(mViewport);
1521 setScale(preferredScale);
1522 return;
1523 }
1524
1525 if (mSurface && mSurface->version() >= 6) {
1526 auto preferredScale = mSurface->preferredBufferScale().value_or(1);
1527 preferredScale = std::max(1, preferredScale);
1528 setScale(preferredScale);
1529 return;
1530 }
1531
1532 int scale = screen()->isPlaceholder() ? 1 : static_cast<QWaylandScreen *>(screen())->scale();
1533 setScale(scale);
1534}
1535
1536void QWaylandWindow::setScale(qreal newScale)
1537{
1538 if (mScale.has_value() && qFuzzyCompare(p1: mScale.value(), p2: newScale))
1539 return;
1540 mScale = newScale;
1541
1542 if (mSurface) {
1543 if (mViewport)
1544 updateViewport();
1545 else if (mSurface->version() >= 3)
1546 mSurface->set_buffer_scale(std::ceil(x: newScale));
1547 }
1548 ensureSize();
1549
1550 QWindowSystemInterface::handleWindowDevicePixelRatioChanged<QWindowSystemInterface::SynchronousDelivery>(window());
1551 if (isExposed()) {
1552 // redraw at the new DPR
1553 window()->requestUpdate();
1554 sendExposeEvent(rect: QRect(QPoint(), geometry().size()));
1555 }
1556}
1557
1558#if QT_CONFIG(cursor)
1559void QWaylandWindow::restoreMouseCursor(QWaylandInputDevice *device)
1560{
1561 if (const QCursor *overrideCursor = QGuiApplication::overrideCursor()) {
1562 applyCursor(device, cursor: *overrideCursor);
1563 } else if (mHasStoredCursor) {
1564 applyCursor(device, cursor: mStoredCursor);
1565 } else {
1566 applyCursor(device, cursor: QCursor(Qt::ArrowCursor));
1567 }
1568}
1569
1570void QWaylandWindow::resetStoredCursor()
1571{
1572 mHasStoredCursor = false;
1573}
1574
1575// Keep track of application set cursors on each window
1576void QWaylandWindow::setStoredCursor(const QCursor &cursor) {
1577 mStoredCursor = cursor;
1578 mHasStoredCursor = true;
1579}
1580
1581void QWaylandWindow::applyCursor(QWaylandInputDevice *device, const QCursor &cursor) {
1582 if (!device || !device->pointer() || device->pointer()->focusWindow() != this)
1583 return;
1584
1585 int fallbackBufferScale = qCeil(devicePixelRatio());
1586 device->setCursor(cursor: &cursor, cachedBuffer: {}, fallbackOutputScale: fallbackBufferScale);
1587}
1588#endif
1589
1590void QWaylandWindow::requestActivateWindow()
1591{
1592 if (mShellSurface)
1593 mShellSurface->requestActivate();
1594}
1595
1596bool QWaylandWindow::calculateExposure() const
1597{
1598 if (!window()->isVisible())
1599 return false;
1600
1601 if (mFrameCallbackTimedOut)
1602 return false;
1603
1604 if (mShellSurface)
1605 return mShellSurface->isExposed();
1606
1607 if (mSubSurfaceWindow)
1608 return mSubSurfaceWindow->parent()->isExposed();
1609
1610 return !(shouldCreateShellSurface() || shouldCreateSubSurface());
1611}
1612
1613void QWaylandWindow::updateExposure()
1614{
1615 bool exposed = calculateExposure();
1616 if (exposed == mExposed)
1617 return;
1618
1619 mExposed = exposed;
1620
1621 if (!exposed)
1622 sendExposeEvent(rect: QRect());
1623 else
1624 sendExposeEvent(rect: QRect(QPoint(), geometry().size()));
1625
1626 for (QWaylandSubSurface *subSurface : std::as_const(mChildren)) {
1627 auto subWindow = subSurface->window();
1628 subWindow->updateExposure();
1629 }
1630}
1631
1632bool QWaylandWindow::isExposed() const
1633{
1634 return mExposed;
1635}
1636
1637bool QWaylandWindow::isActive() const
1638{
1639 return mDisplay->isWindowActivated(window: this);
1640}
1641
1642qreal QWaylandWindow::scale() const
1643{
1644 return devicePixelRatio();
1645}
1646
1647qreal QWaylandWindow::devicePixelRatio() const
1648{
1649 return mScale.value_or(waylandScreen() ? waylandScreen()->scale() : 1);
1650}
1651
1652bool QWaylandWindow::setMouseGrabEnabled(bool grab)
1653{
1654 if (window()->type() != Qt::Popup) {
1655 qWarning(msg: "This plugin supports grabbing the mouse only for popup windows");
1656 return false;
1657 }
1658
1659 mMouseGrab = grab ? this : nullptr;
1660 return true;
1661}
1662
1663QWaylandWindow::ToplevelWindowTilingStates QWaylandWindow::toplevelWindowTilingStates() const
1664{
1665 return mLastReportedToplevelWindowTilingStates;
1666}
1667
1668void QWaylandWindow::handleToplevelWindowTilingStatesChanged(ToplevelWindowTilingStates states)
1669{
1670 mLastReportedToplevelWindowTilingStates = states;
1671}
1672
1673Qt::WindowStates QWaylandWindow::windowStates() const
1674{
1675 return mLastReportedWindowStates;
1676}
1677
1678void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states)
1679{
1680 createDecoration();
1681 Qt::WindowStates statesWithoutActive = states & ~Qt::WindowActive;
1682 Qt::WindowStates lastStatesWithoutActive = mLastReportedWindowStates & ~Qt::WindowActive;
1683 QWindowSystemInterface::handleWindowStateChanged(window(), statesWithoutActive,
1684 lastStatesWithoutActive);
1685 mLastReportedWindowStates = states;
1686}
1687
1688void QWaylandWindow::sendProperty(const QString &name, const QVariant &value)
1689{
1690 m_properties.insert(name, value);
1691 QWaylandNativeInterface *nativeInterface = static_cast<QWaylandNativeInterface *>(
1692 QGuiApplication::platformNativeInterface());
1693 nativeInterface->emitWindowPropertyChanged(this, name);
1694 if (mShellSurface)
1695 mShellSurface->sendProperty(name, value);
1696}
1697
1698void QWaylandWindow::setProperty(const QString &name, const QVariant &value)
1699{
1700 m_properties.insert(name, value);
1701 QWaylandNativeInterface *nativeInterface = static_cast<QWaylandNativeInterface *>(
1702 QGuiApplication::platformNativeInterface());
1703 nativeInterface->emitWindowPropertyChanged(this, name);
1704}
1705
1706QVariantMap QWaylandWindow::properties() const
1707{
1708 return m_properties;
1709}
1710
1711QVariant QWaylandWindow::property(const QString &name)
1712{
1713 return m_properties.value(name);
1714}
1715
1716QVariant QWaylandWindow::property(const QString &name, const QVariant &defaultValue)
1717{
1718 return m_properties.value(key: name, defaultValue);
1719}
1720
1721#ifdef QT_PLATFORM_WINDOW_HAS_VIRTUAL_SET_BACKING_STORE
1722void QWaylandWindow::setBackingStore(QPlatformBackingStore *store)
1723{
1724 mBackingStore = dynamic_cast<QWaylandShmBackingStore *>(store);
1725}
1726#endif
1727
1728void QWaylandWindow::timerEvent(QTimerEvent *event)
1729{
1730 if (event->timerId() != mFrameCallbackCheckIntervalTimerId)
1731 return;
1732
1733 {
1734 QMutexLocker lock(&mFrameSyncMutex);
1735
1736 const bool callbackTimerValid = mFrameCallbackElapsedTimer.isValid();
1737 const bool callbackTimerExpired = callbackTimerValid && mFrameCallbackElapsedTimer.hasExpired(timeout: mFrameCallbackTimeout);
1738 if (!callbackTimerValid || callbackTimerExpired) {
1739 killTimer(mFrameCallbackCheckIntervalTimerId);
1740 mFrameCallbackCheckIntervalTimerId = -1;
1741 }
1742 if (!callbackTimerValid || !callbackTimerExpired) {
1743 return;
1744 }
1745 mFrameCallbackElapsedTimer.invalidate();
1746 }
1747
1748 qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed";
1749 mFrameCallbackTimedOut = true;
1750 mWaitingForUpdate = false;
1751 updateExposure();
1752}
1753
1754void QWaylandWindow::requestUpdate()
1755{
1756 qCDebug(lcWaylandBackingstore) << "requestUpdate";
1757 Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA
1758
1759 // If we have a frame callback all is good and will be taken care of there
1760 {
1761 QMutexLocker locker(&mFrameSyncMutex);
1762 if (mWaitingForFrameCallback)
1763 return;
1764 }
1765
1766 // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet
1767 // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log
1768 // here so we can get this information when debugging update/frame callback issues.
1769 // Continue as nothing happened, though.
1770 if (mWaitingForUpdate)
1771 qCDebug(lcWaylandBackingstore) << "requestUpdate called twice without committing anything";
1772
1773 // Some applications (such as Qt Quick) depend on updates being delivered asynchronously,
1774 // so use invokeMethod to delay the delivery a bit.
1775 QMetaObject::invokeMethod(this, [this] {
1776 // Things might have changed in the meantime
1777 {
1778 QMutexLocker locker(&mFrameSyncMutex);
1779 if (mWaitingForFrameCallback)
1780 return;
1781 }
1782 if (hasPendingUpdateRequest())
1783 deliverUpdateRequest();
1784 }, Qt::QueuedConnection);
1785}
1786
1787// Should be called whenever we commit a buffer (directly through wl_surface.commit or indirectly
1788// with eglSwapBuffers) to know when it's time to commit the next one.
1789// Can be called from the render thread (without locking anything) so make sure to not make races in this method.
1790void QWaylandWindow::handleUpdate()
1791{
1792 mExposeEventNeedsAttachedBuffer = false;
1793 qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread();
1794
1795 // TODO: Should sync subsurfaces avoid requesting frame callbacks?
1796 QReadLocker lock(&mSurfaceLock);
1797 if (!mSurface)
1798 return;
1799
1800 QMutexLocker locker(&mFrameSyncMutex);
1801 if (mWaitingForFrameCallback)
1802 return;
1803
1804 struct ::wl_surface *wrappedSurface = reinterpret_cast<struct ::wl_surface *>(wl_proxy_create_wrapper(mSurface->object()));
1805 wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mDisplay->frameEventQueue());
1806 mFrameCallback = wl_surface_frame(wrappedSurface);
1807 wl_proxy_wrapper_destroy(wrappedSurface);
1808 wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this);
1809 mWaitingForFrameCallback = true;
1810 mWaitingForUpdate = false;
1811
1812 // Start a timer for handling the case when the compositor stops sending frame callbacks.
1813 if (mFrameCallbackTimeout > 0) {
1814 QMetaObject::invokeMethod(this, [this] {
1815 QMutexLocker locker(&mFrameSyncMutex);
1816
1817 if (mWaitingForFrameCallback) {
1818 if (mFrameCallbackCheckIntervalTimerId < 0)
1819 mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout);
1820 mFrameCallbackElapsedTimer.start();
1821 }
1822 }, Qt::QueuedConnection);
1823 }
1824}
1825
1826void QWaylandWindow::deliverUpdateRequest()
1827{
1828 qCDebug(lcWaylandBackingstore) << "deliverUpdateRequest";
1829 mWaitingForUpdate = true;
1830 QPlatformWindow::deliverUpdateRequest();
1831}
1832
1833void QWaylandWindow::addAttachOffset(const QPoint point)
1834{
1835 mOffset += point;
1836}
1837
1838void QWaylandWindow::propagateSizeHints()
1839{
1840 if (mShellSurface)
1841 mShellSurface->propagateSizeHints();
1842}
1843
1844bool QWaylandWindow::startSystemResize(Qt::Edges edges)
1845{
1846 if (auto *seat = display()->lastInputDevice()) {
1847 bool rc = mShellSurface && mShellSurface->resize(seat, edges);
1848 seat->handleEndDrag();
1849 return rc;
1850 }
1851 return false;
1852}
1853
1854bool QtWaylandClient::QWaylandWindow::startSystemMove()
1855{
1856 if (auto seat = display()->lastInputDevice()) {
1857 bool rc = mShellSurface && mShellSurface->move(seat);
1858 seat->handleEndDrag();
1859 return rc;
1860 }
1861 return false;
1862}
1863
1864bool QWaylandWindow::isOpaque() const
1865{
1866 return window()->requestedFormat().alphaBufferSize() <= 0;
1867}
1868
1869void QWaylandWindow::setOpaqueArea(const QRegion &opaqueArea)
1870{
1871 const QRegion translatedOpaqueArea = opaqueArea.translated(dx: clientSideMargins().left(), dy: clientSideMargins().top());
1872
1873 if (translatedOpaqueArea == mOpaqueArea || !mSurface)
1874 return;
1875
1876 mOpaqueArea = translatedOpaqueArea;
1877
1878 struct ::wl_region *region = mDisplay->createRegion(translatedOpaqueArea);
1879 mSurface->set_opaque_region(region);
1880 wl_region_destroy(region);
1881}
1882
1883void QWaylandWindow::requestXdgActivationToken(uint serial)
1884{
1885 if (!mShellSurface) {
1886 qCWarning(lcQpaWayland) << "requestXdgActivationToken is called with no surface role created, emitting synthetic signal";
1887 Q_EMIT xdgActivationTokenCreated({});
1888 return;
1889 }
1890 mShellSurface->requestXdgActivationToken(serial);
1891}
1892
1893void QWaylandWindow::setXdgActivationToken(const QString &token)
1894{
1895 if (mShellSurface)
1896 mShellSurface->setXdgActivationToken(token);
1897 else
1898 qCWarning(lcQpaWayland) << "setXdgActivationToken is called with no surface role created, token" << token << "discarded";
1899}
1900
1901void QWaylandWindow::addChildPopup(QWaylandWindow *child)
1902{
1903 if (mShellSurface)
1904 mShellSurface->attachPopup(popup: child->shellSurface());
1905 mChildPopups.append(child);
1906}
1907
1908void QWaylandWindow::removeChildPopup(QWaylandWindow *child)
1909{
1910 if (mShellSurface)
1911 mShellSurface->detachPopup(popup: child->shellSurface());
1912 mChildPopups.removeAll(child);
1913}
1914
1915void QWaylandWindow::closeChildPopups() {
1916 while (!mChildPopups.isEmpty()) {
1917 auto popup = mChildPopups.takeLast();
1918 popup->resetSurfaceRole();
1919 }
1920}
1921
1922void QWaylandWindow::reinit()
1923{
1924 if (window()->isVisible()) {
1925 initWindow();
1926 if (hasPendingUpdateRequest())
1927 deliverUpdateRequest();
1928 }
1929}
1930
1931bool QWaylandWindow::windowEvent(QEvent *event)
1932{
1933 if (event->type() == QEvent::ApplicationPaletteChange
1934 || event->type() == QEvent::ApplicationFontChange) {
1935 if (mWindowDecorationEnabled && window()->isVisible())
1936 mWindowDecoration->update();
1937 } else if (event->type() == QEvent::WindowUnblocked) {
1938 // QtGui sends leave event to window under cursor when modal window opens, so we have
1939 // to send enter event when modal closes and window has cursor and gets unblocked.
1940 if (auto *inputDevice = mDisplay->lastInputDevice(); inputDevice && inputDevice->pointerFocus() == this) {
1941 const auto pos = mDisplay->waylandCursor()->pos();
1942 QWindowSystemInterface::handleEnterEvent(window(), mapFromGlobalF(pos), pos);
1943 }
1944 }
1945
1946 return QPlatformWindow::windowEvent(event);
1947}
1948
1949QSurfaceFormat QWaylandWindow::format() const
1950{
1951 return mSurfaceFormat;
1952}
1953
1954}
1955
1956QT_END_NAMESPACE
1957
1958#include "moc_qwaylandwindow_p.cpp"
1959

source code of qtbase/src/plugins/platforms/wayland/qwaylandwindow.cpp