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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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