/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2016 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ #include "kwin_wayland_test.h" #include "core/output.h" #include "core/platform.h" #include "cursor.h" #include "decorations/decorationbridge.h" #include "decorations/settings.h" #include "wayland_server.h" #include "window.h" #include "workspace.h" #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 testInitiallyMaximizedBorderless(); void testBorderlessMaximizedWindow(); void testMaximizedGainFocusAndBeActivated(); }; void TestMaximized::initTestCase() { qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(applicationStartedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->start(); QVERIFY(applicationStartedSpy.wait()); const auto outputs = workspace()->outputs(); QCOMPARE(outputs.count(), 2); QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); } void TestMaximized::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecorationV1 | Test::AdditionalWaylandInterface::PlasmaShell)); workspace()->setActiveOutput(QPoint(640, 512)); KWin::Cursors::self()->mouse()->setPos(QPoint(640, 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 window. std::unique_ptr surface(Test::createSurface()); std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); std::unique_ptr xdgDecoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy.wait()); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); QVERIFY(window); QVERIFY(window->isDecorated()); auto decoration = window->decoration(); QVERIFY(decoration); QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); // Wait for configure event that signals the window is active now. QVERIFY(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); // 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 = Workspace::self()->decorationBridge()->settings()->borderSize() != KDecoration2::BorderSize::None; // now maximize QSignalSpy bordersChangedSpy(decoration, &KDecoration2::Decoration::bordersChanged); QVERIFY(bordersChangedSpy.isValid()); QSignalSpy maximizedChangedSpy(decoration->client().toStrongRef().get(), &KDecoration2::DecoratedClient::maximizedChanged); QVERIFY(maximizedChangedSpy.isValid()); QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); workspace()->slotWindowMaximize(); QVERIFY(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024 - decoration->borderTop())); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).toSize(), Qt::red); QVERIFY(frameGeometryChangedSpy.wait()); // If no borders, there is only the initial geometry shape change, but none through border resizing. QCOMPARE(frameGeometryChangedSpy.count(), hasBorders ? 2 : 1); QCOMPARE(window->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(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); Test::render(surface.get(), QSize(100, 50), Qt::red); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(frameGeometryChangedSpy.count(), hasBorders ? 4 : 2); QCOMPARE(window->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 window. shellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(window)); } void TestMaximized::testInitiallyMaximizedBorderless() { // This test verifies that a window created as maximized, will be maximized and without Border with BorderlessMaximizedWindows // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", true); group.sync(); Workspace::self()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), true); // Create the test window. std::unique_ptr surface(Test::createSurface()); std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); shellSurface->set_maximized(); QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy.last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::blue); QVERIFY(window); QVERIFY(!window->isDecorated()); QVERIFY(window->isActive()); QVERIFY(window->isMaximizable()); QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value(), Test::XdgToplevelDecorationV1::mode_server_side); // Destroy the window. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(window)); } void TestMaximized::testBorderlessMaximizedWindow() { // This test verifies that a maximized window 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 window. std::unique_ptr surface(Test::createSurface()); std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); states = toplevelConfigureRequestedSpy.last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the window. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); QVERIFY(window); QVERIFY(window->isActive()); QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(window->isDecorated(), true); // We should receive a configure event when the window becomes active. QVERIFY(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); states = toplevelConfigureRequestedSpy.last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Maximize the window. const QRectF maximizeRestoreGeometry = window->frameGeometry(); workspace()->slotWindowMaximize(); QVERIFY(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy.last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); Test::render(surface.get(), QSize(1280, 1024), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(window->isDecorated(), false); // Restore the window. workspace()->slotWindowMaximize(); QVERIFY(surfaceConfigureRequestedSpy.wait()); QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); states = toplevelConfigureRequestedSpy.last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); Test::render(surface.get(), QSize(100, 50), Qt::red); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(window->frameGeometry(), maximizeRestoreGeometry); QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(window->isDecorated(), true); // Destroy the window. shellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(window)); } void TestMaximized::testMaximizedGainFocusAndBeActivated() { // This test verifies that a window will be raised and gain focus when it's maximized std::unique_ptr surface(Test::createSurface()); std::unique_ptr xdgShellSurface(Test::createXdgToplevelSurface(surface.get())); auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); std::unique_ptr surface2(Test::createSurface()); std::unique_ptr xdgShellSurface2(Test::createXdgToplevelSurface(surface2.get())); auto window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); QVERIFY(!window->isActive()); QVERIFY(window2->isActive()); QCOMPARE(workspace()->stackingOrder(), (QList{window, window2})); workspace()->performWindowOperation(window, Options::MaximizeOp); QVERIFY(window->isActive()); QVERIFY(!window2->isActive()); QCOMPARE(workspace()->stackingOrder(), (QList{window2, window})); xdgShellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(window)); xdgShellSurface2.reset(); QVERIFY(Test::waitForWindowDestroyed(window2)); } WAYLANDTEST_MAIN(TestMaximized) #include "maximize_test.moc"