diff --git a/autotests/wayland/dont_crash_empty_deco.cpp b/autotests/wayland/dont_crash_empty_deco.cpp index 1b74bf878..7ae4a2e1f 100644 --- a/autotests/wayland/dont_crash_empty_deco.cpp +++ b/autotests/wayland/dont_crash_empty_deco.cpp @@ -1,221 +1,224 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "abstract_backend.h" #include "client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_empty_decoration-0"); class DontCrashEmptyDecorationTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testBug361551(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::ServerSideDecorationManager *m_deco = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void DontCrashEmptyDecorationTest::initTestCase() { + if (!QFile::exists(QStringLiteral("/dev/dri/card0"))) { + QSKIP("Needs a dri device"); + } qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); waylandServer()->backend()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(waylandServer()->backend(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); // this test needs to enforce OpenGL compositing to get into the crashy condition qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DontCrashEmptyDecorationTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); QSignalSpy decorationSpy(®istry, &Registry::serverSideDecorationManagerAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); QVERIFY(seatSpy.isValid()); QVERIFY(decorationSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); QVERIFY(!seatSpy.isEmpty()); QVERIFY(!decorationSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QVERIFY(m_seat->isValid()); m_deco = registry.createServerSideDecorationManager(decorationSpy.first().first().value(), decorationSpy.first().last().value()); QVERIFY(m_deco->isValid()); QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerSpy.isValid()); QVERIFY(hasPointerSpy.wait()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DontCrashEmptyDecorationTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_deco; m_deco = nullptr; delete m_seat; m_seat = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } void DontCrashEmptyDecorationTest::testBug361551() { // this test verifies that resizing an X11 window to an invalid size does not result in crash on unmap // when the DecorationRenderer gets copied to the Deleted // there a repaint is scheduled and the resulting texture is invalid if the window size is invalid // create an xcb window xcb_connection_t *c = xcb_connect(nullptr, nullptr); QVERIFY(!xcb_connection_has_error(c)); xcb_window_t w = xcb_generate_id(c); xcb_create_window(c, XCB_COPY_FROM_PARENT, w, rootWindow(), 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_map_window(c, w); xcb_flush(c); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(client->isDecorated()); // let's set a stupid geometry client->setGeometry(0, 0, 0, 0); QCOMPARE(client->geometry(), QRect(0, 0, 0, 0)); // and destroy the window again xcb_unmap_window(c, w); xcb_destroy_window(c, w); xcb_flush(c); xcb_disconnect(c); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::DontCrashEmptyDecorationTest) #include "dont_crash_empty_deco.moc" diff --git a/wayland_server.cpp b/wayland_server.cpp index 8d205c591..939cf0ab4 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -1,517 +1,519 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "wayland_server.h" #include "client.h" #include "abstract_backend.h" #include "composite.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" // Client #include #include #include #include #include // Server #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include // system #include #include //screenlocker #include using namespace KWayland::Server; namespace KWin { KWIN_SINGLETON_FACTORY(WaylandServer) WaylandServer::WaylandServer(QObject *parent) : QObject(parent) { qRegisterMetaType("KWayland::Server::SurfaceInterface *"); qRegisterMetaType(); } WaylandServer::~WaylandServer() { destroyInputMethodConnection(); } void WaylandServer::destroyInternalConnection() { emit terminatingInternalClientConnection(); if (m_internalConnection.client) { delete m_internalConnection.registry; delete m_internalConnection.shm; dispatch(); m_internalConnection.client->deleteLater(); m_internalConnection.clientThread->quit(); m_internalConnection.clientThread->wait(); delete m_internalConnection.clientThread; m_internalConnection.client = nullptr; m_internalConnection.server->destroy(); } } void WaylandServer::terminateClientConnections() { destroyInternalConnection(); destroyInputMethodConnection(); - const auto connections = m_display->connections(); - for (auto it = connections.begin(); it != connections.end(); ++it) { - (*it)->destroy(); + if (m_display) { + const auto connections = m_display->connections(); + for (auto it = connections.begin(); it != connections.end(); ++it) { + (*it)->destroy(); + } } } void WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) { m_initFlags = flags; m_display = new KWayland::Server::Display(this); if (!socketName.isNull() && !socketName.isEmpty()) { m_display->setSocketName(QString::fromUtf8(socketName)); } m_display->start(); m_compositor = m_display->createCompositor(m_display); m_compositor->create(); connect(m_compositor, &CompositorInterface::surfaceCreated, this, [this] (SurfaceInterface *surface) { // check whether we have a Toplevel with the Surface's id Workspace *ws = Workspace::self(); if (!ws) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() != xWaylandConnection()) { // setting surface is only relevat for Xwayland clients return; } auto check = [surface] (const Toplevel *t) { return t->surfaceId() == surface->id(); }; if (Toplevel *t = ws->findToplevel(check)) { t->setSurface(surface); } } ); m_shell = m_display->createShell(m_display); m_shell->create(); connect(m_shell, &ShellInterface::surfaceCreated, this, [this] (ShellSurfaceInterface *surface) { if (!Workspace::self()) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() == m_xwayland.client) { // skip Xwayland clients, those are created using standard X11 way return; } if (surface->client() == m_screenLockerClientConnection) { ScreenLocker::KSldApp::self()->lockScreenShown(); } auto client = new ShellClient(surface); if (auto c = Compositor::self()) { connect(client, &Toplevel::needsRepaint, c, &Compositor::scheduleRepaint); } if (client->isInternal()) { m_internalClients << client; } else { m_clients << client; } if (client->readyForPainting()) { emit shellClientAdded(client); } else { connect(client, &ShellClient::windowShown, this, [this, client] { emit shellClientAdded(client); } ); } } ); m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create(); m_display->createDataDeviceManager(m_display)->create(); m_display->createIdle(m_display)->create(); m_plasmaShell = m_display->createPlasmaShell(m_display); m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated, [this] (PlasmaShellSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installPlasmaShellSurface(surface); } } ); m_qtExtendedSurface = m_display->createQtSurfaceExtension(m_display); m_qtExtendedSurface->create(); connect(m_qtExtendedSurface, &QtSurfaceExtensionInterface::surfaceCreated, [this] (QtExtendedSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installQtExtendedSurface(surface); } } ); m_windowManagement = m_display->createPlasmaWindowManagement(m_display); m_windowManagement->create(); m_windowManagement->setShowingDesktopState(PlasmaWindowManagementInterface::ShowingDesktopState::Disabled); connect(m_windowManagement, &PlasmaWindowManagementInterface::requestChangeShowingDesktop, this, [] (PlasmaWindowManagementInterface::ShowingDesktopState state) { if (!workspace()) { return; } bool set = false; switch (state) { case PlasmaWindowManagementInterface::ShowingDesktopState::Disabled: set = false; break; case PlasmaWindowManagementInterface::ShowingDesktopState::Enabled: set = true; break; default: Q_UNREACHABLE(); break; } if (set == workspace()->showingDesktop()) { return; } workspace()->setShowingDesktop(set); } ); auto shadowManager = m_display->createShadowManager(m_display); shadowManager->create(); m_display->createDpmsManager(m_display)->create(); m_decorationManager = m_display->createServerSideDecorationManager(m_display); connect(m_decorationManager, &ServerSideDecorationManagerInterface::decorationCreated, this, [this] (ServerSideDecorationInterface *deco) { if (ShellClient *c = findClient(deco->surface())) { c->installServerSideDecoration(deco); } } ); m_decorationManager->create(); } void WaylandServer::initWorkspace() { if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { using namespace KWayland::Server; m_windowManagement->setShowingDesktopState(set ? PlasmaWindowManagementInterface::ShowingDesktopState::Enabled : PlasmaWindowManagementInterface::ShowingDesktopState::Disabled ); } ); } ScreenLocker::KSldApp::self(); ScreenLocker::KSldApp::self()->setWaylandDisplay(m_display); ScreenLocker::KSldApp::self()->setGreeterEnvironment(kwinApp()->processStartupEnvironment()); ScreenLocker::KSldApp::self()->initialize(); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::greeterClientConnectionChanged, this, [this] () { m_screenLockerClientConnection = ScreenLocker::KSldApp::self()->greeterClientConnection(); } ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::unlocked, this, [this] () { m_screenLockerClientConnection = nullptr; } ); if (m_initFlags.testFlag(InitalizationFlag::LockScreen)) { ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); } } void WaylandServer::initOutputs() { if (m_backend && m_backend->handlesOutputs()) { return; } Screens *s = screens(); Q_ASSERT(s); for (int i = 0; i < s->count(); ++i) { OutputInterface *output = m_display->createOutput(m_display); const QRect &geo = s->geometry(i); output->setGlobalPosition(geo.topLeft()); output->setPhysicalSize(geo.size() / 3.8); output->addMode(geo.size()); output->create(); } } int WaylandServer::createXWaylandConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return -1; } m_xwayland.client = m_display->createClient(sx[0]); m_xwayland.destroyConnection = connect(m_xwayland.client, &KWayland::Server::ClientConnection::disconnected, this, [] { qFatal("Xwayland Connection died"); } ); return sx[1]; } void WaylandServer::destroyXWaylandConnection() { if (!m_xwayland.client) { return; } disconnect(m_xwayland.destroyConnection); m_xwayland.client->destroy(); m_xwayland.client = nullptr; } int WaylandServer::createInputMethodConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return -1; } m_inputMethodServerConnection = m_display->createClient(sx[0]); return sx[1]; } void WaylandServer::destroyInputMethodConnection() { if (!m_inputMethodServerConnection) { return; } m_inputMethodServerConnection->destroy(); m_inputMethodServerConnection = nullptr; } void WaylandServer::createInternalConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return; } m_internalConnection.server = m_display->createClient(sx[0]); using namespace KWayland::Client; m_internalConnection.client = new ConnectionThread(); m_internalConnection.client->setSocketFd(sx[1]); m_internalConnection.clientThread = new QThread; m_internalConnection.client->moveToThread(m_internalConnection.clientThread); m_internalConnection.clientThread->start(); connect(m_internalConnection.client, &ConnectionThread::connected, this, [this] { Registry *registry = new Registry(this); EventQueue *eventQueue = new EventQueue(this); eventQueue->setup(m_internalConnection.client); registry->setEventQueue(eventQueue); registry->create(m_internalConnection.client); m_internalConnection.registry = registry; connect(registry, &Registry::shmAnnounced, this, [this] (quint32 name, quint32 version) { m_internalConnection.shm = m_internalConnection.registry->createShmPool(name, version, this); } ); registry->setup(); } ); m_internalConnection.client->initConnection(); } void WaylandServer::installBackend(AbstractBackend *backend) { Q_ASSERT(!m_backend); m_backend = backend; } void WaylandServer::uninstallBackend(AbstractBackend *backend) { Q_ASSERT(m_backend == backend); m_backend = nullptr; } void WaylandServer::removeClient(ShellClient *c) { m_clients.removeAll(c); m_internalClients.removeAll(c); emit shellClientRemoved(c); } void WaylandServer::dispatch() { if (!m_display) { return; } if (m_internalConnection.server) { m_internalConnection.server->flush(); } m_display->dispatchEvents(0); } static ShellClient *findClientInList(const QList &clients, quint32 id) { auto it = std::find_if(clients.begin(), clients.end(), [id] (ShellClient *c) { return c->windowId() == id; } ); if (it == clients.end()) { return nullptr; } return *it; } static ShellClient *findClientInList(const QList &clients, KWayland::Server::SurfaceInterface *surface) { auto it = std::find_if(clients.begin(), clients.end(), [surface] (ShellClient *c) { return c->surface() == surface; } ); if (it == clients.end()) { return nullptr; } return *it; } ShellClient *WaylandServer::findClient(quint32 id) const { if (id == 0) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, id)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, id)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(SurfaceInterface *surface) const { if (!surface) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, surface)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, surface)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(QWindow *w) const { if (!w) { return nullptr; } auto it = std::find_if(m_internalClients.constBegin(), m_internalClients.constEnd(), [w] (const ShellClient *c) { return c->internalWindow() == w; } ); if (it != m_internalClients.constEnd()) { return *it; } return nullptr; } quint32 WaylandServer::createWindowId(SurfaceInterface *surface) { auto it = m_clientIds.constFind(surface->client()); quint16 clientId = 0; if (it != m_clientIds.constEnd()) { clientId = it.value(); } else { clientId = createClientId(surface->client()); } Q_ASSERT(clientId != 0); quint32 id = clientId; // TODO: this does not prevent that two surfaces of same client get same id id = (id << 16) | (surface->id() & 0xFFFF); if (findClient(id)) { qCWarning(KWIN_CORE) << "Invalid client windowId generated:" << id; return 0; } return id; } quint16 WaylandServer::createClientId(ClientConnection *c) { auto ids = m_clientIds.values().toSet(); quint16 id = 1; if (!ids.isEmpty()) { for (quint16 i = ids.count() + 1; i >= 1 ; i--) { if (!ids.contains(i)) { id = i; break; } } } Q_ASSERT(!ids.contains(id)); m_clientIds.insert(c, id); connect(c, &ClientConnection::disconnected, this, [this] (ClientConnection *c) { m_clientIds.remove(c); } ); return id; } bool WaylandServer::isScreenLocked() const { return ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked || ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::AcquiringLock; } }