/******************************************************************** 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 "xdgshellclient.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #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 TestMaximized::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); 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 XdgShellClient gets maximized the Decoration receives the signal // Create the test client. QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); QScopedPointer ssd(Test::waylandServerSideDecoration()->create(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isDecorated()); auto decoration = client->decoration(); QVERIFY(decoration); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); // Wait for configure event that signals the client is active now. QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); // 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 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(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 2); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024 - decoration->borderTop())); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); Test::render(surface.data(), configureRequestedSpy.last().at(0).toSize(), Qt::red); QVERIFY(geometryShapeChangedSpy.wait()); // 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(), hasBorders ? 1 : 0); QCOMPARE(decoration->borderLeft(), 0); QCOMPARE(decoration->borderBottom(), 0); QCOMPARE(decoration->borderRight(), 0); QVERIFY(decoration->borderTop() != 0); // now unmaximize again workspace()->slotWindowMaximize(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 3); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(geometryShapeChangedSpy.wait()); QCOMPARE(geometryShapeChangedSpy.count(), hasBorders ? 4 : 2); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(maximizedChangedSpy.count(), 2); QCOMPARE(maximizedChangedSpy.last().first().toBool(), false); QCOMPARE(bordersChangedSpy.count(), hasBorders ? 2 : 0); QVERIFY(decoration->borderTop() != 0); QVERIFY(decoration->borderLeft() != !hasBorders); QVERIFY(decoration->borderRight() != !hasBorders); QVERIFY(decoration->borderBottom() != !hasBorders); // Destroy the test client. shellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestMaximized::testInitiallyMaximized() { // This test verifies that a window created as maximized, will be maximized. // Create the test client. QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface( Test::createXdgShellStableSurface(surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); shellSurface->setMaximized(true); surface->commit(Surface::CommitFlag::None); // Wait for the initial configure event. XdgShellSurface::States states; QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); states = configureRequestedSpy.last().at(1).value(); QVERIFY(!states.testFlag(XdgShellSurface::State::Activated)); QVERIFY(states.testFlag(XdgShellSurface::State::Maximized)); // Now let's render in an incorrect size. shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); XdgShellClient *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); // Destroy the client. shellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestMaximized::testBorderlessMaximizedWindow() { // This test verifies that a maximized client looses it's server-side // decoration when the borderless maximized option is on. // Enable the borderless maximized windows option. auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", true); group.sync(); Workspace::self()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), true); // Create the test client. QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface( Test::createXdgShellStableSurface(surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer decoration( Test::xdgDecorationManager()->getToplevelDecoration(shellSurface.data())); QSignalSpy decorationConfiguredSpy(decoration.data(), &XdgDecoration::modeChanged); QVERIFY(decorationConfiguredSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); decoration->setMode(XdgDecoration::Mode::ServerSide); surface->commit(Surface::CommitFlag::None); // Wait for the initial configure event. XdgShellSurface::States states; QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); states = configureRequestedSpy.last().at(1).value(); QVERIFY(!states.testFlag(XdgShellSurface::State::Activated)); QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); // Map the client. shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); XdgShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->isDecorated(), true); // We should receive a configure event when the client becomes active. QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 2); states = configureRequestedSpy.last().at(1).value(); QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); // Maximize the client. const QRect maximizeRestoreGeometry = client->geometry(); workspace()->slotWindowMaximize(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 3); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); states = configureRequestedSpy.last().at(1).value(); QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); QVERIFY(states.testFlag(XdgShellSurface::State::Maximized)); QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); Test::render(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(client->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->isDecorated(), false); // Restore the client. workspace()->slotWindowMaximize(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 4); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); states = configureRequestedSpy.last().at(1).value(); QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(client->geometry(), maximizeRestoreGeometry); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->isDecorated(), true); // Destroy the client. shellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } 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, &XdgShellClient::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); } WAYLANDTEST_MAIN(TestMaximized) #include "maximize_test.moc"