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