diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 46c85a6a3f2..5c3431a05ae 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -65,18 +65,6 @@ #include // for std::tie -static void checkWaylandError(struct wl_display *display) -{ - int ecode = wl_display_get_error(display); - if ((ecode == EPIPE || ecode == ECONNRESET)) { - // special case this to provide a nicer error - qWarning("The Wayland connection broke. Did the Wayland compositor die?"); - } else { - qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); - } - _exit(1); -} - QT_BEGIN_NAMESPACE namespace QtWaylandClient { @@ -114,9 +102,13 @@ public: * not only the one issued from event thread's waitForReading(), which means functions * called from dispatch_pending() can safely spin an event loop. */ + if (m_quitting) + return; + for (;;) { if (dispatchQueuePending() < 0) { - checkWaylandError(m_wldisplay); + Q_EMIT waylandError(); + m_quitting = true; return; } @@ -154,6 +146,7 @@ public: Q_SIGNALS: void needReadAndDispatch(); + void waylandError(); protected: void run() override @@ -328,11 +321,17 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) qRegisterMetaType("uint32_t"); mDisplay = wl_display_connect(nullptr); - if (!mDisplay) { + if (mDisplay) { + setupConnection(); + } else { qErrnoWarning(errno, "Failed to create wl_display"); - return; } + mWaylandTryReconnect = qEnvironmentVariableIsSet("QT_WAYLAND_RECONNECT"); +} + +void QWaylandDisplay::setupConnection() +{ struct ::wl_registry *registry = wl_display_get_registry(mDisplay); init(registry); @@ -404,7 +403,110 @@ void QWaylandDisplay::ensureScreen() Q_ASSERT(!QGuiApplication::screens().empty()); } -// Called in main thread, either from queued signal or directly. +void QWaylandDisplay::reconnect() +{ + qCWarning(lcQpaWayland) << "Attempting wayland reconnect"; + m_eventThread->stop(); + m_frameEventQueueThread->stop(); + m_eventThread->wait(); + m_frameEventQueueThread->wait(); + + qDeleteAll(mWaitingScreens); + mWaitingScreens.clear(); + + // mCompositor + mShm.reset(); + mCursorThemes.clear(); + mCursor.reset(); + mDndSelectionHandler.reset(); + mWindowExtension.reset(); + mSubCompositor.reset(); + mTouchExtension.reset(); + mQtKeyExtension.reset(); + mWindowManagerIntegration.reset(); + mTabletManager.reset(); + mPointerGestures.reset(); +#if QT_CONFIG(wayland_client_primary_selection) + mPrimarySelectionManager.reset(); +#endif + mTextInputMethodManager.reset(); + mTextInputManagerv1.reset(); + mTextInputManagerv2.reset(); + mTextInputManagerv4.reset(); + mHardwareIntegration.reset(); + mXdgOutputManager.reset(); + mViewporter.reset(); + mFractionalScaleManager.reset(); + + mWaylandIntegration->reset(); + + qDeleteAll(std::exchange(mInputDevices, {})); + mLastInputDevice = nullptr; + + auto screens = mScreens; + mScreens.clear(); + + for (const RegistryGlobal &global : mGlobals) { + emit globalRemoved(global); + } + mGlobals.clear(); + + mLastInputSerial = 0; + mLastInputWindow.clear(); + mLastKeyboardFocus.clear(); + mActiveWindows.clear(); + + const auto windows = QGuiApplication::allWindows(); + for (auto window : windows) { + if (auto waylandWindow = dynamic_cast(window->handle())) + waylandWindow->closeChildPopups(); + } + // Remove windows that do not need to be recreated and now closed popups + QList recreateWindows; + for (auto window : std::as_const(windows)) { + auto waylandWindow = dynamic_cast((window)->handle()); + if (waylandWindow && waylandWindow->wlSurface()) { + waylandWindow->reset(); + recreateWindows.push_back(waylandWindow); + } + } + + if (mSyncCallback) { + wl_callback_destroy(mSyncCallback); + mSyncCallback = nullptr; + } + + mDisplay = wl_display_connect(nullptr); + if (!mDisplay) + _exit(1); + + setupConnection(); + initialize(); + + if (m_frameEventQueue) + wl_event_queue_destroy(m_frameEventQueue); + initEventThread(); + + emit reconnected(); + + auto needsRecreate = [](QPlatformWindow *window) { + return window && !static_cast(window)->wlSurface(); + }; + auto window = recreateWindows.begin(); + while (!recreateWindows.isEmpty()) { + if (!needsRecreate((*window)->QPlatformWindow::parent()) && !needsRecreate((*window)->transientParent())) { + (*window)->reinit(); + window = recreateWindows.erase(window); + } else { + ++window; + } + if (window == recreateWindows.end()) + window = recreateWindows.begin(); + } + + mWaylandIntegration->reconfigureInputContext(); +} + void QWaylandDisplay::flushRequests() { m_eventThread->readAndDispatchEvents(); @@ -419,6 +521,8 @@ void QWaylandDisplay::initEventThread() new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, &QWaylandDisplay::flushRequests, Qt::QueuedConnection); + connect(m_eventThread.get(), &EventThread::waylandError, this, + &QWaylandDisplay::checkWaylandError, Qt::QueuedConnection); m_eventThread->start(); // wl_display_disconnect() free this. @@ -428,10 +532,31 @@ void QWaylandDisplay::initEventThread() m_frameEventQueueThread->start(); } +void QWaylandDisplay::checkWaylandError() +{ + int ecode = wl_display_get_error(mDisplay); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + if (mWaylandTryReconnect) { + reconnect(); + return; + } + } else { + qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + } + _exit(-1); +} + void QWaylandDisplay::blockingReadEvents() { - if (wl_display_dispatch(mDisplay) < 0) - checkWaylandError(mDisplay); + if (wl_display_dispatch(mDisplay) < 0) { + int ecode = wl_display_get_error(mDisplay); + if ((ecode == EPIPE || ecode == ECONNRESET)) + qWarning("The Wayland connection broke during blocking read event. Did the Wayland compositor die?"); + else + qWarning("The Wayland connection experienced a fatal error during blocking read event: %s", strerror(ecode)); + _exit(-1); + } } void QWaylandDisplay::checkTextInputProtocol() diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h index 640f33b8e57..f5c1bcbbd51 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay_p.h +++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h @@ -200,10 +200,14 @@ public slots: void flushRequests(); signals: + void reconnected(); void globalAdded(const RegistryGlobal &global); void globalRemoved(const RegistryGlobal &global); private: + void checkWaylandError(); + void reconnect(); + void setupConnection(); void handleWaylandSync(); void requestWaylandSync(); @@ -283,6 +287,7 @@ private: QList mActiveWindows; struct wl_callback *mSyncCallback = nullptr; static const wl_callback_listener syncCallbackListener; + bool mWaylandTryReconnect = false; bool mClientSideInputContextRequested = [] () { const QString& requested = QPlatformInputContextFactory::requested(); diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp index 13bacc9be72..45a247f9b10 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.cpp +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -503,6 +503,17 @@ QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QStr } } +void QWaylandIntegration::reset() +{ + mServerBufferIntegration.reset(); + mServerBufferIntegrationInitialized = false; + + mInputDeviceIntegration.reset(); + + mClientBufferIntegration.reset(); + mClientBufferIntegrationInitialized = false; +} + } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandintegration_p.h b/src/plugins/platforms/wayland/qwaylandintegration_p.h index 6221e6893b3..1ba7a63390f 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration_p.h +++ b/src/plugins/platforms/wayland/qwaylandintegration_p.h @@ -103,6 +103,7 @@ protected: QScopedPointer mDisplay; protected: + void reset(); virtual QPlatformNativeInterface *createPlatformNativeInterface(); QScopedPointer mClientBufferIntegration; diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp index b9bfc9c81f1..779ee45c7bc 100644 --- a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp @@ -136,7 +136,18 @@ QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDispla : QPlatformBackingStore(window) , mDisplay(display) { - + QObject::connect(mDisplay, &QWaylandDisplay::reconnected, window, [this]() { + auto copy = mBuffers; + // clear available buffers so we create new ones + // actual deletion is deferred till after resize call so we can copy + // contents from the back buffer + mBuffers.clear(); + mFrontBuffer = nullptr; + // resize always resets mBackBuffer + if (mRequestedSize.isValid() && waylandWindow()) + resize(mRequestedSize); + qDeleteAll(copy); + }); } QWaylandShmBackingStore::~QWaylandShmBackingStore() diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp index a77cce5ae8c..90f4c609af5 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow.cpp +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -291,11 +291,21 @@ void QWaylandWindow::reset() mFrameCallbackElapsedTimer.invalidate(); mWaitingForFrameCallback = false; } + if (mFrameCallbackCheckIntervalTimerId != -1) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; + } + mFrameCallbackTimedOut = false; mWaitingToApplyConfigure = false; + mCanResize = true; + mResizeDirty = false; + mOpaqueArea = QRegion(); mMask = QRegion(); + mQueuedBuffer = nullptr; + mQueuedBufferDamage = QRegion(); mDisplay->handleWindowDestroyed(this); } @@ -1623,6 +1633,16 @@ void QWaylandWindow::closeChildPopups() { popup->reset(); } } + +void QWaylandWindow::reinit() +{ + if (window()->isVisible()) { + initWindow(); + if (hasPendingUpdateRequest()) + deliverUpdateRequest(); + } +} + } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h index 22b42a9a0e3..d0640e7eb2f 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow_p.h +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -229,6 +229,9 @@ public: void removeChildPopup(QWaylandWindow* child); void closeChildPopups(); + virtual void reinit(); + void reset(); + public slots: void applyConfigure(); @@ -325,8 +328,6 @@ private: void initializeWlSurface(); bool shouldCreateShellSurface() const; bool shouldCreateSubSurface() const; - void reset(); - static void closePopups(QWaylandWindow *parent); QPlatformScreen *calculateScreenFromSurfaceEvents() const; void setOpaqueArea(const QRegion &opaqueArea); bool isOpaque() const; diff --git a/tests/auto/wayland/CMakeLists.txt b/tests/auto/wayland/CMakeLists.txt index 44cf32714cb..5ae005eaafe 100644 --- a/tests/auto/wayland/CMakeLists.txt +++ b/tests/auto/wayland/CMakeLists.txt @@ -16,6 +16,7 @@ if (NOT WEBOS) add_subdirectory(nooutput) add_subdirectory(output) add_subdirectory(primaryselectionv1) + add_subdirectory(reconnect) add_subdirectory(seatv4) add_subdirectory(seatv7) add_subdirectory(seat) diff --git a/tests/auto/wayland/reconnect/CMakeLists.txt b/tests/auto/wayland/reconnect/CMakeLists.txt new file mode 100644 index 00000000000..21ce68fd501 --- /dev/null +++ b/tests/auto/wayland/reconnect/CMakeLists.txt @@ -0,0 +1,11 @@ +##################################################################### +## tst_client Test: +##################################################################### + +qt_internal_add_test(tst_reconnect + SOURCES + wl-socket.c + tst_reconnect.cpp + PUBLIC_LIBRARIES + SharedClientTest + ) diff --git a/tests/auto/wayland/reconnect/tst_reconnect.cpp b/tests/auto/wayland/reconnect/tst_reconnect.cpp new file mode 100644 index 00000000000..93007d4cc9b --- /dev/null +++ b/tests/auto/wayland/reconnect/tst_reconnect.cpp @@ -0,0 +1,210 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mockcompositor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(opengl) +#include +#endif +#include + +#include +#include +#include + +#include "wl-socket.h" + +using namespace MockCompositor; + +class TestWindow : public QRasterWindow +{ +public: + TestWindow() + { + } + + void focusInEvent(QFocusEvent *) override + { + ++focusInEventCount; + } + + void focusOutEvent(QFocusEvent *) override + { + ++focusOutEventCount; + } + + void keyPressEvent(QKeyEvent *event) override + { + ++keyPressEventCount; + keyCode = event->nativeScanCode(); + } + + void keyReleaseEvent(QKeyEvent *event) override + { + ++keyReleaseEventCount; + keyCode = event->nativeScanCode(); + } + + void mousePressEvent(QMouseEvent *event) override + { + ++mousePressEventCount; + mousePressPos = event->position().toPoint(); + } + + void mouseReleaseEvent(QMouseEvent *) override + { + ++mouseReleaseEventCount; + } + + void touchEvent(QTouchEvent *event) override + { + Q_UNUSED(event); + ++touchEventCount; + } + + QPoint frameOffset() const { return QPoint(frameMargins().left(), frameMargins().top()); } + + int focusInEventCount = 0; + int focusOutEventCount = 0; + int keyPressEventCount = 0; + int keyReleaseEventCount = 0; + int mousePressEventCount = 0; + int mouseReleaseEventCount = 0; + int touchEventCount = 0; + + uint keyCode = 0; + QPoint mousePressPos; +}; + +class tst_WaylandReconnect : public QObject +{ + Q_OBJECT +public: + tst_WaylandReconnect(); + void triggerReconnect(); + + template + auto exec(function_type func, arg_types&&... args) -> decltype(func()) + { + return m_comp->exec(func, std::forward(args)...); + } + +private Q_SLOTS: +//core + void cleanup() { QTRY_VERIFY2(m_comp->isClean(), qPrintable(m_comp->dirtyMessage())); } + void basicWindow(); + +//input + void keyFocus(); + +private: + void configureWindow(); + QScopedPointer m_comp; + wl_socket *m_socket; +}; + +tst_WaylandReconnect::tst_WaylandReconnect() +{ + m_socket = wl_socket_create(); + QVERIFY(m_socket); + const int socketFd = wl_socket_get_fd(m_socket); + const QByteArray socketName = wl_socket_get_display_name(m_socket); + qputenv("WAYLAND_DISPLAY", socketName); + + m_comp.reset(new DefaultCompositor(CoreCompositor::Default, dup(socketFd))); +} + +void tst_WaylandReconnect::triggerReconnect() +{ + const int socketFd = wl_socket_get_fd(m_socket); + m_comp.reset(new DefaultCompositor(CoreCompositor::Default, dup(socketFd))); + QTest::qWait(50); //we need to spin the main loop to actually reconnect +} + +void tst_WaylandReconnect::basicWindow() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); + + triggerReconnect(); + + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); +} + +void tst_WaylandReconnect::keyFocus() +{ + TestWindow window; + window.resize(64, 48); + window.show(); + + configureWindow(); + QTRY_VERIFY(window.isExposed()); + exec([=] { + m_comp->keyboard()->sendEnter(m_comp->surface()); + }); + QTRY_COMPARE(window.focusInEventCount, 1); + + uint keyCode = 80; + QCOMPARE(window.keyPressEventCount, 0); + exec([=] { + m_comp->keyboard()->sendKey(m_comp->client(), keyCode - 8, Keyboard::key_state_pressed); + }); + QTRY_COMPARE(window.keyPressEventCount, 1); + QCOMPARE(QGuiApplication::focusWindow(), &window); + + triggerReconnect(); + configureWindow(); + + // on reconnect our knowledge of focus is reset to a clean slate + QCOMPARE(QGuiApplication::focusWindow(), nullptr); + QTRY_COMPARE(window.focusOutEventCount, 1); + + // fake the user explicitly focussing this window afterwards + exec([=] { + m_comp->keyboard()->sendEnter(m_comp->surface()); + }); + exec([=] { + m_comp->keyboard()->sendKey(m_comp->client(), keyCode - 8, Keyboard::key_state_pressed); + }); + QTRY_COMPARE(window.focusInEventCount, 2); + QTRY_COMPARE(window.keyPressEventCount, 2); +} + + +void tst_WaylandReconnect::configureWindow() +{ + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); + m_comp->exec([=] { + m_comp->xdgToplevel()->sendConfigure({0, 0}, {}); + const uint serial = m_comp->nextSerial(); // Let the window decide the size + m_comp->xdgSurface()->sendConfigure(serial); + }); +} + +int main(int argc, char **argv) +{ + // Note when debugging that a failing reconnect will exit this + // test rather than fail. Making sure it finishes is important! + + QTemporaryDir tmpRuntimeDir; + setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin + setenv("QT_WAYLAND_RECONNECT", "1", 1); + setenv("XDG_CURRENT_DESKTOP", "qtwaylandtests", 1); + + tst_WaylandReconnect tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_reconnect.moc" diff --git a/tests/auto/wayland/reconnect/wl-socket.c b/tests/auto/wayland/reconnect/wl-socket.c new file mode 100644 index 00000000000..f96de8c73bf --- /dev/null +++ b/tests/auto/wayland/reconnect/wl-socket.c @@ -0,0 +1,166 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* This is the size of the char array in struct sock_addr_un. + * No Wayland socket can be created with a path longer than this, + * including the null terminator. + */ +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#define LOCK_SUFFIX ".lock" +#define LOCK_SUFFIXLEN 5 + +struct wl_socket { + int fd; + int fd_lock; + struct sockaddr_un addr; + char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN]; + char display_name[16]; +}; + +static struct wl_socket *wl_socket_alloc(void) +{ + struct wl_socket *s; + + s = malloc(sizeof *s); + if (!s) + return NULL; + + s->fd = -1; + s->fd_lock = -1; + + return s; +} + +static int wl_socket_lock(struct wl_socket *socket) +{ + struct stat socket_stat; + + snprintf(socket->lock_addr, sizeof socket->lock_addr, "%s%s", socket->addr.sun_path, LOCK_SUFFIX); + + // differening from kwin, we're back to setting CLOEXEC as we're all in process + socket->fd_lock = open(socket->lock_addr, O_CREAT | O_RDWR | O_CLOEXEC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + + if (socket->fd_lock < 0) { + printf("unable to open lockfile %s check permissions\n", socket->lock_addr); + goto err; + } + + if (flock(socket->fd_lock, LOCK_EX | LOCK_NB) < 0) { + printf("unable to lock lockfile %s, maybe another compositor is running\n", socket->lock_addr); + goto err_fd; + } + + if (lstat(socket->addr.sun_path, &socket_stat) < 0) { + if (errno != ENOENT) { + printf("did not manage to stat file %s\n", socket->addr.sun_path); + goto err_fd; + } + } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) { + unlink(socket->addr.sun_path); + } + + return 0; +err_fd: + close(socket->fd_lock); + socket->fd_lock = -1; +err: + *socket->lock_addr = 0; + /* we did not set this value here, but without lock the + * socket won't be created anyway. This prevents the + * wl_socket_destroy from unlinking already existing socket + * created by other compositor */ + *socket->addr.sun_path = 0; + + return -1; +} + +void wl_socket_destroy(struct wl_socket *s) +{ + if (s->addr.sun_path[0]) + unlink(s->addr.sun_path); + if (s->fd >= 0) + close(s->fd); + if (s->lock_addr[0]) + unlink(s->lock_addr); + if (s->fd_lock >= 0) + close(s->fd_lock); + + free(s); +} + +const char *wl_socket_get_display_name(struct wl_socket *s) +{ + return s->display_name; +} + +int wl_socket_get_fd(struct wl_socket *s) +{ + return s->fd; +} + +struct wl_socket *wl_socket_create() +{ + struct wl_socket *s; + int displayno = 0; + int name_size; + + /* A reasonable number of maximum default sockets. If + * you need more than this, use the explicit add_socket API. */ + const int MAX_DISPLAYNO = 32; + const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (!runtime_dir) { + printf("XDG_RUNTIME_DIR not set"); + return NULL; + } + + s = wl_socket_alloc(); + if (s == NULL) + return NULL; + + do { + snprintf(s->display_name, sizeof s->display_name, "wayland-%d", displayno); + s->addr.sun_family = AF_LOCAL; + name_size = snprintf(s->addr.sun_path, sizeof s->addr.sun_path, "%s/%s", runtime_dir, s->display_name) + 1; + assert(name_size > 0); + + if (name_size > (int)sizeof s->addr.sun_path) { + goto fail; + } + + if (wl_socket_lock(s) < 0) + continue; + + s->fd = socket(PF_LOCAL, SOCK_STREAM, 0); + + int size = SUN_LEN(&s->addr); + int ret = bind(s->fd, (struct sockaddr*)&s->addr, size); + if (ret < 0) { + goto fail; + } + ret = listen(s->fd, 128); + if (ret < 0) { + goto fail; + } + return s; + } while (displayno++ < MAX_DISPLAYNO); + +fail: + wl_socket_destroy(s); + return NULL; +} diff --git a/tests/auto/wayland/reconnect/wl-socket.h b/tests/auto/wayland/reconnect/wl-socket.h new file mode 100644 index 00000000000..285870e0179 --- /dev/null +++ b/tests/auto/wayland/reconnect/wl-socket.h @@ -0,0 +1,34 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Allocate and create a socket + * It is bound and accepted + */ +struct wl_socket *wl_socket_create(); + +/** + * Returns the file descriptor for the socket + */ +int wl_socket_get_fd(struct wl_socket *); + +/** + * Returns the name of the socket, i.e "wayland-0" + */ +char *wl_socket_get_display_name(struct wl_socket *); + +/** + * Cleanup resources and close the FD + */ +void wl_socket_destroy(struct wl_socket *socket); + +#ifdef __cplusplus +} +#endif diff --git a/tests/auto/wayland/shared/corecompositor.cpp b/tests/auto/wayland/shared/corecompositor.cpp index dd7311e41ce..9b2c7deae1d 100644 --- a/tests/auto/wayland/shared/corecompositor.cpp +++ b/tests/auto/wayland/shared/corecompositor.cpp @@ -6,10 +6,9 @@ namespace MockCompositor { -CoreCompositor::CoreCompositor(CompositorType t) +CoreCompositor::CoreCompositor(CompositorType t, int socketFd) : m_type(t) , m_display(wl_display_create()) - , m_socketName(wl_display_add_socket_auto(m_display)) , m_eventLoop(wl_display_get_event_loop(m_display)) // Start dispatching @@ -20,7 +19,12 @@ CoreCompositor::CoreCompositor(CompositorType t) } }) { - qputenv("WAYLAND_DISPLAY", m_socketName); + if (socketFd == -1) { + QByteArray socketName = wl_display_add_socket_auto(m_display); + qputenv("WAYLAND_DISPLAY", socketName); + } else { + wl_display_add_socket_fd(m_display, socketFd); + } m_timer.start(); Q_ASSERT(isClean()); } @@ -29,7 +33,9 @@ CoreCompositor::~CoreCompositor() { m_running = false; m_dispatchThread.join(); + wl_display_destroy_clients(m_display); wl_display_destroy(m_display); + qDebug() << "cleanup"; } bool CoreCompositor::isClean() diff --git a/tests/auto/wayland/shared/corecompositor.h b/tests/auto/wayland/shared/corecompositor.h index b7d1de78d79..6fd14371c3e 100644 --- a/tests/auto/wayland/shared/corecompositor.h +++ b/tests/auto/wayland/shared/corecompositor.h @@ -29,7 +29,8 @@ public: }; CompositorType m_type = Default; - explicit CoreCompositor(CompositorType t = Default); + explicit CoreCompositor(CompositorType t = Default, int socketFd = -1); + ~CoreCompositor(); bool isClean(); QString dirtyMessage(); @@ -178,7 +179,6 @@ protected: CoreCompositor *m_compositor = nullptr; std::thread::id m_threadId; }; - QByteArray m_socketName; wl_event_loop *m_eventLoop = nullptr; bool m_running = true; QList m_globals; diff --git a/tests/auto/wayland/shared/mockcompositor.cpp b/tests/auto/wayland/shared/mockcompositor.cpp index 71f3775a5ae..43d417ff8bf 100644 --- a/tests/auto/wayland/shared/mockcompositor.cpp +++ b/tests/auto/wayland/shared/mockcompositor.cpp @@ -6,8 +6,8 @@ namespace MockCompositor { -DefaultCompositor::DefaultCompositor(CompositorType t) - : CoreCompositor(t) +DefaultCompositor::DefaultCompositor(CompositorType t, int socketFd) + : CoreCompositor(t, socketFd) { { Lock l(this); diff --git a/tests/auto/wayland/shared/mockcompositor.h b/tests/auto/wayland/shared/mockcompositor.h index 6803a646c5e..8d26361a4f9 100644 --- a/tests/auto/wayland/shared/mockcompositor.h +++ b/tests/auto/wayland/shared/mockcompositor.h @@ -32,7 +32,7 @@ namespace MockCompositor { class DefaultCompositor : public CoreCompositor { public: - explicit DefaultCompositor(CompositorType t = CompositorType::Default); + explicit DefaultCompositor(CompositorType t = CompositorType::Default, int socketFd = -1); // Convenience functions Output *output(int i = 0) { return getAll().value(i, nullptr); } Surface *surface(int i = 0);