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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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