diff --git a/autotests/integration/dont_crash_glxgears.cpp b/autotests/integration/dont_crash_glxgears.cpp index a3943244b..998a68ccb 100644 --- a/autotests/integration/dont_crash_glxgears.cpp +++ b/autotests/integration/dont_crash_glxgears.cpp @@ -1,104 +1,105 @@ /******************************************************************** 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 "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "client.h" #include "deleted.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_glxgears-0"); class DontCrashGlxgearsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testGlxgears(); }; void DontCrashGlxgearsTest::initTestCase() { qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void DontCrashGlxgearsTest::testGlxgears() { // closing a glxgears window through Aurorae themes used to crash KWin // Let's make sure that doesn't happen anymore QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded); QVERIFY(clientAddedSpy.isValid()); QProcess glxgears; glxgears.start(QStringLiteral("glxgears")); QVERIFY(glxgears.waitForStarted()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(workspace()->clientList().count(), 1); Client *glxgearsClient = workspace()->clientList().first(); QVERIFY(glxgearsClient->isDecorated()); QSignalSpy closedSpy(glxgearsClient, &Client::windowClosed); QVERIFY(closedSpy.isValid()); KDecoration2::Decoration *decoration = glxgearsClient->decoration(); QVERIFY(decoration); // send a mouse event to the position of the close button - QPointF pos = decoration->rect().topRight() + QPointF(-decoration->borderRight() * 2, decoration->borderRight() * 2); + // TODO: position is dependent on the decoration in use. We should use a static target instead, a fake deco for autotests. + QPointF pos = decoration->rect().topRight() + QPointF(-decoration->borderTop() / 2, decoration->borderTop() / 2); QHoverEvent event(QEvent::HoverMove, pos, pos); QCoreApplication::instance()->sendEvent(decoration, &event); // mouse press QMouseEvent mousePressevent(QEvent::MouseButtonPress, pos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); mousePressevent.setAccepted(false); QCoreApplication::sendEvent(decoration, &mousePressevent); QVERIFY(mousePressevent.isAccepted()); // mouse Release QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, pos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); mouseReleaseEvent.setAccepted(false); QCoreApplication::sendEvent(decoration, &mouseReleaseEvent); QVERIFY(mouseReleaseEvent.isAccepted()); QVERIFY(closedSpy.wait()); QCOMPARE(closedSpy.count(), 1); xcb_flush(connection()); if (glxgears.state() == QProcess::Running) { QVERIFY(glxgears.waitForFinished()); } } } WAYLANDTEST_MAIN(KWin::DontCrashGlxgearsTest) #include "dont_crash_glxgears.moc" diff --git a/autotests/integration/maximize_test.cpp b/autotests/integration/maximize_test.cpp index 5abe5928d..d843332b7 100644 --- a/autotests/integration/maximize_test.cpp +++ b/autotests/integration/maximize_test.cpp @@ -1,336 +1,346 @@ /******************************************************************** 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 "cursor.h" +#include "decorations/decorationbridge.h" +#include "decorations/settings.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include -#include #include +#include +#include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_maximized-0"); class TestMaximized : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMaximizedPassedToDeco(); void testInitiallyMaximized(); void testBorderlessMaximizedWindow(); void testBorderlessMaximizedWindowNoClientSideDecoration(); void testMaximizePlacementStrategy(); }; void TestMaximized::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); 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)); waylandServer()->initWorkspace(); } void TestMaximized::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecoration | Test::AdditionalWaylandInterface::PlasmaShell)); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestMaximized::cleanup() { Test::destroyWaylandConnection(); // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", false); group.sync(); Workspace::self()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), false); } void TestMaximized::testMaximizedPassedToDeco() { // this test verifies that when a ShellClient gets maximized the Decoration receives the signal QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer ssd(Test::waylandServerSideDecoration()->create(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); QVERIFY(client); QVERIFY(client->isDecorated()); auto decoration = client->decoration(); QVERIFY(decoration); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + // When there are no borders, there is no change to them when maximizing. + // TODO: we should test both cases with fixed fake decoration for autotests. + const bool hasBorders = Decoration::DecorationBridge::self()->settings()->borderSize() != KDecoration2::BorderSize::None; + // now maximize QVERIFY(sizeChangedSpy.isEmpty()); QSignalSpy bordersChangedSpy(decoration, &KDecoration2::Decoration::bordersChanged); QVERIFY(bordersChangedSpy.isValid()); QSignalSpy maximizedChangedSpy(decoration->client().data(), &KDecoration2::DecoratedClient::maximizedChanged); QVERIFY(maximizedChangedSpy.isValid()); QSignalSpy geometryShapeChangedSpy(client, &AbstractClient::geometryShapeChanged); QVERIFY(geometryShapeChangedSpy.isValid()); workspace()->slotWindowMaximize(); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.first().first().toSize(), QSize(1280, 1024 - decoration->borderTop())); Test::render(surface.data(), sizeChangedSpy.first().first().toSize(), Qt::red); QVERIFY(geometryShapeChangedSpy.wait()); - QCOMPARE(geometryShapeChangedSpy.count(), 2); + + // If no borders, there is only the initial geometry shape change, but none through border resizing. + QCOMPARE(geometryShapeChangedSpy.count(), hasBorders ? 2 : 1); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(maximizedChangedSpy.count(), 1); QCOMPARE(maximizedChangedSpy.last().first().toBool(), true); - QCOMPARE(bordersChangedSpy.count(), 1); + QCOMPARE(bordersChangedSpy.count(), hasBorders ? 1 : 0); QCOMPARE(decoration->borderLeft(), 0); QCOMPARE(decoration->borderBottom(), 0); QCOMPARE(decoration->borderRight(), 0); QVERIFY(decoration->borderTop() != 0); // now unmaximize again workspace()->slotWindowMaximize(); Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(geometryShapeChangedSpy.wait()); - QCOMPARE(geometryShapeChangedSpy.count(), 4); + QCOMPARE(geometryShapeChangedSpy.count(), hasBorders ? 4 : 2); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(maximizedChangedSpy.count(), 2); QCOMPARE(maximizedChangedSpy.last().first().toBool(), false); - QCOMPARE(bordersChangedSpy.count(), 2); + QCOMPARE(bordersChangedSpy.count(), hasBorders ? 2 : 0); QVERIFY(decoration->borderTop() != 0); - QVERIFY(decoration->borderLeft() != 0); - QVERIFY(decoration->borderRight() != 0); - QVERIFY(decoration->borderBottom() != 0); + QVERIFY(decoration->borderLeft() != !hasBorders); + QVERIFY(decoration->borderRight() != !hasBorders); + QVERIFY(decoration->borderBottom() != !hasBorders); QCOMPARE(sizeChangedSpy.count(), 2); QCOMPARE(sizeChangedSpy.last().first().toSize(), QSize(100, 50)); } void TestMaximized::testInitiallyMaximized() { // this test verifies that a window created as maximized, will be maximized QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); shellSurface->setMaximized(); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(shellSurface->size(), QSize(1280, 1024)); // now let's render in an incorrect size auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QCOMPARE(client->geometry(), QRect(0, 0, 100, 50)); QEXPECT_FAIL("", "Should go out of maximzied", Continue); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); } void TestMaximized::testBorderlessMaximizedWindow() { // test case verifies that borderless maximized window works // see BUG 370982 // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", true); group.sync(); Workspace::self()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), true); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer ssd(Test::waylandServerSideDecoration()->create(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client->isDecorated()); const QRect origGeo = client->geometry(); QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); // go to maximized shellSurface->setMaximized(); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(shellSurface->size(), QSize(1280, 1024)); QSignalSpy geometryChangedSpy(client, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); Test::render(surface.data(), shellSurface->size(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(client->geometryRestore(), origGeo); QCOMPARE(client->isDecorated(), false); // go back to normal shellSurface->setToplevel(); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(shellSurface->size(), QSize(100, 50)); Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->geometry(), origGeo); QCOMPARE(client->geometryRestore(), origGeo); QCOMPARE(client->isDecorated(), true); } void TestMaximized::testBorderlessMaximizedWindowNoClientSideDecoration() { // test case verifies that borderless maximized windows doesn't cause // clients to render client-side decorations instead (BUG 405385) // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", true); group.sync(); Workspace::self()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), true); QScopedPointer surface(Test::createSurface()); QScopedPointer xdgShellSurface(Test::createXdgShellStableSurface(surface.data())); QScopedPointer deco(Test::xdgDecorationManager()->getToplevelDecoration(xdgShellSurface.data())); QSignalSpy decorationConfiguredSpy(deco.data(), &XdgDecoration::modeChanged); QVERIFY(decorationConfiguredSpy.isValid()); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QSignalSpy geometryChangedSpy(client, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(xdgShellSurface.data(), &XdgShellSurface::sizeChanged); QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(xdgShellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); QVERIFY(client->isDecorated()); QVERIFY(!client->noBorder()); configureRequestedSpy.wait(); QCOMPARE(decorationConfiguredSpy.count(), 1); QCOMPARE(deco->mode(), XdgDecoration::Mode::ServerSide); // go to maximized xdgShellSurface->setMaximized(true); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); // no deco QVERIFY(!client->isDecorated()); QVERIFY(client->noBorder()); // but still server-side QCOMPARE(deco->mode(), XdgDecoration::Mode::ServerSide); // go back to normal xdgShellSurface->setMaximized(false); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QVERIFY(client->isDecorated()); QVERIFY(!client->noBorder()); QCOMPARE(deco->mode(), XdgDecoration::Mode::ServerSide); } void TestMaximized::testMaximizePlacementStrategy() { // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("Placement", "Maximizing"); group.sync(); Workspace::self()->slotReconfigure(); // add a top panel QScopedPointer panelSurface(Test::createSurface()); QScopedPointer panelShellSurface(Test::createXdgShellStableSurface(panelSurface.data())); QScopedPointer plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.data())); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); plasmaSurface->setPosition(QPoint(0, 0)); Test::renderAndWaitForShown(panelSurface.data(), QSize(1280, 20), Qt::blue); // create a new window - it should be maximised on the first configure and positioned beneath the strut QScopedPointer surface(Test::createSurface()); auto shellSurface = Test::createXdgShellStableSurface(surface.data(), surface.data(), Test::CreationSetup::CreateOnly); QSignalSpy configSpy(shellSurface, &XdgShellSurface::configureRequested); surface->commit(Surface::CommitFlag::None); QVERIFY(configSpy.wait()); const auto size = configSpy[0][0].toSize(); const auto states = configSpy[0][1].value(); QVERIFY(states & XdgShellSurface::State::Maximized); shellSurface->ackConfigure(configSpy[0][2].toUInt()); QCOMPARE(size, QSize(1280, 1024 - 20)); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::red); QVERIFY(c); QCOMPARE(c->geometry(), QRect(0, 20, 1280, 1024 - 20)); } WAYLANDTEST_MAIN(TestMaximized) #include "maximize_test.moc" diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp index 45d14fe32..144a9f5c2 100644 --- a/autotests/integration/quick_tiling_test.cpp +++ b/autotests/integration/quick_tiling_test.cpp @@ -1,926 +1,935 @@ /******************************************************************** 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 "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "client.h" #include "cursor.h" +#include "decorations/decorationbridge.h" +#include "decorations/settings.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include "scripting/scripting.h" +#include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::QuickTileMode) Q_DECLARE_METATYPE(KWin::MaximizeMode) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); class QuickTilingTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testQuickTiling_data(); void testQuickTiling(); void testQuickMaximizing_data(); void testQuickMaximizing(); void testQuickTilingKeyboardMove_data(); void testQuickTilingKeyboardMove(); void testQuickTilingPointerMove_data(); void testQuickTilingPointerMove(); void testQuickTilingPointerMoveXdgShell_data(); void testQuickTilingPointerMoveXdgShell(); void testQuickTilingTouchMoveXdgShell_data(); void testQuickTilingTouchMoveXdgShell(); void testX11QuickTiling_data(); void testX11QuickTiling(); void testX11QuickTilingAfterVertMaximize_data(); void testX11QuickTilingAfterVertMaximize(); void testShortcut_data(); void testShortcut(); void testScript_data(); void testScript(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; void QuickTilingTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("MaximizeMode"); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // set custom config which disables the Outline KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup group = config->group("Outline"); group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); group.sync(); kwinApp()->setConfig(config); qputenv("XKB_DEFAULT_RULES", "evdev"); 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)); } void QuickTilingTest::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); m_connection = Test::waylandConnection(); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); screens()->setCurrent(0); } void QuickTilingTest::cleanup() { Test::destroyWaylandConnection(); } void QuickTilingTest::testQuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("secondScreen"); QTest::addColumn("expectedModeAfterToggle"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << QRect(1280, 0, 640, 1024) << FLAG(Right); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << QRect(1280, 0, 1280, 512) << FLAG(Top); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << QRect(1920, 0, 640, 1024) << QuickTileMode(); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << QRect(1280, 512, 1280, 512) << FLAG(Bottom); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << QRect(1280, 0, 640, 512) << (FLAG(Right) | FLAG(Top)); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << QRect(1920, 0, 640, 512) << QuickTileMode(); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << QRect(1280, 512, 640, 512) << (FLAG(Right) | FLAG(Bottom)); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << QRect(1920, 512, 640, 512) << QuickTileMode(); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QuickTileMode(); #undef FLAG } void QuickTilingTest::testQuickTiling() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(QuickTileMode, mode); QFETCH(QRect, expectedGeometry); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), mode); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); // send window to other screen QCOMPARE(c->screen(), 0); c->sendToScreen(1); QCOMPARE(c->screen(), 1); // quick tile should not be changed QCOMPARE(c->quickTileMode(), mode); QTEST(c->geometry(), "secondScreen"); // now try to toggle again c->setQuickTileMode(mode, true); QTEST(c->quickTileMode(), "expectedModeAfterToggle"); } void QuickTilingTest::testQuickMaximizing_data() { QTest::addColumn("mode"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("maximize") << FLAG(Maximize); QTest::newRow("none") << FLAG(None); #undef FLAG } void QuickTilingTest::testQuickMaximizing() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy maximizeChangedSpy1(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,MaximizeMode))); QVERIFY(maximizeChangedSpy1.isValid()); QSignalSpy maximizeChangedSpy2(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,bool,bool))); QVERIFY(maximizeChangedSpy2.isValid()); c->setQuickTileMode(QuickTileFlag::Maximize, true); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), QuickTileFlag::Maximize); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), QSize(1280, 1024)); // attach a new image Test::render(surface.data(), QSize(1280, 1024), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 2); QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // client is now set to maximised QCOMPARE(maximizeChangedSpy1.count(), 1); QCOMPARE(maximizeChangedSpy1.first().first().value(), c); QCOMPARE(maximizeChangedSpy1.first().last().value(), MaximizeFull); QCOMPARE(maximizeChangedSpy2.count(), 1); QCOMPARE(maximizeChangedSpy2.first().first().value(), c); QCOMPARE(maximizeChangedSpy2.first().at(1).toBool(), true); QCOMPARE(maximizeChangedSpy2.first().at(2).toBool(), true); QCOMPARE(c->maximizeMode(), MaximizeFull); // go back to quick tile none QFETCH(QuickTileMode, mode); c->setQuickTileMode(mode, true); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(quickTileChangedSpy.count(), 2); // geometry not yet changed QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 2); QCOMPARE(sizeChangeSpy.last().first().toSize(), QSize(100, 50)); // render again Test::render(surface.data(), QSize(100, 50), Qt::yellow); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 4); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); QCOMPARE(maximizeChangedSpy1.count(), 2); QCOMPARE(maximizeChangedSpy1.last().first().value(), c); QCOMPARE(maximizeChangedSpy1.last().last().value(), MaximizeRestore); QCOMPARE(maximizeChangedSpy2.count(), 2); QCOMPARE(maximizeChangedSpy2.last().first().value(), c); QCOMPARE(maximizeChangedSpy2.last().at(1).toBool(), false); QCOMPARE(maximizeChangedSpy2.last().at(2).toBool(), false); } void QuickTilingTest::testQuickTilingKeyboardMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingKeyboardMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->moveResizeClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); while (Cursor::pos().x() > targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFT, timestamp++); } while (Cursor::pos().x() < targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); } while (Cursor::pos().y() < targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_DOWN, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_DOWN, timestamp++); } while (Cursor::pos().y() > targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_UP, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_UP, timestamp++); } kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_ENTER, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ENTER, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->moveResizeClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } void QuickTilingTest::testQuickTilingPointerMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingPointerMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->moveResizeClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(targetPos, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->moveResizeClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); QTRY_COMPARE(sizeChangeSpy.count(), 1); } void QuickTilingTest::testQuickTilingPointerMoveXdgShell_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingPointerMoveXdgShell() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createXdgShellV6Surface( surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); QVERIFY(!shellSurface.isNull()); // wait for the initial configure event QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); surface->commit(Surface::CommitFlag::None); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); // let's render shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); // we have to receive a configure event when the client becomes active QVERIFY(configureRequestedSpy.wait()); QTRY_COMPARE(configureRequestedSpy.count(), 2); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->moveResizeClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 3); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(targetPos, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->moveResizeClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 4); QCOMPARE(false, configureRequestedSpy.last().first().toSize().isEmpty()); } void QuickTilingTest::testQuickTilingTouchMoveXdgShell_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingTouchMoveXdgShell() { // test verifies that touch on decoration also allows quick tiling // see BUG: 390113 using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QScopedPointer shellSurface(Test::createXdgShellV6Surface( surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); QVERIFY(!shellSurface.isNull()); // wait for the initial configure event QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); surface->commit(Surface::CommitFlag::None); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); // let's render shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(1000, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isDecorated()); const auto decoration = c->decoration(); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(-decoration->borderLeft(), 0, 1000 + decoration->borderLeft() + decoration->borderRight(), 50 + decoration->borderTop() + decoration->borderBottom())); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); // we have to receive a configure event when the client becomes active QVERIFY(configureRequestedSpy.wait()); QTRY_COMPARE(configureRequestedSpy.count(), 2); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(0, QPointF(c->geometry().center().x(), c->geometry().y() + decoration->borderTop() / 2), timestamp++); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(c, workspace()->moveResizeClient()); QCOMPARE(configureRequestedSpy.count(), 3); QFETCH(QPoint, targetPos); kwinApp()->platform()->touchMotion(0, targetPos, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!workspace()->moveResizeClient()); + + // When there are no borders, there is no change to them when quick-tiling. + // TODO: we should test both cases with fixed fake decoration for autotests. + const bool hasBorders = Decoration::DecorationBridge::self()->settings()->borderSize() != KDecoration2::BorderSize::None; + QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); QVERIFY(configureRequestedSpy.wait()); - QTRY_COMPARE(configureRequestedSpy.count(), 5); + QTRY_COMPARE(configureRequestedSpy.count(), hasBorders ? 5 : 4); QCOMPARE(false, configureRequestedSpy.last().first().toSize().isEmpty()); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void QuickTilingTest::testX11QuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("screen"); QTest::addColumn("modeAfterToggle"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << 0 << QuickTileMode(); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << 1 << FLAG(Top); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << 1 << FLAG(Left); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << 1 << FLAG(Bottom); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << 0 << QuickTileMode(); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << 1 << (FLAG(Left) | FLAG(Top)); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << 0 << QuickTileMode(); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << 1 << (FLAG(Left) | FLAG(Bottom)); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << 0 << QuickTileMode(); #undef FLAG } void QuickTilingTest::testX11QuickTiling() { QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); 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); 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); // now quick tile QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); const QRect origGeo = client->geometry(); QFETCH(QuickTileMode, mode); client->setQuickTileMode(mode, true); QCOMPARE(client->quickTileMode(), mode); QTEST(client->geometry(), "expectedGeometry"); QCOMPARE(client->geometryRestore(), origGeo); QEXPECT_FAIL("maximize", "For maximize we get two changed signals", Continue); QCOMPARE(quickTileChangedSpy.count(), 1); // quick tile to same edge again should also act like send to screen QCOMPARE(client->screen(), 0); client->setQuickTileMode(mode, true); QTEST(client->screen(), "screen"); QTEST(client->quickTileMode(), "modeAfterToggle"); QCOMPARE(client->geometryRestore(), origGeo); // 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()); } void QuickTilingTest::testX11QuickTilingAfterVertMaximize_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024); #undef FLAG } void QuickTilingTest::testX11QuickTilingAfterVertMaximize() { QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); 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); 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); const QRect origGeo = client->geometry(); QCOMPARE(client->maximizeMode(), MaximizeRestore); // vertically maximize the window client->maximize(client->maximizeMode() ^ MaximizeVertical); QCOMPARE(client->geometry().width(), origGeo.width()); QCOMPARE(client->height(), screens()->size(client->screen()).height()); QCOMPARE(client->geometryRestore(), origGeo); // now quick tile QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QFETCH(QuickTileMode, mode); client->setQuickTileMode(mode, true); QCOMPARE(client->quickTileMode(), mode); QTEST(client->geometry(), "expectedGeometry"); QEXPECT_FAIL("", "We get two changed events", Continue); QCOMPARE(quickTileChangedSpy.count(), 1); // 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()); } void QuickTilingTest::testShortcut_data() { QTest::addColumn("shortcut"); QTest::addColumn("expectedMode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("top") << QStringLiteral("Window Quick Tile Top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("left") << QStringLiteral("Window Quick Tile Left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("bottom") << QStringLiteral("Window Quick Tile Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("right") << QStringLiteral("Window Quick Tile Right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("top right") << QStringLiteral("Window Quick Tile Top Right") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); QTest::newRow("top left") << QStringLiteral("Window Quick Tile Top Left") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); QTest::newRow("bottom right") << QStringLiteral("Window Quick Tile Bottom Right") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); QTest::newRow("bottom left") << QStringLiteral("Window Quick Tile Bottom Left") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); #undef FLAG } void QuickTilingTest::testShortcut() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(QString, shortcut); QFETCH(QuickTileMode, expectedMode); QFETCH(QRect, expectedGeometry); // invoke global shortcut through dbus auto msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/component/kwin"), QStringLiteral("org.kde.kglobalaccel.Component"), QStringLiteral("invokeShortcut")); msg.setArguments(QList{shortcut}); QDBusConnection::sessionBus().asyncCall(msg); QVERIFY(quickTileChangedSpy.wait()); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), expectedMode); // but we got requested a new geometry QTRY_COMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); } void QuickTilingTest::testScript_data() { QTest::addColumn("action"); QTest::addColumn("expectedMode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("top") << QStringLiteral("Top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("left") << QStringLiteral("Left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("bottom") << QStringLiteral("Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("right") << QStringLiteral("Right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("top right") << QStringLiteral("TopRight") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); QTest::newRow("top left") << QStringLiteral("TopLeft") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); QTest::newRow("bottom right") << QStringLiteral("BottomRight") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); QTest::newRow("bottom left") << QStringLiteral("BottomLeft") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); #undef FLAG } void QuickTilingTest::testScript() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QVERIFY(Scripting::self()); QTemporaryFile tmpFile; QVERIFY(tmpFile.open()); QTextStream out(&tmpFile); QFETCH(QString, action); out << "workspace.slotWindowQuickTile" << action << "()"; out.flush(); QFETCH(QuickTileMode, expectedMode); QFETCH(QRect, expectedGeometry); const int id = Scripting::self()->loadScript(tmpFile.fileName()); QVERIFY(id != -1); QVERIFY(Scripting::self()->isScriptLoaded(tmpFile.fileName())); auto s = Scripting::self()->findScript(tmpFile.fileName()); QVERIFY(s); QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); QVERIFY(runningChangedSpy.isValid()); s->run(); QVERIFY(quickTileChangedSpy.wait()); QCOMPARE(quickTileChangedSpy.count(), 1); QCOMPARE(runningChangedSpy.count(), 1); QCOMPARE(runningChangedSpy.first().first().toBool(), true); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), expectedMode); // but we got requested a new geometry QTRY_COMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); } } WAYLANDTEST_MAIN(KWin::QuickTilingTest) #include "quick_tiling_test.moc" diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index 67fbf31c0..5a10cda00 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -1,1504 +1,1518 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin Copyright (C) 2019 David Edmundson 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 "cursor.h" +#include "decorations/decorationbridge.h" +#include "decorations/settings.h" #include "effects.h" #include "deleted.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" -#include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + // system #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client-0"); class TestShellClient : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMapUnmapMap_data(); void testMapUnmapMap(); void testDesktopPresenceChanged(); void testTransientPositionAfterRemap(); void testWindowOutputs_data(); void testWindowOutputs(); void testMinimizeActiveWindow_data(); void testMinimizeActiveWindow(); void testFullscreen_data(); void testFullscreen(); + void testFullscreenRestore_data(); void testFullscreenRestore(); void testUserCanSetFullscreen_data(); void testUserCanSetFullscreen(); void testUserSetFullscreenWlShell(); void testUserSetFullscreenXdgShell_data(); void testUserSetFullscreenXdgShell(); + void testMaximizedToFullscreenWlShell_data(); void testMaximizedToFullscreenWlShell(); void testMaximizedToFullscreenXdgShell_data(); void testMaximizedToFullscreenXdgShell(); void testWindowOpensLargerThanScreen_data(); void testWindowOpensLargerThanScreen(); void testHidden_data(); void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); void testCaptionMultipleWindows(); void testUnresponsiveWindow_data(); void testUnresponsiveWindow(); void testX11WindowId_data(); void testX11WindowId(); void testAppMenu(); void testNoDecorationModeRequested_data(); void testNoDecorationModeRequested(); void testSendClientWithTransientToDesktop_data(); void testSendClientWithTransientToDesktop(); void testMinimizeWindowWithTransients_data(); void testMinimizeWindowWithTransients(); void testXdgDecoration_data(); void testXdgDecoration(); void testXdgNeverCommitted(); void testXdgInitialState(); void testXdgInitiallyMaximised(); void testXdgInitiallyMinimized(); void testXdgWindowGeometry(); }; void TestShellClient::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(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)); waylandServer()->initWorkspace(); } void TestShellClient::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecoration | Test::AdditionalWaylandInterface::AppMenu)); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestShellClient::cleanup() { Test::destroyWaylandConnection(); } void TestShellClient::testMapUnmapMap_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testMapUnmapMap() { // this test verifies that mapping a previously mapped window works correctly QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); QVERIFY(effectsWindowShownSpy.isValid()); QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); QVERIFY(effectsWindowHiddenSpy.isValid()); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // now let's render Test::render(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(clientAddedSpy.isEmpty()); QVERIFY(clientAddedSpy.wait()); auto client = clientAddedSpy.first().first().value(); QVERIFY(client); QVERIFY(client->isShown(true)); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->depth(), 32); QVERIFY(client->hasAlpha()); QCOMPARE(client->icon().name(), QStringLiteral("wayland")); QCOMPARE(workspace()->activeClient(), client); QVERIFY(effectsWindowShownSpy.isEmpty()); QVERIFY(client->isMaximizable()); QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QVERIFY(client->isResizable()); QVERIFY(client->property("maximizable").toBool()); QVERIFY(client->property("moveable").toBool()); QVERIFY(client->property("moveableAcrossScreens").toBool()); QVERIFY(client->property("resizeable").toBool()); QCOMPARE(client->isInternal(), false); QVERIFY(client->effectWindow()); QVERIFY(!client->effectWindow()->internalWindow()); QCOMPARE(client->internalId().isNull(), false); const auto uuid = client->internalId(); QUuid deletedUuid; QCOMPARE(deletedUuid.isNull(), true); connect(client, &ShellClient::windowClosed, this, [&deletedUuid] (Toplevel *, Deleted *d) { deletedUuid = d->internalId(); }); // now unmap QSignalSpy hiddenSpy(client, &ShellClient::windowHidden); QVERIFY(hiddenSpy.isValid()); QSignalSpy windowClosedSpy(client, &ShellClient::windowClosed); QVERIFY(windowClosedSpy.isValid()); surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QVERIFY(windowClosedSpy.isEmpty()); QVERIFY(!workspace()->activeClient()); QCOMPARE(effectsWindowHiddenSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.first().first().value(), client->effectWindow()); QSignalSpy windowShownSpy(client, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(surface.data(), QSize(100, 50), Qt::blue, QImage::Format_RGB32); QCOMPARE(clientAddedSpy.count(), 1); QVERIFY(windowShownSpy.wait()); QCOMPARE(windowShownSpy.count(), 1); QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->depth(), 24); QVERIFY(!client->hasAlpha()); QCOMPARE(workspace()->activeClient(), client); QCOMPARE(effectsWindowShownSpy.count(), 1); QCOMPARE(effectsWindowShownSpy.first().first().value(), client->effectWindow()); // let's unmap again surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(hiddenSpy.count(), 2); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QCOMPARE(client->internalId(), uuid); QVERIFY(windowClosedSpy.isEmpty()); QCOMPARE(effectsWindowHiddenSpy.count(), 2); QCOMPARE(effectsWindowHiddenSpy.last().first().value(), client->effectWindow()); shellSurface.reset(); surface.reset(); QVERIFY(windowClosedSpy.wait()); QCOMPARE(windowClosedSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.count(), 2); QCOMPARE(deletedUuid.isNull(), false); QCOMPARE(deletedUuid, uuid); } void TestShellClient::testDesktopPresenceChanged() { // this test verifies that the desktop presence changed signals are properly emitted QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktop(), 1); effects->setNumberOfDesktops(4); QSignalSpy desktopPresenceChangedClientSpy(c, &ShellClient::desktopPresenceChanged); QVERIFY(desktopPresenceChangedClientSpy.isValid()); QSignalSpy desktopPresenceChangedWorkspaceSpy(workspace(), &Workspace::desktopPresenceChanged); QVERIFY(desktopPresenceChangedWorkspaceSpy.isValid()); QSignalSpy desktopPresenceChangedEffectsSpy(effects, &EffectsHandler::desktopPresenceChanged); QVERIFY(desktopPresenceChangedEffectsSpy.isValid()); // let's change the desktop workspace()->sendClientToDesktop(c, 2, false); QCOMPARE(c->desktop(), 2); QCOMPARE(desktopPresenceChangedClientSpy.count(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.count(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.count(), 1); // verify the arguments QCOMPARE(desktopPresenceChangedClientSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedClientSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(0).value(), c->effectWindow()); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(2).toInt(), 2); } void TestShellClient::testTransientPositionAfterRemap() { // this test simulates the situation that a transient window gets reused and the parent window // moved between the two usages QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); // create the Transient window QScopedPointer transientSurface(Test::createSurface()); QScopedPointer transientShellSurface(Test::createShellSurface(transientSurface.data())); transientShellSurface->setTransient(surface.data(), QPoint(5, 10)); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(transient); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); // unmap the transient QSignalSpy windowHiddenSpy(transient, &ShellClient::windowHidden); QVERIFY(windowHiddenSpy.isValid()); transientSurface->attachBuffer(Buffer::Ptr()); transientSurface->commit(Surface::CommitFlag::None); QVERIFY(windowHiddenSpy.wait()); // now move the parent surface c->setGeometry(c->geometry().translated(5, 10)); // now map the transient again QSignalSpy windowShownSpy(transient, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(windowShownSpy.wait()); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); } void TestShellClient::testWindowOutputs_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testWindowOutputs() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto size = QSize(200,200); QSignalSpy outputEnteredSpy(surface.data(), &Surface::outputEntered); QSignalSpy outputLeftSpy(surface.data(), &Surface::outputLeft); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); //move to be in the first screen c->setGeometry(QRect(QPoint(100,100), size)); //we don't don't know where the compositor first placed this window, //this might fire, it might not outputEnteredSpy.wait(5); outputEnteredSpy.clear(); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(0,0)); //move to overlapping both first and second screen c->setGeometry(QRect(QPoint(1250,100), size)); QVERIFY(outputEnteredSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 0); QCOMPARE(surface->outputs().count(), 2); QVERIFY(surface->outputs()[0] != surface->outputs()[1]); //move entirely into second screen c->setGeometry(QRect(QPoint(1400,100), size)); QVERIFY(outputLeftSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 1); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(1280,0)); } void TestShellClient::testMinimizeActiveWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testMinimizeActiveWindow() { // this test verifies that when minimizing the active window it gets deactivated QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); workspace()->slotWindowMinimize(); QVERIFY(!c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(!c->isActive()); QVERIFY(!workspace()->activeClient()); QVERIFY(c->isMinimized()); // unminimize again c->unminimize(); QVERIFY(!c->isMinimized()); QVERIFY(c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellWmBase") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellWmBase - deco") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Server; } void TestShellClient::testFullscreen() { // this test verifies that a window can be properly fullscreened QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); ShellSurface* wlShellSurface = nullptr; XdgShellSurface *xdgShellSurface = nullptr; // fullscreen the window switch (type) { case Test::ShellSurfaceType::WlShell: wlShellSurface = qobject_cast(shellSurface.data()); break; case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: case Test::ShellSurfaceType::XdgShellStable: xdgShellSurface = qobject_cast(shellSurface.data()); break; default: Q_UNREACHABLE(); break; } QVERIFY(wlShellSurface || xdgShellSurface); QVERIFY(!(wlShellSurface && xdgShellSurface)); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->layer(), NormalLayer); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QCOMPARE(c->sizeForClientSize(c->clientSize()), c->geometry().size()); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); if (xdgShellSurface) { QVERIFY(configureRequestedSpy.isValid()); } if (wlShellSurface) { wlShellSurface->setFullscreen(); } if (xdgShellSurface) { xdgShellSurface->setFullscreen(true); } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QCOMPARE(sizeChangeRequestedSpy.first().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); if (xdgShellSurface) { for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } } Test::render(surface.data(), sizeChangeRequestedSpy.first().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.first().first().toSize())); QCOMPARE(c->layer(), ActiveLayer); // swap back to normal if (wlShellSurface) { wlShellSurface->setToplevel(); } if (xdgShellSurface) { xdgShellSurface->setFullscreen(false); } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->layer(), NormalLayer); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testFullscreenRestore_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgShellWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testFullscreenRestore() { // this test verifies that windows created fullscreen can be later properly restored QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); XdgShellSurface *xdgShellSurface = Test::createXdgShellSurface(type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly); QSignalSpy configureRequestedSpy(xdgShellSurface, SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); // fullscreen the window xdgShellSurface->setFullscreen(true); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); const auto state = configureRequestedSpy.first()[1].value(); QCOMPARE(size, screens()->size(0)); QVERIFY(state & KWayland::Client::XdgShellSurface::State::Fullscreen); xdgShellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); QVERIFY(c); QVERIFY(c->isFullScreen()); configureRequestedSpy.wait(100); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); // swap back to normal configureRequestedSpy.clear(); xdgShellSurface->setFullscreen(false); QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.last().first().toSize(), QSize(0, 0)); QVERIFY(!c->isFullScreen()); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toUInt()); } Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(!c->isFullScreen()); QCOMPARE(c->geometry().size(), QSize(100, 50)); } void TestShellClient::testUserCanSetFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("expected"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << false; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << true; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << true; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable << true; } void TestShellClient::testUserCanSetFullscreen() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QTEST(c->userCanSetFullScreen(), "expected"); } void TestShellClient::testUserSetFullscreenWlShell() { // wlshell cannot sync fullscreen to the client QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); c->setFullScreen(true); QCOMPARE(fullscreenChangedSpy.count(), 0); QVERIFY(!c->isFullScreen()); } void TestShellClient::testUserSetFullscreenXdgShell_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testUserSetFullscreenXdgShell() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createXdgShellSurface( type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); QVERIFY(!shellSurface.isNull()); // wait for the initial configure event QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); surface->commit(Surface::CommitFlag::None); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); // The client gets activated, which gets another configure event. Though that's not relevant to the test configureRequestedSpy.wait(10); QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); c->setFullScreen(true); QCOMPARE(c->isFullScreen(), true); configureRequestedSpy.clear(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); QCOMPARE(configureRequestedSpy.first().at(0).toSize(), screens()->size(0)); const auto states = configureRequestedSpy.first().at(1).value(); QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Activated)); QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Maximized)); QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Resizing)); QCOMPARE(fullscreenChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); shellSurface->ackConfigure(configureRequestedSpy.first().at(2).value()); // unset fullscreen again c->setFullScreen(false); QCOMPARE(c->isFullScreen(), false); configureRequestedSpy.clear(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); QCOMPARE(configureRequestedSpy.first().at(0).toSize(), QSize(100, 50)); QVERIFY(!configureRequestedSpy.first().at(1).value().testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); QCOMPARE(fullscreenChangedSpy.count(), 2); QVERIFY(!c->isFullScreen()); } void TestShellClient::testMaximizedToFullscreenWlShell_data() { QTest::addColumn("decoMode"); QTest::newRow("wlShell") << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << ServerSideDecoration::Mode::Server; } void TestShellClient::testMaximizedToFullscreenWlShell() { // this test verifies that a window can be properly fullscreened after maximizing QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(shellSurface.data()); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); shellSurface->setMaximized(); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(geometryChangedSpy.isEmpty(), false); geometryChangedSpy.clear(); // fullscreen the window shellSurface->setFullscreen(); QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.last().first().toSize())); sizeChangeRequestedSpy.clear(); // swap back to normal shellSurface->setToplevel(); QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); - // fails as we don't correctly call setMaximize(false) + + // TODO: we should test both cases with fixed fake decoration for autotests. + const bool hasBorders = Decoration::DecorationBridge::self()->settings()->borderSize() != KDecoration2::BorderSize::None; + + // fails if we have borders as we don't correctly call setMaximize(false) // but realistically the only toolkits that support the deco also use XDGShell - QEXPECT_FAIL("wlShell - deco", "With decoration incorrect geometry requested", Continue); + if (hasBorders) { + QEXPECT_FAIL("wlShell - deco", "With decoration incorrect geometry requested", Continue); + } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); } // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testMaximizedToFullscreenXdgShell_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellWmBase") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellWmBase - deco") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Server; } void TestShellClient::testMaximizedToFullscreenXdgShell() { // this test verifies that a window can be properly fullscreened after maximizing QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); XdgShellSurface *xdgShellSurface = nullptr; // fullscreen the window switch (type) { case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: case Test::ShellSurfaceType::XdgShellStable: xdgShellSurface = qobject_cast(shellSurface.data()); break; default: Q_UNREACHABLE(); break; } QVERIFY(xdgShellSurface); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); QVERIFY(configureRequestedSpy.isValid()); xdgShellSurface->setMaximized(true); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(geometryChangedSpy.isEmpty(), false); geometryChangedSpy.clear(); // fullscreen the window xdgShellSurface->setFullscreen(true); QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.last().first().toSize())); sizeChangeRequestedSpy.clear(); // swap back to normal switch (type) { case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: case Test::ShellSurfaceType::XdgShellStable: qobject_cast(shellSurface.data())->setFullscreen(false); qobject_cast(shellSurface.data())->setMaximized(false); break; default: Q_UNREACHABLE(); } QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); // XDG will legitimately get two updates. They might be batched if (xdgShellSurface && sizeChangeRequestedSpy.count() == 1) { QVERIFY(sizeChangeRequestedSpy.wait()); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); } // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testWindowOpensLargerThanScreen_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testWindowOpensLargerThanScreen() { // this test creates a window which is as large as the screen, but is decorated // the window should get resized to fit into the screen, BUG: 366632 QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); auto c = Test::renderAndWaitForShown(surface.data(), screens()->size(0), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->clientSize(), screens()->size(0)); QVERIFY(c->isDecorated()); QEXPECT_FAIL("", "BUG 366632", Continue); QVERIFY(sizeChangeRequestedSpy.wait(10)); } void TestShellClient::testHidden_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testHidden() { // this test verifies that when hiding window it doesn't get shown QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); c->hideClient(true); QVERIFY(!c->isShown(true)); QVERIFY(!c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); // unhide again c->hideClient(false); QVERIFY(c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); //QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testDesktopFileName() { QIcon::setThemeName(QStringLiteral("breeze")); // this test verifies that desktop file name is passed correctly to the window QScopedPointer surface(Test::createSurface()); // only xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setAppId(QByteArrayLiteral("org.kde.foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.foo")); QCOMPARE(c->resourceClass(), QByteArrayLiteral("org.kde.foo")); QVERIFY(c->resourceName().startsWith("testShellClient")); // the desktop file does not exist, so icon should be generic Wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QSignalSpy desktopFileNameChangedSpy(c, &AbstractClient::desktopFileNameChanged); QVERIFY(desktopFileNameChangedSpy.isValid()); QSignalSpy iconChangedSpy(c, &ShellClient::iconChanged); QVERIFY(iconChangedSpy.isValid()); shellSurface->setAppId(QByteArrayLiteral("org.kde.bar")); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.bar")); QCOMPARE(c->resourceClass(), QByteArrayLiteral("org.kde.bar")); QVERIFY(c->resourceName().startsWith("testShellClient")); // icon should still be wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QVERIFY(iconChangedSpy.isEmpty()); const QString dfPath = QFINDTESTDATA("data/example.desktop"); shellSurface->setAppId(dfPath.toUtf8()); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(iconChangedSpy.count(), 1); QCOMPARE(QString::fromUtf8(c->desktopFileName()), dfPath); QCOMPARE(c->icon().name(), QStringLiteral("kwin")); } void TestShellClient::testCaptionSimplified() { // this test verifies that caption is properly trimmed // see BUG 323798 comment #12 QScopedPointer surface(Test::createSurface()); // only done for xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); const QString origTitle = QString::fromUtf8(QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")); shellSurface->setTitle(origTitle); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->caption() != origTitle); QCOMPARE(c->caption(), origTitle.simplified()); } void TestShellClient::testCaptionMultipleWindows() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setTitle(QStringLiteral("foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->caption(), QStringLiteral("foo")); QCOMPARE(c->captionNormal(), QStringLiteral("foo")); QCOMPARE(c->captionSuffix(), QString()); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data()))); shellSurface2->setTitle(QStringLiteral("foo")); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); QVERIFY(c2); QCOMPARE(c2->caption(), QStringLiteral("foo <2>")); QCOMPARE(c2->captionNormal(), QStringLiteral("foo")); QCOMPARE(c2->captionSuffix(), QStringLiteral(" <2>")); QScopedPointer surface3(Test::createSurface()); QScopedPointer shellSurface3(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data()))); shellSurface3->setTitle(QStringLiteral("foo")); auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::blue); QVERIFY(c3); QCOMPARE(c3->caption(), QStringLiteral("foo <3>")); QCOMPARE(c3->captionNormal(), QStringLiteral("foo")); QCOMPARE(c3->captionSuffix(), QStringLiteral(" <3>")); QScopedPointer surface4(Test::createSurface()); QScopedPointer shellSurface4(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface4.data()))); shellSurface4->setTitle(QStringLiteral("bar")); auto c4 = Test::renderAndWaitForShown(surface4.data(), QSize(100, 50), Qt::blue); QVERIFY(c4); QCOMPARE(c4->caption(), QStringLiteral("bar")); QCOMPARE(c4->captionNormal(), QStringLiteral("bar")); QCOMPARE(c4->captionSuffix(), QString()); QSignalSpy captionChangedSpy(c4, &ShellClient::captionChanged); QVERIFY(captionChangedSpy.isValid()); shellSurface4->setTitle(QStringLiteral("foo")); QVERIFY(captionChangedSpy.wait()); QCOMPARE(captionChangedSpy.count(), 1); QCOMPARE(c4->caption(), QStringLiteral("foo <4>")); QCOMPARE(c4->captionNormal(), QStringLiteral("foo")); QCOMPARE(c4->captionSuffix(), QStringLiteral(" <4>")); } void TestShellClient::testUnresponsiveWindow_data() { QTest::addColumn("shellInterface");//see env selection in qwaylandintegration.cpp QTest::addColumn("socketMode"); //wl-shell ping is not implemented //QTest::newRow("wl-shell display") << "wl-shell" << false; //QTest::newRow("wl-shell socket") << "wl-shell" << true; QTest::newRow("xdgv5 display") << "xdg-shell-v5" << false; QTest::newRow("xdgv5 socket") << "xdg-shell-v5" << true; QTest::newRow("xdgv6 display") << "xdg-shell-v6" << false; QTest::newRow("xdgv6 socket") << "xdg-shell-v6" << true; //TODO add XDG WM Base when Kwin relies on Qt 5.12 } void TestShellClient::testUnresponsiveWindow() { // this test verifies that killWindow properly terminates a process // for this an external binary is launched const QString kill = QFINDTESTDATA(QStringLiteral("kill")); QVERIFY(!kill.isEmpty()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QScopedPointer process(new QProcess); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QFETCH(QString, shellInterface); QFETCH(bool, socketMode); env.insert("QT_WAYLAND_SHELL_INTEGRATION", shellInterface); if (socketMode) { int sx[2]; QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0); waylandServer()->display()->createClient(sx[0]); int socket = dup(sx[1]); QVERIFY(socket != -1); env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); env.remove("WAYLAND_DISPLAY"); } else { env.insert("WAYLAND_DISPLAY", s_socketName); } process->setProcessEnvironment(env); process->setProcessChannelMode(QProcess::ForwardedChannels); process->setProgram(kill); QSignalSpy processStartedSpy{process.data(), &QProcess::started}; QVERIFY(processStartedSpy.isValid()); process->start(); QVERIFY(processStartedSpy.wait()); AbstractClient *killClient = nullptr; if (shellClientAddedSpy.isEmpty()) { QVERIFY(shellClientAddedSpy.wait()); } ::kill(process->processId(), SIGUSR1); // send a signal to freeze the process killClient = shellClientAddedSpy.first().first().value(); QVERIFY(killClient); QSignalSpy unresponsiveSpy(killClient, &AbstractClient::unresponsiveChanged); QSignalSpy killedSpy(process.data(), static_cast(&QProcess::finished)); QSignalSpy deletedSpy(killClient, &QObject::destroyed); qint64 startTime = QDateTime::currentMSecsSinceEpoch(); //wait for the process to be frozen QTest::qWait(10); //pretend the user clicked the close button killClient->closeWindow(); //client should not yet be marked unresponsive nor killed QVERIFY(!killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(unresponsiveSpy.wait()); //client should be marked unresponsive but not killed auto elapsed1 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed1 > 900 && elapsed1 < 1200); //ping timer is 1s, but coarse timers on a test across two processes means we need a fuzzy compare QVERIFY(killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(deletedSpy.wait()); if (!socketMode) { //process was killed - because we're across process this could happen in either order QVERIFY(killedSpy.count() || killedSpy.wait()); } auto elapsed2 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed2 > 1800); //second ping comes in a second later } void TestShellClient::testX11WindowId_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testX11WindowId() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->windowId() != 0); QCOMPARE(c->window(), 0u); } void TestShellClient::testAppMenu() { //register a faux appmenu client QVERIFY (QDBusConnection::sessionBus().registerService("org.kde.kappmenu")); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV6, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QScopedPointer menu(Test::waylandAppMenuManager()->create(surface.data())); QSignalSpy spy(c, &ShellClient::hasApplicationMenuChanged); menu->setAddress("service.name", "object/path"); spy.wait(); QCOMPARE(c->hasApplicationMenu(), true); QCOMPARE(c->applicationMenuServiceName(), QString("service.name")); QCOMPARE(c->applicationMenuObjectPath(), QString("object/path")); QVERIFY (QDBusConnection::sessionBus().unregisterService("org.kde.kappmenu")); } void TestShellClient::testNoDecorationModeRequested_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testNoDecorationModeRequested() { // this test verifies that the decoration follows the default mode if no mode is explicitly requested QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); if (deco->mode() != ServerSideDecoration::Mode::Server) { QVERIFY(decoSpy.wait()); } QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->noBorder(), false); QCOMPARE(c->isDecorated(), true); } void TestShellClient::testSendClientWithTransientToDesktop_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testSendClientWithTransientToDesktop() { // this test verifies that when sending a client to a desktop all transients are also send to that desktop VirtualDesktopManager::self()->setCount(2); QScopedPointer surface{Test::createSurface()}; QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface{qobject_cast(Test::createShellSurface(type, surface.data()))}; auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); // let's create a transient window QScopedPointer transientSurface{Test::createSurface()}; QScopedPointer transientShellSurface{qobject_cast(Test::createShellSurface(type, transientSurface.data()))}; transientShellSurface->setTransientFor(shellSurface.data()); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(100, 50), Qt::blue); QVERIFY(transient); QCOMPARE(workspace()->activeClient(), transient); QCOMPARE(transient->transientFor(), c); QVERIFY(c->transients().contains(transient)); QCOMPARE(c->desktop(), 1); QVERIFY(!c->isOnAllDesktops()); QCOMPARE(transient->desktop(), 1); QVERIFY(!transient->isOnAllDesktops()); workspace()->slotWindowToDesktop(2); QCOMPARE(c->desktop(), 1); QCOMPARE(transient->desktop(), 2); // activate c workspace()->activateClient(c); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->isActive()); // and send it to the desktop it's already on QCOMPARE(c->desktop(), 1); QCOMPARE(transient->desktop(), 2); workspace()->slotWindowToDesktop(1); // which should move the transient back to the desktop QCOMPARE(c->desktop(), 1); QCOMPARE(transient->desktop(), 1); } void TestShellClient::testMinimizeWindowWithTransients_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testMinimizeWindowWithTransients() { // this test verifies that when minimizing/unminimizing a window all its // transients will be minimized/unminimized as well // create the main window QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(qobject_cast( Test::createShellSurface(type, surface.data()))); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(!c->isMinimized()); // create a transient window QScopedPointer transientSurface(Test::createSurface()); QScopedPointer transientShellSurface(qobject_cast( Test::createShellSurface(type, transientSurface.data()))); transientShellSurface->setTransientFor(shellSurface.data()); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(100, 50), Qt::red); QVERIFY(transient); QVERIFY(!transient->isMinimized()); QCOMPARE(transient->transientFor(), c); QVERIFY(c->hasTransient(transient, false)); // minimize the main window, the transient should be minimized as well c->minimize(); QVERIFY(c->isMinimized()); QVERIFY(transient->isMinimized()); // unminimize the main window, the transient should be unminimized as well c->unminimize(); QVERIFY(!c->isMinimized()); QVERIFY(!transient->isMinimized()); } void TestShellClient::testXdgDecoration_data() { QTest::addColumn("requestedMode"); QTest::addColumn("expectedMode"); QTest::newRow("client side requested") << XdgDecoration::Mode::ClientSide << XdgDecoration::Mode::ClientSide; QTest::newRow("server side requested") << XdgDecoration::Mode::ServerSide << XdgDecoration::Mode::ServerSide; } void TestShellClient::testXdgDecoration() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); QScopedPointer deco(Test::xdgDecorationManager()->getToplevelDecoration(shellSurface.data())); QSignalSpy decorationConfiguredSpy(deco.data(), &XdgDecoration::modeChanged); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QFETCH(KWayland::Client::XdgDecoration::Mode, requestedMode); QFETCH(KWayland::Client::XdgDecoration::Mode, expectedMode); //request a mode deco->setMode(requestedMode); //kwin will send a configure decorationConfiguredSpy.wait(); configureRequestedSpy.wait(); QCOMPARE(decorationConfiguredSpy.count(), 1); QCOMPARE(decorationConfiguredSpy.first()[0].value(), expectedMode); QVERIFY(configureRequestedSpy.count() > 0); shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QCOMPARE(c->userCanSetNoBorder(), expectedMode == XdgDecoration::Mode::ServerSide); QCOMPARE(c->isDecorated(), expectedMode == XdgDecoration::Mode::ServerSide); } void TestShellClient::testXdgNeverCommitted() { //check we don't crash if we create a shell object but delete the ShellClient before committing it QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); } void TestShellClient::testXdgInitialState() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); QCOMPARE(size, QSize(0, 0)); //client should chose it's preferred size shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(200,100), Qt::blue); QCOMPARE(c->size(), QSize(200, 100)); } void TestShellClient::testXdgInitiallyMaximised() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); shellSurface->setMaximized(true); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); const auto state = configureRequestedSpy.first()[1].value(); QCOMPARE(size, QSize(1280, 1024)); QVERIFY(state & KWayland::Client::XdgShellSurface::State::Maximized); shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(c->size(), QSize(1280, 1024)); } void TestShellClient::testXdgInitiallyMinimized() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); shellSurface->requestMinimize(); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); const auto state = configureRequestedSpy.first()[1].value(); QCOMPARE(size, QSize(0, 0)); QCOMPARE(state, 0); shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); QEXPECT_FAIL("", "Client created in a minimised state is not exposed to kwin bug 404838", Abort); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue, QImage::Format_ARGB32, 10); QVERIFY(c); QVERIFY(c->isMinimized()); } void TestShellClient::testXdgWindowGeometry() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); // Create a 160x140 window in with a margin of 10(left), 20(top), 30(right), 40(bottom). Giving a total buffer size 200, 100 shellSurface->setWindowGeometry(QRect(10, 20, 160, 40)); auto c = Test::renderAndWaitForShown(surface.data(), QSize(200,100), Qt::blue); configureRequestedSpy.wait(); //window activated after being shown QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); // resize to 300,200 in kwin terms c->setGeometry(QRect(100, 100, 300, 200)); QVERIFY(configureRequestedSpy.wait()); // requested geometry should not include the margins we had above const QSize requestedSize = configureRequestedSpy.last()[0].value(); QCOMPARE(requestedSize, QSize(300, 200) - QSize(10 + 30, 20 + 40)); shellSurface->ackConfigure(configureRequestedSpy.last()[2].toUInt()); Test::render(surface.data(), requestedSize + QSize(10 + 30, 20 + 40), Qt::blue); geometryChangedSpy.wait(); // kwin's concept of geometry should remain the same QCOMPARE(c->geometry(), QRect(100, 100, 300, 200)); c->setFullScreen(true); configureRequestedSpy.wait(); // when full screen, the window geometry (i.e without margins) should fill the screen const QSize requestedFullScreenSize = configureRequestedSpy.last()[0].value(); QCOMPARE(requestedFullScreenSize, QSize(1280, 1024)); } WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/autotests/integration/xwayland_input_test.cpp b/autotests/integration/xwayland_input_test.cpp index a88bc3a15..dfbe8f173 100644 --- a/autotests/integration/xwayland_input_test.cpp +++ b/autotests/integration/xwayland_input_test.cpp @@ -1,212 +1,212 @@ /******************************************************************** 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 "deleted.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_input-0"); class XWaylandInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testPointerEnterLeave(); }; void XWaylandInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(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 XWaylandInputTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); QVERIFY(waylandServer()->clients().isEmpty()); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; class X11EventReaderHelper : public QObject { Q_OBJECT public: X11EventReaderHelper(xcb_connection_t *c); Q_SIGNALS: void entered(); void left(); private: void processXcbEvents(); xcb_connection_t *m_connection; QSocketNotifier *m_notifier; }; X11EventReaderHelper::X11EventReaderHelper(xcb_connection_t *c) : QObject() , m_connection(c) , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this)) { connect(m_notifier, &QSocketNotifier::activated, this, &X11EventReaderHelper::processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11EventReaderHelper::processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &X11EventReaderHelper::processXcbEvents); } void X11EventReaderHelper::processXcbEvents() { while (auto event = xcb_poll_for_event(m_connection)) { const uint8_t eventType = event->response_type & ~0x80; switch (eventType) { case XCB_ENTER_NOTIFY: emit entered(); break; case XCB_LEAVE_NOTIFY: emit left(); break; } free(event); } xcb_flush(m_connection); } void XWaylandInputTest::testPointerEnterLeave() { // this test simulates a pointer enter and pointer leave on an X11 window // create the test window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); if (xcb_get_setup(c.data())->release_number < 11800000) { QSKIP("XWayland 1.18 required"); } X11EventReaderHelper eventReader(c.data()); QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); QVERIFY(leftSpy.isValid()); // atom for the screenedge show hide functionality Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, c.data()); xcb_window_t w = xcb_generate_id(c.data()); - const QRect windowGeometry = QRect(0, 0, 10, 20); + const QRect windowGeometry = QRect(0, 0, 100, 200); const uint32_t values[] = { XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW }; 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, XCB_CW_EVENT_MASK, values); 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::Normal); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.last().first().value(); QVERIFY(client); QVERIFY(client->isDecorated()); QVERIFY(!client->hasStrut()); QVERIFY(!client->isHiddenInternal()); QVERIFY(!client->readyForPainting()); QMetaObject::invokeMethod(client, "setReadyForPainting"); QVERIFY(client->readyForPainting()); QVERIFY(!client->surface()); QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); QVERIFY(surfaceChangedSpy.isValid()); QVERIFY(surfaceChangedSpy.wait()); QVERIFY(client->surface()); // move pointer into the window, should trigger an enter QVERIFY(!client->geometry().contains(Cursor::pos())); QVERIFY(enteredSpy.isEmpty()); Cursor::setPos(client->geometry().center()); QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); QVERIFY(waylandServer()->seat()->focusedPointer()); QVERIFY(enteredSpy.wait()); // move out of window Cursor::setPos(client->geometry().bottomRight() + QPoint(10, 10)); QVERIFY(leftSpy.wait()); // destroy window again QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::XWaylandInputTest) #include "xwayland_input_test.moc"