diff --git a/autotests/wayland/CMakeLists.txt b/autotests/wayland/CMakeLists.txt index b460b216a9..8519bf893b 100644 --- a/autotests/wayland/CMakeLists.txt +++ b/autotests/wayland/CMakeLists.txt @@ -147,3 +147,12 @@ if (XCB_ICCCM_FOUND) add_test(kwin-testStruts testStruts) ecm_mark_as_test(testStruts) endif() + +######################################################## +# Dont Crash Empty Deco Test +######################################################## +set( testDontCrashEmptyDeco_SRCS dont_crash_empty_deco.cpp kwin_wayland_test.cpp ) +add_executable(testDontCrashEmptyDeco ${testDontCrashEmptyDeco_SRCS}) +target_link_libraries( testDontCrashEmptyDeco kwin Qt5::Test) +add_test(kwin-testDontCrashEmptyDeco testDontCrashEmptyDeco) +ecm_mark_as_test(testDontCrashEmptyDeco) diff --git a/autotests/wayland/dont_crash_empty_deco.cpp b/autotests/wayland/dont_crash_empty_deco.cpp new file mode 100644 index 0000000000..8ca2f2ed90 --- /dev/null +++ b/autotests/wayland/dont_crash_empty_deco.cpp @@ -0,0 +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 "platform.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()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QMetaObject::invokeMethod(kwinApp()->platform(), "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/autotests/wayland/kwin_wayland_test.cpp b/autotests/wayland/kwin_wayland_test.cpp index 4001abb6ea..d37015b27e 100644 --- a/autotests/wayland/kwin_wayland_test.cpp +++ b/autotests/wayland/kwin_wayland_test.cpp @@ -19,6 +19,7 @@ along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "../../platform.h" +#include "../../composite.h" #include "../../effects.h" #include "../../wayland_server.h" #include "../../workspace.h" @@ -112,8 +113,7 @@ void WaylandTestApplication::continueStartupWithScreens() waylandServer()->initOutputs(); createCompositor(); - - startXwaylandServer(); + connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer); } void WaylandTestApplication::continueStartupWithX() @@ -190,6 +190,7 @@ void WaylandTestApplication::createX11Connection() void WaylandTestApplication::startXwaylandServer() { + disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer); int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; diff --git a/composite.cpp b/composite.cpp index 1b4cfc9a5c..b50da1f9bf 100644 --- a/composite.cpp +++ b/composite.cpp @@ -275,6 +275,7 @@ void Compositor::slotCompositingOptionsInitialized() } return; } + emit sceneCreated(); if (Workspace::self()) { startupWithWorkspace(); diff --git a/composite.h b/composite.h index fc0ddd5288..50134cb18f 100644 --- a/composite.h +++ b/composite.h @@ -186,6 +186,7 @@ public Q_SLOTS: Q_SIGNALS: void compositingToggled(bool active); void aboutToDestroy(); + void sceneCreated(); protected: void timerEvent(QTimerEvent *te); diff --git a/effects/morphingpopups/package/metadata.desktop b/effects/morphingpopups/package/metadata.desktop index 252ce397aa..7c96bfe8c1 100644 --- a/effects/morphingpopups/package/metadata.desktop +++ b/effects/morphingpopups/package/metadata.desktop @@ -23,6 +23,7 @@ Comment[sv]=Övertona animering när verktygstips eller underrättelser ändrar Comment[uk]=Анімація зі зміною освітленості під час зміни геометрії панелей підказок і сповіщень Comment[x-test]=xxCross fade animation when Tooltips or Notifications change their geometryxx Comment[zh_CN]=在工具提示或者通知大小变化时交叉淡入淡出的动画 +Comment[zh_TW]=當工具提示或通知變更位置時交錯的淡出動畫 Encoding=UTF-8 Icon=preferences-system-windows-effect-morphingpopups Name=Morphing popups @@ -49,6 +50,7 @@ Name[sv]=Föränderliga meddelanderutor Name[uk]=Аморфні контекстні панелі Name[x-test]=xxMorphing popupsxx Name[zh_CN]=变形气泡通知 +Name[zh_TW]=交錯彈出視窗 Type=Service X-KDE-ParentApp= X-KDE-PluginInfo-Author=Marco Martin diff --git a/main_wayland.cpp b/main_wayland.cpp index f15d30ee3e..a9ece4de5d 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "main_wayland.h" +#include "composite.h" #include "workspace.h" #include // kwin @@ -151,8 +152,7 @@ void ApplicationWayland::continueStartupWithScreens() return; } createCompositor(); - - startXwaylandServer(); + connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer); } void ApplicationWayland::continueStartupWithX() @@ -279,6 +279,7 @@ void ApplicationWayland::createX11Connection() void ApplicationWayland::startXwaylandServer() { + disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer); int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; diff --git a/scene_opengl.cpp b/scene_opengl.cpp index 88c9d50f4c..195575bbf6 100644 --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -2501,6 +2501,11 @@ void SceneOpenGLDecorationRenderer::render() resetImageSizesDirty(); } + if (!m_texture) { + // for invalid sizes we get no texture, see BUG 361551 + return; + } + QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); diff --git a/wayland_server.cpp b/wayland_server.cpp index 3798c1de0f..cac929c537 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -100,9 +100,11 @@ 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(); + } } }