diff --git a/autotests/wayland/CMakeLists.txt b/autotests/wayland/CMakeLists.txt index 982e8dcf1b..a77ee7a67c 100644 --- a/autotests/wayland/CMakeLists.txt +++ b/autotests/wayland/CMakeLists.txt @@ -134,3 +134,12 @@ add_executable(testDebugConsole ${testDebugConsole_SRCS}) target_link_libraries( testDebugConsole kwin Qt5::Test) add_test(kwin-testDebugConsole testDebugConsole) ecm_mark_as_test(testDebugConsole) + +######################################################## +# Struts Test +######################################################## +set( testStruts_SRCS struts_test.cpp kwin_wayland_test.cpp ) +add_executable(testStruts ${testStruts_SRCS}) +target_link_libraries( testStruts kwin Qt5::Test XCB::ICCCM) +add_test(kwin-testStruts testStruts) +ecm_mark_as_test(testStruts) diff --git a/autotests/wayland/struts_test.cpp b/autotests/wayland/struts_test.cpp new file mode 100644 index 0000000000..8fabfe5129 --- /dev/null +++ b/autotests/wayland/struts_test.cpp @@ -0,0 +1,499 @@ +/******************************************************************** +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 +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_struts-0"); + +class StrutsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testX11Struts_data(); + void testX11Struts(); + +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 StrutsTest::initTestCase() +{ + 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()); + + 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 StrutsTest::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 StrutsTest::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 StrutsTest::testX11Struts_data() +{ + QTest::addColumn("windowGeometry"); + QTest::addColumn("leftStrut"); + QTest::addColumn("rightStrut"); + QTest::addColumn("topStrut"); + QTest::addColumn("bottomStrut"); + QTest::addColumn("leftStrutStart"); + QTest::addColumn("leftStrutEnd"); + QTest::addColumn("rightStrutStart"); + QTest::addColumn("rightStrutEnd"); + QTest::addColumn("topStrutStart"); + QTest::addColumn("topStrutEnd"); + QTest::addColumn("bottomStrutStart"); + QTest::addColumn("bottomStrutEnd"); + QTest::addColumn("screen0Maximized"); + QTest::addColumn("screen1Maximized"); + QTest::addColumn("workArea"); + + QTest::newRow("bottom panel/no strut") << QRect(0, 980, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("bottom panel/strut") << QRect(0, 980, 1280, 44) + << 0 << 0 << 0 << 44 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << QRect(0, 0, 1280, 980) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 980); + QTest::newRow("top panel/no strut") << QRect(0, 0, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("top panel/strut") << QRect(0, 0, 1280, 44) + << 0 << 0 << 44 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << 0 << 0 + << QRect(0, 44, 1280, 980) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 44, 2560, 980); + QTest::newRow("left panel/no strut") << QRect(0, 0, 60, 1024) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("left panel/strut") << QRect(0, 0, 60, 1024) + << 60 << 0 << 0 << 0 + << 0 << 1023 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(60, 0, 1220, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(60, 0, 2500, 1024); + QTest::newRow("right panel/no strut") << QRect(0, 1220, 60, 1024) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("right panel/strut") << QRect(0, 1220, 60, 1024) + << 0 << 1340 << 0 << 0 + << 0 << 0 + << 0 << 1023 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, /*1220*/1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + // second screen + QTest::newRow("bottom panel 1/no strut") << QRect(1280, 980, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("bottom panel 1/strut") << QRect(1280, 980, 1280, 44) + << 0 << 0 << 0 << 44 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 1280 << 2559 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 980) + << QRect(0, 0, 2560, 980); + QTest::newRow("top panel 1/no strut") << QRect(1280, 0, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("top panel 1 /strut") << QRect(1280, 0, 1280, 44) + << 0 << 0 << 44 << 0 + << 0 << 0 + << 0 << 0 + << 1280 << 2559 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 44, 1280, 980) + << QRect(0, 44, 2560, 980); + QTest::newRow("left panel 1/no strut") << QRect(1280, 0, 60, 1024) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("left panel 1/strut") << QRect(1280, 0, 60, 1024) + << 1340 << 0 << 0 << 0 + << 0 << 1023 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024)//QRect(1340, 0, 1220, 1024) + << QRect(0, 0, 2560, 1024); + // invalid struts + QTest::newRow("bottom panel/ invalid strut") << QRect(0, 980, 1280, 44) + << 1280 << 0 << 0 << 44 + << 980 << 1024 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("top panel/ invalid strut") << QRect(0, 0, 1280, 44) + << 1280 << 0 << 44 << 0 + << 0 << 44 + << 0 << 0 + << 0 << 1279 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); + QTest::newRow("top panel/invalid strut 2") << QRect(0, 0, 1280, 44) + << 0 << 0 << 1024 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << 0 << 0 + << QRect(0, 0, 1280, 1024) + << QRect(1280, 0, 1280, 1024) + << QRect(0, 0, 2560, 1024); +} + +void StrutsTest::testX11Struts() +{ + // this test verifies that struts are applied correctly for X11 windows + + // no, struts yet + // first screen + QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); + // second screen + QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); + + // create an xcb window + struct XcbConnectionDeleter + { + static inline void cleanup(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } + }; + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + + xcb_window_t w = xcb_generate_id(c.data()); + QFETCH(QRect, windowGeometry); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); + NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Dock); + // set the extended strut + QFETCH(int, leftStrut); + QFETCH(int, rightStrut); + QFETCH(int, topStrut); + QFETCH(int, bottomStrut); + QFETCH(int, leftStrutStart); + QFETCH(int, leftStrutEnd); + QFETCH(int, rightStrutStart); + QFETCH(int, rightStrutEnd); + QFETCH(int, topStrutStart); + QFETCH(int, topStrutEnd); + QFETCH(int, bottomStrutStart); + QFETCH(int, bottomStrutEnd); + NETExtendedStrut strut; + strut.left_start = leftStrutStart; + strut.left_end = leftStrutEnd; + strut.left_width = leftStrut; + strut.right_start = rightStrutStart; + strut.right_end = rightStrutEnd; + strut.right_width = rightStrut; + strut.top_start = topStrutStart; + strut.top_end = topStrutEnd; + strut.top_width = topStrut; + strut.bottom_start = bottomStrutStart; + strut.bottom_end = bottomStrutEnd; + strut.bottom_width = bottomStrut; + info.setExtendedStrut(strut); + xcb_map_window(c.data(), w); + xcb_flush(c.data()); + + // 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()); + QCOMPARE(client->windowType(), NET::Dock); + QCOMPARE(client->geometry(), windowGeometry); + + // this should have affected the client area + // some props are independent of struts - those first + // screen 0 + QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); + // screen 1 + QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); + + // now verify the actual updated client areas + QTEST(workspace()->clientArea(PlacementArea, 0, 1), "screen0Maximized"); + QTEST(workspace()->clientArea(MaximizeArea, 0, 1), "screen0Maximized"); + QTEST(workspace()->clientArea(PlacementArea, 1, 1), "screen1Maximized"); + QTEST(workspace()->clientArea(MaximizeArea, 1, 1), "screen1Maximized"); + QTEST(workspace()->clientArea(WorkArea, 0, 1), "workArea"); + + // and destroy the window again + xcb_unmap_window(c.data(), w); + xcb_destroy_window(c.data(), w); + xcb_flush(c.data()); + c.reset(); + + QSignalSpy windowClosedSpy(client, &Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + QVERIFY(windowClosedSpy.wait()); + + // now struts should be removed again + QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); + // second screen + QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); +} + +} + +WAYLANDTEST_MAIN(KWin::StrutsTest) +#include "struts_test.moc" diff --git a/client.h b/client.h index 697b6418bf..7a28b547ba 100644 --- a/client.h +++ b/client.h @@ -61,7 +61,7 @@ enum class Predicate { InputIdMatch }; -class Client +class KWIN_EXPORT Client : public AbstractClient { Q_OBJECT diff --git a/geometry.cpp b/geometry.cpp index 730b14adb2..0eb289940a 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -926,6 +926,17 @@ QRect Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) co // qDebug() << "Moving bottom of: " << r << " to " << stareaB.top() - 1; r . setBottom(stareaB . top() - 1); } + + // sanity check that a strut doesn't exclude a complete screen geometry + // this is a violation to EWMH, as KWin just ignores the strut + for (int i = 0; i < screens()->count(); i++) { + const QRect screenGeo = screens()->geometry(i); + if (!r.intersects(screenGeo)) { + qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore"; + return area; + } + } + return r; }