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

source code of qtwayland/src/client/qwaylandwindow.cpp