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