diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp index 03aedd6b63a..34845c63922 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.cpp +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -359,7 +359,7 @@ void QWaylandIntegration::initializeServerBufferIntegration() disableHardwareIntegration = disableHardwareIntegration || !mDisplay->hardwareIntegration(); if (disableHardwareIntegration) { QByteArray serverBufferIntegrationName = qgetenv("QT_WAYLAND_SERVER_BUFFER_INTEGRATION"); - QString targetKey = QString::fromLocal8Bit(serverBufferIntegrationName); + targetKey = QString::fromLocal8Bit(serverBufferIntegrationName); } else { targetKey = mDisplay->hardwareIntegration()->serverBufferIntegration(); } diff --git a/src/plugins/platforms/wayland/qwaylandscreen.cpp b/src/plugins/platforms/wayland/qwaylandscreen.cpp index 2f90be813cc..b6336f2e6df 100644 --- a/src/plugins/platforms/wayland/qwaylandscreen.cpp +++ b/src/plugins/platforms/wayland/qwaylandscreen.cpp @@ -185,6 +185,12 @@ QWaylandScreen * QWaylandScreen::waylandScreenFromWindow(QWindow *window) return static_cast(platformScreen); } +QWaylandScreen *QWaylandScreen::fromWlOutput(::wl_output *output) +{ + auto wlOutput = static_cast(wl_output_get_user_data(output)); + return static_cast(wlOutput); +} + void QWaylandScreen::output_mode(uint32_t flags, int width, int height, int refresh) { if (!(flags & WL_OUTPUT_MODE_CURRENT)) diff --git a/src/plugins/platforms/wayland/qwaylandscreen_p.h b/src/plugins/platforms/wayland/qwaylandscreen_p.h index 526c94878f9..674e1a9134e 100644 --- a/src/plugins/platforms/wayland/qwaylandscreen_p.h +++ b/src/plugins/platforms/wayland/qwaylandscreen_p.h @@ -102,6 +102,7 @@ public: ::wl_output *output() { return object(); } static QWaylandScreen *waylandScreenFromWindow(QWindow *window); + static QWaylandScreen *fromWlOutput(::wl_output *output); private: void output_mode(uint32_t flags, int width, int height, int refresh) override; diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp index cd846c0ba0e..6d50b0d72de 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow.cpp +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -69,6 +69,8 @@ #include +#include + QT_BEGIN_NAMESPACE namespace QtWaylandClient { @@ -78,15 +80,13 @@ QWaylandWindow *QWaylandWindow::mMouseGrab = 0; QWaylandWindow::QWaylandWindow(QWindow *window) : QObject() , QPlatformWindow(window) - , mScreen(QWaylandScreen::waylandScreenFromWindow(window)) - , mDisplay(mScreen->display()) + , mDisplay(waylandScreen()->display()) , mShellSurface(0) , mSubSurfaceWindow(0) , mWindowDecoration(0) , mMouseEventsInContentArea(false) , mMousePressedInContentArea(Qt::NoButton) , mWaitingForFrameSync(false) - , mFrameCallback(nullptr) , mRequestResizeSent(false) , mCanResize(true) , mResizeDirty(false) @@ -100,6 +100,7 @@ QWaylandWindow::QWaylandWindow(QWindow *window) { static WId id = 1; mWindowId = id++; + connect(qApp, &QGuiApplication::screenRemoved, this, &QWaylandWindow::handleScreenRemoved); initializeWlSurface(); } @@ -349,9 +350,31 @@ void QWaylandWindow::sendExposeEvent(const QRect &rect) QWindowSystemInterface::handleExposeEvent(window(), rect); } + +static QVector> activePopups; + +void QWaylandWindow::closePopups(QWaylandWindow *parent) +{ + while (!activePopups.isEmpty()) { + auto popup = activePopups.takeLast(); + if (popup.isNull()) + continue; + if (popup.data() == parent) + return; + popup->reset(); + } +} + +QWaylandScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const +{ + return mScreens.isEmpty() ? waylandScreen() : mScreens.first(); +} + void QWaylandWindow::setVisible(bool visible) { if (visible) { + if (window()->type() == Qt::Popup) + activePopups << this; initWindow(); mDisplay->flushRequests(); @@ -365,6 +388,8 @@ void QWaylandWindow::setVisible(bool visible) // case 'this' will be deleted. When that happens, we must abort right away. QPointer deleteGuard(this); QWindowSystemInterface::flushWindowSystemEvents(); + if (!deleteGuard.isNull() && window()->type() == Qt::Popup) + closePopups(this); if (!deleteGuard.isNull()) reset(); } @@ -480,14 +505,63 @@ void QWaylandWindow::requestResize() QWindowSystemInterface::flushWindowSystemEvents(); } +void QWaylandWindow::surface_enter(wl_output *output) +{ + QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); + auto addedScreen = QWaylandScreen::fromWlOutput(output); + + if (mScreens.contains(addedScreen)) { + qWarning() << "Unexpected wl_surface.enter received for output with id:" + << wl_proxy_get_id(reinterpret_cast(output)) + << "screen name:" << addedScreen->name() << "screen model:" << addedScreen->model(); + return; + } + + mScreens.append(addedScreen); + + QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); + if (oldScreen != newScreen) //currently this will only happen if the first wl_surface.enter is for a non-primary screen + QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); +} + +void QWaylandWindow::surface_leave(wl_output *output) +{ + QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); + auto *removedScreen = QWaylandScreen::fromWlOutput(output); + bool wasRemoved = mScreens.removeOne(removedScreen); + if (!wasRemoved) { + qWarning() << "Unexpected wl_surface.leave received for output with id:" + << wl_proxy_get_id(reinterpret_cast(output)) + << "screen name:" << removedScreen->name() << "screen model:" << removedScreen->model(); + return; + } + + QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); + if (oldScreen != newScreen) + QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); +} + +void QWaylandWindow::handleScreenRemoved(QScreen *qScreen) +{ + QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); + bool wasRemoved = mScreens.removeOne(static_cast(qScreen->handle())); + if (wasRemoved) { + QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); + if (oldScreen != newScreen) + QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); + } +} + void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { - mFrameCallback = nullptr; + if (mFrameCallback) { + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + } if (buffer) { - auto callback = frame(); - wl_callback_add_listener(callback, &QWaylandWindow::callbackListener, this); - mFrameCallback = callback; + mFrameCallback = frame(); + wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); mWaitingForFrameSync = true; buffer->setBusy(); @@ -530,8 +604,6 @@ void QWaylandWindow::frameCallback(void *data, struct wl_callback *callback, uin QWaylandWindow *self = static_cast(data); self->mWaitingForFrameSync = false; - wl_callback_destroy(callback); - self->mFrameCallback.testAndSetRelaxed(callback, nullptr); if (self->mUpdateRequested) { QWindowPrivate *w = QWindowPrivate::get(self->window()); self->mUpdateRequested = false; @@ -568,6 +640,11 @@ QWaylandSubSurface *QWaylandWindow::subSurfaceWindow() const return mSubSurfaceWindow; } +QWaylandScreen *QWaylandWindow::waylandScreen() const +{ + return static_cast(QPlatformWindow::screen()); +} + void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation) { if (mDisplay->compositorVersion() < 2) @@ -832,7 +909,7 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe #if QT_CONFIG(cursor) void QWaylandWindow::setMouseCursor(QWaylandInputDevice *device, const QCursor &cursor) { - device->setCursor(cursor, mScreen); + device->setCursor(cursor, waylandScreen()); } void QWaylandWindow::restoreMouseCursor(QWaylandInputDevice *device) diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h index 6e07ff1c7a3..4cebcd7afd9 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow_p.h +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -53,7 +53,6 @@ #include #include -#include #include #include @@ -143,7 +142,7 @@ public: QWaylandDisplay *display() const { return mDisplay; } QWaylandShellSurface *shellSurface() const; QWaylandSubSurface *subSurfaceWindow() const; - QWaylandScreen *waylandScreen() const { return mScreen; } + QWaylandScreen *waylandScreen() const; void handleContentOrientationChange(Qt::ScreenOrientation orientation) override; void setOrientationMask(Qt::ScreenOrientations mask); @@ -210,7 +209,10 @@ public slots: void requestResize(); protected: - QWaylandScreen *mScreen; + void surface_enter(struct ::wl_output *output) override; + void surface_leave(struct ::wl_output *output) override; + + QVector mScreens; //As seen by wl_surface.enter/leave events. Chronological order. QWaylandDisplay *mDisplay; QWaylandShellSurface *mShellSurface; QWaylandSubSurface *mSubSurfaceWindow; @@ -222,7 +224,7 @@ protected: WId mWindowId; bool mWaitingForFrameSync; - QAtomicPointer mFrameCallback; + struct ::wl_callback *mFrameCallback = nullptr; QWaitCondition mFrameSyncWait; QMutex mResizeLock; @@ -245,6 +247,9 @@ protected: QWaylandShmBackingStore *mBackingStore; +private slots: + void handleScreenRemoved(QScreen *qScreen); + private: bool setWindowStateInternal(Qt::WindowStates flags); void setGeometry_helper(const QRect &rect); @@ -254,6 +259,8 @@ private: bool shouldCreateSubSurface() const; void reset(bool sendDestroyEvent = true); void sendExposeEvent(const QRect &rect); + static void closePopups(QWaylandWindow *parent); + QWaylandScreen *calculateScreenFromSurfaceEvents() const; void handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); diff --git a/tests/auto/wayland/client/tst_client.cpp b/tests/auto/wayland/client/tst_client.cpp index 0c9c007a298..8acddfbe740 100644 --- a/tests/auto/wayland/client/tst_client.cpp +++ b/tests/auto/wayland/client/tst_client.cpp @@ -142,6 +142,7 @@ private slots: void backingStore(); void touchDrag(); void mouseDrag(); + void dontCrashOnMultipleCommits(); private: MockCompositor *compositor; @@ -333,6 +334,32 @@ void tst_WaylandClient::mouseDrag() QTRY_VERIFY(window.dragStarted); } +void tst_WaylandClient::dontCrashOnMultipleCommits() +{ + auto window = new TestWindow(); + window->show(); + + QRect rect(QPoint(), window->size()); + + QBackingStore backingStore(window); + backingStore.resize(rect.size()); + backingStore.beginPaint(rect); + QPainter p(backingStore.paintDevice()); + p.fillRect(rect, Qt::magenta); + p.end(); + backingStore.endPaint(); + + backingStore.flush(rect); + backingStore.flush(rect); + backingStore.flush(rect); + + compositor->processWaylandEvents(); + + delete window; + + QTRY_VERIFY(!compositor->surface()); +} + int main(int argc, char **argv) { setenv("XDG_RUNTIME_DIR", ".", 1);