File indexing completed on 2024-05-12 05:30:38

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kwin_wayland_test.h"
0008 
0009 #include "core/output.h"
0010 #include "core/outputbackend.h"
0011 #include "core/outputconfiguration.h"
0012 #include "pointer_input.h"
0013 #include "wayland_server.h"
0014 #include "window.h"
0015 #include "workspace.h"
0016 
0017 #include <KWayland/Client/surface.h>
0018 
0019 using namespace std::chrono_literals;
0020 
0021 namespace KWin
0022 {
0023 
0024 static const QString s_socketName = QStringLiteral("wayland_test_output_changes-0");
0025 
0026 class OutputChangesTest : public QObject
0027 {
0028     Q_OBJECT
0029 
0030 private Q_SLOTS:
0031     void initTestCase();
0032     void init();
0033     void cleanup();
0034 
0035     void testWindowSticksToOutputAfterOutputIsDisabled();
0036     void testWindowSticksToOutputAfterAnotherOutputIsDisabled();
0037     void testWindowSticksToOutputAfterOutputIsMoved();
0038     void testWindowSticksToOutputAfterOutputsAreSwappedLeftToRight();
0039     void testWindowSticksToOutputAfterOutputsAreSwappedRightToLeft();
0040 
0041     void testWindowRestoredAfterEnablingOutput();
0042     void testMaximizedWindowRestoredAfterEnablingOutput();
0043     void testFullScreenWindowRestoredAfterEnablingOutput();
0044     void testWindowRestoredAfterChangingScale();
0045     void testMaximizeStateRestoredAfterEnablingOutput();
0046 
0047     void testWindowNotRestoredAfterMovingWindowAndEnablingOutput();
0048     void testLaptopLidClosed();
0049 };
0050 
0051 void OutputChangesTest::initTestCase()
0052 {
0053     qRegisterMetaType<Window *>();
0054 
0055     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0056     QVERIFY(waylandServer()->init(s_socketName));
0057     Test::setOutputConfig({
0058         QRect(0, 0, 1280, 1024),
0059         QRect(1280, 0, 1280, 1024),
0060     });
0061 
0062     kwinApp()->start();
0063     QVERIFY(applicationStartedSpy.wait());
0064     const auto outputs = workspace()->outputs();
0065     QCOMPARE(outputs.count(), 2);
0066     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0067     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0068 }
0069 
0070 void OutputChangesTest::init()
0071 {
0072     Test::setOutputConfig({
0073         QRect(0, 0, 1280, 1024),
0074         QRect(1280, 0, 1280, 1024),
0075     });
0076     QVERIFY(Test::setupWaylandConnection());
0077 
0078     workspace()->setActiveOutput(QPoint(640, 512));
0079     input()->pointer()->warp(QPoint(640, 512));
0080 }
0081 
0082 void OutputChangesTest::cleanup()
0083 {
0084     Test::destroyWaylandConnection();
0085 }
0086 
0087 void OutputChangesTest::testWindowSticksToOutputAfterOutputIsDisabled()
0088 {
0089     auto outputs = kwinApp()->outputBackend()->outputs();
0090 
0091     // Create a window.
0092     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0093     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0094     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0095     QVERIFY(window);
0096 
0097     // Move the window to some predefined position so the test is more robust.
0098     window->move(QPoint(42, 67));
0099     QCOMPARE(window->frameGeometry(), QRect(42, 67, 100, 50));
0100 
0101     // Disable the output where the window is on.
0102     OutputConfiguration config;
0103     {
0104         auto changeSet = config.changeSet(outputs[0]);
0105         changeSet->enabled = false;
0106     }
0107     workspace()->applyOutputConfiguration(config);
0108 
0109     // The window will be sent to the second output, which is at (1280, 0).
0110     QCOMPARE(window->frameGeometry(), QRect(1280 + 42, 0 + 67, 100, 50));
0111 }
0112 
0113 void OutputChangesTest::testWindowSticksToOutputAfterAnotherOutputIsDisabled()
0114 {
0115     auto outputs = kwinApp()->outputBackend()->outputs();
0116 
0117     // Create a window.
0118     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0119     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0120     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0121     QVERIFY(window);
0122 
0123     // Move the window to the second output.
0124     window->move(QPoint(1280 + 42, 67));
0125     QCOMPARE(window->frameGeometry(), QRect(1280 + 42, 67, 100, 50));
0126 
0127     // Disable the first output.
0128     OutputConfiguration config;
0129     {
0130         auto changeSet = config.changeSet(outputs[0]);
0131         changeSet->enabled = false;
0132     }
0133     {
0134         auto changeSet = config.changeSet(outputs[1]);
0135         changeSet->pos = QPoint(0, 0);
0136     }
0137     workspace()->applyOutputConfiguration(config);
0138 
0139     // The position of the window relative to its output should remain the same.
0140     QCOMPARE(window->frameGeometry(), QRect(42, 67, 100, 50));
0141 }
0142 
0143 void OutputChangesTest::testWindowSticksToOutputAfterOutputIsMoved()
0144 {
0145     auto outputs = kwinApp()->outputBackend()->outputs();
0146 
0147     // Create a window.
0148     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0149     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0150     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0151     QVERIFY(window);
0152 
0153     // Move the window to some predefined position so the test is more robust.
0154     window->move(QPoint(42, 67));
0155     QCOMPARE(window->frameGeometry(), QRect(42, 67, 100, 50));
0156 
0157     // Disable the first output.
0158     OutputConfiguration config;
0159     {
0160         auto changeSet = config.changeSet(outputs[0]);
0161         changeSet->pos = QPoint(-10, 20);
0162     }
0163     workspace()->applyOutputConfiguration(config);
0164 
0165     // The position of the window relative to its output should remain the same.
0166     QCOMPARE(window->frameGeometry(), QRect(-10 + 42, 20 + 67, 100, 50));
0167 }
0168 
0169 void OutputChangesTest::testWindowSticksToOutputAfterOutputsAreSwappedLeftToRight()
0170 {
0171     // This test verifies that a window placed on the left monitor sticks
0172     // to that monitor even after the monitors are swapped horizontally.
0173 
0174     const auto outputs = kwinApp()->outputBackend()->outputs();
0175 
0176     // Create a window.
0177     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0178     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0179     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0180     QVERIFY(window);
0181 
0182     // Move the window to the left output.
0183     window->move(QPointF(0, 0));
0184     QCOMPARE(window->output(), outputs[0]);
0185     QCOMPARE(window->frameGeometry(), QRectF(0, 0, 100, 50));
0186 
0187     // Swap outputs.
0188     OutputConfiguration config;
0189     {
0190         auto changeSet1 = config.changeSet(outputs[0]);
0191         changeSet1->pos = QPoint(1280, 0);
0192         auto changeSet2 = config.changeSet(outputs[1]);
0193         changeSet2->pos = QPoint(0, 0);
0194     }
0195     workspace()->applyOutputConfiguration(config);
0196 
0197     // The window should be still on its original output.
0198     QCOMPARE(window->output(), outputs[0]);
0199     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 100, 50));
0200 }
0201 
0202 void OutputChangesTest::testWindowSticksToOutputAfterOutputsAreSwappedRightToLeft()
0203 {
0204     // This test verifies that a window placed on the right monitor sticks
0205     // to that monitor even after the monitors are swapped horizontally.
0206 
0207     const auto outputs = kwinApp()->outputBackend()->outputs();
0208 
0209     // Create a window.
0210     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0211     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0212     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0213     QVERIFY(window);
0214 
0215     // Move the window to the right output.
0216     window->move(QPointF(1280, 0));
0217     QCOMPARE(window->output(), outputs[1]);
0218     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 100, 50));
0219 
0220     // Swap outputs.
0221     OutputConfiguration config;
0222     {
0223         auto changeSet1 = config.changeSet(outputs[0]);
0224         changeSet1->pos = QPoint(1280, 0);
0225         auto changeSet2 = config.changeSet(outputs[1]);
0226         changeSet2->pos = QPoint(0, 0);
0227     }
0228     workspace()->applyOutputConfiguration(config);
0229 
0230     // The window should be still on its original output.
0231     QCOMPARE(window->output(), outputs[1]);
0232     QCOMPARE(window->frameGeometry(), QRectF(0, 0, 100, 50));
0233 }
0234 
0235 void OutputChangesTest::testWindowRestoredAfterEnablingOutput()
0236 {
0237     // This test verifies that a window will be moved back to its original output when it's hotplugged.
0238 
0239     const auto outputs = kwinApp()->outputBackend()->outputs();
0240 
0241     // Create a window.
0242     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0243     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0244     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0245     QVERIFY(window);
0246 
0247     // Move the window to the right output.
0248     window->move(QPointF(1280 + 50, 100));
0249     QCOMPARE(window->output(), outputs[1]);
0250     QCOMPARE(window->frameGeometry(), QRectF(1280 + 50, 100, 100, 50));
0251 
0252     // Disable the right output.
0253     OutputConfiguration config1;
0254     {
0255         auto changeSet = config1.changeSet(outputs[1]);
0256         changeSet->enabled = false;
0257     }
0258     workspace()->applyOutputConfiguration(config1);
0259 
0260     // The window will be moved to the left monitor.
0261     QCOMPARE(window->output(), outputs[0]);
0262     QCOMPARE(window->frameGeometry(), QRectF(50, 100, 100, 50));
0263 
0264     // Enable the right monitor.
0265     OutputConfiguration config2;
0266     {
0267         auto changeSet = config2.changeSet(outputs[1]);
0268         changeSet->enabled = true;
0269     }
0270     workspace()->applyOutputConfiguration(config2);
0271 
0272     // The window will be moved back to the right monitor.
0273     QCOMPARE(window->output(), outputs[1]);
0274     QCOMPARE(window->frameGeometry(), QRectF(1280 + 50, 100, 100, 50));
0275 }
0276 
0277 void OutputChangesTest::testWindowNotRestoredAfterMovingWindowAndEnablingOutput()
0278 {
0279     // This test verifies that a window won't be moved to its original output when it's
0280     // hotplugged because the window was moved manually by the user.
0281 
0282     const auto outputs = kwinApp()->outputBackend()->outputs();
0283 
0284     // Create a window.
0285     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0286     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0287     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0288     QVERIFY(window);
0289 
0290     // Move the window to the right output.
0291     window->move(QPointF(1280 + 50, 100));
0292     QCOMPARE(window->output(), outputs[1]);
0293     QCOMPARE(window->frameGeometry(), QRectF(1280 + 50, 100, 100, 50));
0294 
0295     // Disable the right output.
0296     OutputConfiguration config1;
0297     {
0298         auto changeSet = config1.changeSet(outputs[1]);
0299         changeSet->enabled = false;
0300     }
0301     workspace()->applyOutputConfiguration(config1);
0302 
0303     // The window will be moved to the left monitor.
0304     QCOMPARE(window->output(), outputs[0]);
0305     QCOMPARE(window->frameGeometry(), QRectF(50, 100, 100, 50));
0306 
0307     // Pretend that the user moved the window.
0308     workspace()->slotWindowMove();
0309     QVERIFY(window->isInteractiveMove());
0310     window->keyPressEvent(Qt::Key_Right);
0311     window->keyPressEvent(Qt::Key_Enter);
0312     QCOMPARE(window->frameGeometry(), QRectF(58, 100, 100, 50));
0313 
0314     // Enable the right monitor.
0315     OutputConfiguration config2;
0316     {
0317         auto changeSet = config2.changeSet(outputs[1]);
0318         changeSet->enabled = true;
0319     }
0320     workspace()->applyOutputConfiguration(config2);
0321 
0322     // The window is still on the left monitor because user manually moved it.
0323     QCOMPARE(window->output(), outputs[0]);
0324     QCOMPARE(window->frameGeometry(), QRectF(58, 100, 100, 50));
0325 }
0326 
0327 void OutputChangesTest::testMaximizedWindowRestoredAfterEnablingOutput()
0328 {
0329     // This test verifies that a maximized window will be moved to its original
0330     // output when it's re-enabled.
0331 
0332     const auto outputs = kwinApp()->outputBackend()->outputs();
0333 
0334     // Create a window.
0335     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0336     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0337     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0338     QVERIFY(window);
0339 
0340     // kwin will send a configure event with the actived state.
0341     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0342     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0343     QVERIFY(surfaceConfigureRequestedSpy.wait());
0344 
0345     // Move the window to the right monitor and make it maximized.
0346     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0347     window->move(QPointF(1280 + 50, 100));
0348     window->maximize(MaximizeFull);
0349     QVERIFY(surfaceConfigureRequestedSpy.wait());
0350     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024));
0351     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0352     Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
0353     QVERIFY(frameGeometryChangedSpy.wait());
0354     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
0355     QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024));
0356     QCOMPARE(window->output(), outputs[1]);
0357     QCOMPARE(window->maximizeMode(), MaximizeFull);
0358     QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
0359     QCOMPARE(window->geometryRestore(), QRectF(1280 + 50, 100, 100, 50));
0360 
0361     // Disable the right output.
0362     OutputConfiguration config1;
0363     {
0364         auto changeSet = config1.changeSet(outputs[1]);
0365         changeSet->enabled = false;
0366     }
0367     workspace()->applyOutputConfiguration(config1);
0368 
0369     // The window will be moved to the left monitor, the geometry restore will be updated too.
0370     QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024));
0371     QCOMPARE(window->moveResizeGeometry(), QRectF(0, 0, 1280, 1024));
0372     QCOMPARE(window->output(), outputs[0]);
0373     QCOMPARE(window->maximizeMode(), MaximizeFull);
0374     QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
0375     QCOMPARE(window->geometryRestore(), QRectF(50, 100, 100, 50));
0376 
0377     // Enable the right monitor.
0378     OutputConfiguration config2;
0379     {
0380         auto changeSet = config2.changeSet(outputs[1]);
0381         changeSet->enabled = true;
0382     }
0383     workspace()->applyOutputConfiguration(config2);
0384 
0385     // The window will be moved back to the right monitor, the geometry restore will be updated too.
0386     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
0387     QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024));
0388     QCOMPARE(window->output(), outputs[1]);
0389     QCOMPARE(window->maximizeMode(), MaximizeFull);
0390     QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
0391     QCOMPARE(window->geometryRestore(), QRectF(1280 + 50, 100, 100, 50));
0392 }
0393 
0394 void OutputChangesTest::testFullScreenWindowRestoredAfterEnablingOutput()
0395 {
0396     // This test verifies that a fullscreen window will be moved to its original
0397     // output when it's re-enabled.
0398 
0399     const auto outputs = kwinApp()->outputBackend()->outputs();
0400 
0401     // Create a window.
0402     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0403     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0404     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0405     QVERIFY(window);
0406 
0407     // kwin will send a configure event with the actived state.
0408     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0409     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0410     QVERIFY(surfaceConfigureRequestedSpy.wait());
0411 
0412     // Move the window to the right monitor and make it fullscreen.
0413     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0414     window->move(QPointF(1280 + 50, 100));
0415     window->setFullScreen(true);
0416     QVERIFY(surfaceConfigureRequestedSpy.wait());
0417     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024));
0418     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0419     Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
0420     QVERIFY(frameGeometryChangedSpy.wait());
0421     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
0422     QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024));
0423     QCOMPARE(window->output(), outputs[1]);
0424     QCOMPARE(window->isFullScreen(), true);
0425     QCOMPARE(window->isRequestedFullScreen(), true);
0426     QCOMPARE(window->fullscreenGeometryRestore(), QRectF(1280 + 50, 100, 100, 50));
0427 
0428     // Disable the right output.
0429     OutputConfiguration config1;
0430     {
0431         auto changeSet = config1.changeSet(outputs[1]);
0432         changeSet->enabled = false;
0433     }
0434     workspace()->applyOutputConfiguration(config1);
0435 
0436     // The window will be moved to the left monitor, the geometry restore will be updated too.
0437     QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024));
0438     QCOMPARE(window->moveResizeGeometry(), QRectF(0, 0, 1280, 1024));
0439     QCOMPARE(window->output(), outputs[0]);
0440     QCOMPARE(window->isFullScreen(), true);
0441     QCOMPARE(window->isRequestedFullScreen(), true);
0442     QCOMPARE(window->fullscreenGeometryRestore(), QRectF(50, 100, 100, 50));
0443 
0444     // Enable the right monitor.
0445     OutputConfiguration config2;
0446     {
0447         auto changeSet = config2.changeSet(outputs[1]);
0448         changeSet->enabled = true;
0449     }
0450     workspace()->applyOutputConfiguration(config2);
0451 
0452     // The window will be moved back to the right monitor, the geometry restore will be updated too.
0453     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
0454     QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024));
0455     QCOMPARE(window->output(), outputs[1]);
0456     QCOMPARE(window->isFullScreen(), true);
0457     QCOMPARE(window->isRequestedFullScreen(), true);
0458     QCOMPARE(window->fullscreenGeometryRestore(), QRectF(1280 + 50, 100, 100, 50));
0459 }
0460 
0461 void OutputChangesTest::testWindowRestoredAfterChangingScale()
0462 {
0463     // This test verifies that a window will be moved to its original position after changing the scale of an output
0464 
0465     const auto output = kwinApp()->outputBackend()->outputs().front();
0466 
0467     // Create a window.
0468     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0469     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0470     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0471     QVERIFY(window);
0472 
0473     // Move the window to the bottom right
0474     const QPointF originalPosition(output->geometry().width() - window->width(), output->geometry().height() - window->height());
0475     window->move(originalPosition);
0476     QCOMPARE(window->pos(), originalPosition);
0477     QCOMPARE(window->output(), output);
0478 
0479     // change the scale of the output
0480     OutputConfiguration config1;
0481     {
0482         auto changeSet = config1.changeSet(output);
0483         changeSet->scale = 2;
0484     }
0485     workspace()->applyOutputConfiguration(config1);
0486 
0487     // The window will be moved to still be in the monitor
0488     QCOMPARE(window->pos(), QPointF(output->geometry().width() - window->width(), output->geometry().height() - window->height()));
0489     QCOMPARE(window->output(), output);
0490 
0491     // Change scale back
0492     OutputConfiguration config2;
0493     {
0494         auto changeSet = config2.changeSet(output);
0495         changeSet->scale = 1;
0496     }
0497     workspace()->applyOutputConfiguration(config2);
0498 
0499     // The window will be moved back to where it was before
0500     QCOMPARE(window->pos(), originalPosition);
0501     QCOMPARE(window->output(), output);
0502 }
0503 
0504 void OutputChangesTest::testMaximizeStateRestoredAfterEnablingOutput()
0505 {
0506     // This test verifies that the window state will get restored after disabling and enabling an output,
0507     // even if its maximize state changed in the process
0508 
0509     const auto outputs = kwinApp()->outputBackend()->outputs();
0510 
0511     // Disable the right output
0512     {
0513         OutputConfiguration config;
0514         auto changeSet = config.changeSet(outputs[1]);
0515         changeSet->enabled = false;
0516         workspace()->applyOutputConfiguration(config);
0517     }
0518 
0519     // Create a window.
0520     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0521     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0522     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0523     QVERIFY(window);
0524 
0525     // kwin will send a configure event with the actived state.
0526     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0527     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0528     QVERIFY(surfaceConfigureRequestedSpy.wait());
0529 
0530     const QRectF originalGeometry = window->moveResizeGeometry();
0531 
0532     // Enable the right output
0533     {
0534         OutputConfiguration config;
0535         auto changeSet = config.changeSet(outputs[1]);
0536         changeSet->enabled = true;
0537         workspace()->applyOutputConfiguration(config);
0538     }
0539 
0540     // Move the window to the right monitor and make it maximized.
0541     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0542     window->move(QPointF(1280 + 50, 100));
0543     window->maximize(MaximizeFull);
0544     QVERIFY(surfaceConfigureRequestedSpy.wait());
0545     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024));
0546     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0547     Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
0548     QVERIFY(frameGeometryChangedSpy.wait());
0549     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
0550     QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024));
0551     QCOMPARE(window->output(), outputs[1]);
0552     QCOMPARE(window->maximizeMode(), MaximizeFull);
0553     QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
0554     QCOMPARE(window->geometryRestore(), QRectF(1280 + 50, 100, 100, 50));
0555 
0556     // Disable the right output
0557     {
0558         OutputConfiguration config;
0559         auto changeSet = config.changeSet(outputs[1]);
0560         changeSet->enabled = false;
0561         workspace()->applyOutputConfiguration(config);
0562     }
0563 
0564     // The window will be moved to its prior position on the left monitor and unmaximized
0565     QVERIFY(surfaceConfigureRequestedSpy.wait());
0566     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), originalGeometry.size().toSize());
0567     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0568     Test::render(surface.get(), originalGeometry.size().toSize(), Qt::blue);
0569     QVERIFY(frameGeometryChangedSpy.wait());
0570     QCOMPARE(window->frameGeometry(), originalGeometry);
0571     QCOMPARE(window->moveResizeGeometry(), originalGeometry);
0572     QCOMPARE(window->output(), outputs[0]);
0573     QCOMPARE(window->maximizeMode(), MaximizeRestore);
0574     QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
0575 
0576     // Enable the right output again
0577     {
0578         OutputConfiguration config;
0579         auto changeSet = config.changeSet(outputs[1]);
0580         changeSet->enabled = true;
0581         workspace()->applyOutputConfiguration(config);
0582     }
0583 
0584     // The window will be moved back to the right monitor, maximized and the geometry restore will be updated
0585     QVERIFY(surfaceConfigureRequestedSpy.wait());
0586     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), outputs[1]->geometry().size());
0587     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0588     Test::render(surface.get(), outputs[1]->geometry().size(), Qt::blue);
0589     QVERIFY(frameGeometryChangedSpy.wait());
0590     QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
0591     QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024));
0592     QCOMPARE(window->output(), outputs[1]);
0593     QCOMPARE(window->maximizeMode(), MaximizeFull);
0594     QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
0595     QCOMPARE(window->geometryRestore(), QRectF(1280 + 50, 100, 100, 50));
0596 }
0597 
0598 void OutputChangesTest::testLaptopLidClosed()
0599 {
0600     Test::setOutputConfig({
0601         Test::OutputInfo{
0602             .geometry = QRect(0, 0, 1280, 1024),
0603             .internal = true,
0604         },
0605         Test::OutputInfo{
0606             .geometry = QRect(1280, 0, 1280, 1024),
0607             .internal = false,
0608         },
0609     });
0610     const auto outputs = kwinApp()->outputBackend()->outputs();
0611     const auto internal = outputs.front();
0612     QVERIFY(internal->isInternal());
0613     const auto external = outputs.back();
0614     QVERIFY(!external->isInternal());
0615 
0616     auto lidSwitch = std::make_unique<Test::VirtualInputDevice>();
0617     lidSwitch->setLidSwitch(true);
0618     lidSwitch->setName("virtual lid switch");
0619     input()->addInputDevice(lidSwitch.get());
0620 
0621     auto timestamp = 1ms;
0622     Q_EMIT lidSwitch->switchToggledOff(timestamp++, lidSwitch.get());
0623     QVERIFY(internal->isEnabled());
0624     QVERIFY(external->isEnabled());
0625 
0626     Q_EMIT lidSwitch->switchToggledOn(timestamp++, lidSwitch.get());
0627     QVERIFY(!internal->isEnabled());
0628     QVERIFY(external->isEnabled());
0629 
0630     Q_EMIT lidSwitch->switchToggledOff(timestamp++, lidSwitch.get());
0631     QVERIFY(internal->isEnabled());
0632     QVERIFY(external->isEnabled());
0633 
0634     input()->removeInputDevice(lidSwitch.get());
0635 }
0636 
0637 } // namespace KWin
0638 
0639 WAYLANDTEST_MAIN(KWin::OutputChangesTest)
0640 #include "outputchanges_test.moc"