Introduce path for surviving compositor restarts

This patch introduces an optional mechanism for clients to survive a
crash and reconnect seemingly seamlessly.

In the event of a disconnect from the compositor socket we simply try to
reconnect again and replay any data needed so that we maintain a
consistent state to where we left off.

From an application point-of-view any open popups will be dismissed and
we we potentially get a new framecallback, but it will be almost
entirely transparent. Users of custom QWaylandClientExtensions will be
notified via the activeChanged signal and rebuild as though the
compositor had withdrawn and re-announced the global.

OpenGL contexts will be marked as invalid, and handled the same way as a
GPU reset. On the next frame RHI will notice these are invalid and
recreate them, only now against a new wl_display and new EGLDisplay.

Users of low level EGL/native objects might be affected, but the
alternative at this point is being closed anyway. The entire codepath is
only activated via an environment variable.

Change-Id: I6c4acc885540e14cead7640794df86dd974fef4f
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
David Edmundson 2022-01-28 16:24:32 +00:00
parent 0042b8040f
commit 358655d8db
16 changed files with 631 additions and 29 deletions

View File

@ -65,18 +65,6 @@
#include <tuple> // 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>("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<QWaylandWindow *>(window->handle()))
waylandWindow->closeChildPopups();
}
// Remove windows that do not need to be recreated and now closed popups
QList<QWaylandWindow *> recreateWindows;
for (auto window : std::as_const(windows)) {
auto waylandWindow = dynamic_cast<QWaylandWindow*>((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<QWaylandWindow *>(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()

View File

@ -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<QWaylandWindow *> mActiveWindows;
struct wl_callback *mSyncCallback = nullptr;
static const wl_callback_listener syncCallbackListener;
bool mWaylandTryReconnect = false;
bool mClientSideInputContextRequested = [] () {
const QString& requested = QPlatformInputContextFactory::requested();

View File

@ -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

View File

@ -103,6 +103,7 @@ protected:
QScopedPointer<QWaylandDisplay> mDisplay;
protected:
void reset();
virtual QPlatformNativeInterface *createPlatformNativeInterface();
QScopedPointer<QWaylandClientBufferIntegration> mClientBufferIntegration;

View File

@ -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()

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -0,0 +1,11 @@
#####################################################################
## tst_client Test:
#####################################################################
qt_internal_add_test(tst_reconnect
SOURCES
wl-socket.c
tst_reconnect.cpp
PUBLIC_LIBRARIES
SharedClientTest
)

View File

@ -0,0 +1,210 @@
// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "mockcompositor.h"
#include <QBackingStore>
#include <QPainter>
#include <QScreen>
#include <QWindow>
#include <QMimeData>
#include <QPixmap>
#include <QDrag>
#include <QWindow>
#if QT_CONFIG(opengl)
#include <QOpenGLWindow>
#endif
#include <QRasterWindow>
#include <QtTest/QtTest>
#include <QtWaylandClient/private/qwaylandintegration_p.h>
#include <QtGui/private/qguiapplication_p.h>
#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<typename function_type, typename... arg_types>
auto exec(function_type func, arg_types&&... args) -> decltype(func())
{
return m_comp->exec(func, std::forward<arg_types>(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<DefaultCompositor> 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"

View File

@ -0,0 +1,166 @@
// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#define _DEFAULT_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
/* 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;
}

View File

@ -0,0 +1,34 @@
// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
// 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

View File

@ -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()

View File

@ -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<Global *> m_globals;

View File

@ -6,8 +6,8 @@
namespace MockCompositor {
DefaultCompositor::DefaultCompositor(CompositorType t)
: CoreCompositor(t)
DefaultCompositor::DefaultCompositor(CompositorType t, int socketFd)
: CoreCompositor(t, socketFd)
{
{
Lock l(this);

View File

@ -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<Output>().value(i, nullptr); }
Surface *surface(int i = 0);