File indexing completed on 2025-03-23 13:48:01
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org> 0006 SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 #include "kwin_wayland_test.h" 0011 0012 #include "core/output.h" 0013 #include "core/outputbackend.h" 0014 #include "cursor.h" 0015 #include "placement.h" 0016 #include "wayland_server.h" 0017 #include "window.h" 0018 #include "workspace.h" 0019 0020 #include <KWayland/Client/compositor.h> 0021 #include <KWayland/Client/plasmashell.h> 0022 #include <KWayland/Client/shm_pool.h> 0023 #include <KWayland/Client/surface.h> 0024 0025 using namespace KWin; 0026 0027 static const QString s_socketName = QStringLiteral("wayland_test_kwin_placement-0"); 0028 0029 struct PlaceWindowResult 0030 { 0031 QSizeF initiallyConfiguredSize; 0032 Test::XdgToplevel::States initiallyConfiguredStates; 0033 QRectF finalGeometry; 0034 }; 0035 0036 class TestPlacement : public QObject 0037 { 0038 Q_OBJECT 0039 0040 private Q_SLOTS: 0041 void init(); 0042 void cleanup(); 0043 void initTestCase(); 0044 0045 void testPlaceSmart(); 0046 void testPlaceMaximized(); 0047 void testPlaceMaximizedLeavesFullscreen(); 0048 void testPlaceCentered(); 0049 void testPlaceUnderMouse(); 0050 void testPlaceZeroCornered(); 0051 void testPlaceRandom(); 0052 void testFullscreen(); 0053 0054 private: 0055 void setPlacementPolicy(PlacementPolicy policy); 0056 /* 0057 * Create a window and return relevant results for testing 0058 * defaultSize is the buffer size to use if the compositor returns an empty size in the first configure 0059 * event. 0060 */ 0061 std::pair<PlaceWindowResult, std::unique_ptr<KWayland::Client::Surface>> createAndPlaceWindow(const QSize &defaultSize); 0062 }; 0063 0064 void TestPlacement::init() 0065 { 0066 QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell)); 0067 0068 workspace()->setActiveOutput(QPoint(640, 512)); 0069 KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); 0070 } 0071 0072 void TestPlacement::cleanup() 0073 { 0074 Test::destroyWaylandConnection(); 0075 } 0076 0077 void TestPlacement::initTestCase() 0078 { 0079 qRegisterMetaType<KWin::Window *>(); 0080 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0081 QVERIFY(waylandServer()->init(s_socketName)); 0082 QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024))); 0083 0084 kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); 0085 0086 kwinApp()->start(); 0087 QVERIFY(applicationStartedSpy.wait()); 0088 const auto outputs = workspace()->outputs(); 0089 QCOMPARE(outputs.count(), 2); 0090 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); 0091 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); 0092 } 0093 0094 void TestPlacement::setPlacementPolicy(PlacementPolicy policy) 0095 { 0096 auto group = kwinApp()->config()->group("Windows"); 0097 group.writeEntry("Placement", Placement::policyToString(policy)); 0098 group.sync(); 0099 Workspace::self()->slotReconfigure(); 0100 } 0101 0102 std::pair<PlaceWindowResult, std::unique_ptr<KWayland::Client::Surface>> TestPlacement::createAndPlaceWindow(const QSize &defaultSize) 0103 { 0104 PlaceWindowResult rc; 0105 0106 // create a new window 0107 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0108 auto shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); 0109 0110 QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested); 0111 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0112 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0113 surfaceConfigureRequestedSpy.wait(); 0114 0115 rc.initiallyConfiguredSize = toplevelConfigureRequestedSpy[0][0].toSize(); 0116 rc.initiallyConfiguredStates = toplevelConfigureRequestedSpy[0][1].value<Test::XdgToplevel::States>(); 0117 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy[0][0].toUInt()); 0118 0119 QSizeF size = rc.initiallyConfiguredSize; 0120 0121 if (size.isEmpty()) { 0122 size = defaultSize; 0123 } 0124 0125 auto window = Test::renderAndWaitForShown(surface.get(), size.toSize(), Qt::red); 0126 0127 rc.finalGeometry = window->frameGeometry(); 0128 return {rc, std::move(surface)}; 0129 } 0130 0131 void TestPlacement::testPlaceSmart() 0132 { 0133 setPlacementPolicy(PlacementSmart); 0134 0135 std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces; 0136 QRegion usedArea; 0137 0138 for (int i = 0; i < 4; i++) { 0139 auto [windowPlacement, surface] = createAndPlaceWindow(QSize(600, 500)); 0140 // smart placement shouldn't define a size on windows 0141 QCOMPARE(windowPlacement.initiallyConfiguredSize, QSize(0, 0)); 0142 QCOMPARE(windowPlacement.finalGeometry.size(), QSize(600, 500)); 0143 0144 // exact placement isn't a defined concept that should be tested 0145 // but the goal of smart placement is to make sure windows don't overlap until they need to 0146 // 4 windows of 600, 500 should fit without overlap 0147 QVERIFY(!usedArea.intersects(windowPlacement.finalGeometry.toRect())); 0148 usedArea += windowPlacement.finalGeometry.toRect(); 0149 surfaces.push_back(std::move(surface)); 0150 } 0151 } 0152 0153 void TestPlacement::testPlaceMaximized() 0154 { 0155 setPlacementPolicy(PlacementMaximizing); 0156 0157 // add a top panel 0158 std::unique_ptr<KWayland::Client::Surface> panelSurface(Test::createSurface()); 0159 std::unique_ptr<QObject> panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get())); 0160 std::unique_ptr<KWayland::Client::PlasmaShellSurface> plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get())); 0161 plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); 0162 plasmaSurface->setPosition(QPoint(0, 0)); 0163 Test::renderAndWaitForShown(panelSurface.get(), QSize(1280, 20), Qt::blue); 0164 0165 std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces; 0166 0167 // all windows should be initially maximized with an initial configure size sent 0168 for (int i = 0; i < 4; i++) { 0169 auto [windowPlacement, surface] = createAndPlaceWindow(QSize(600, 500)); 0170 QVERIFY(windowPlacement.initiallyConfiguredStates & Test::XdgToplevel::State::Maximized); 0171 QCOMPARE(windowPlacement.initiallyConfiguredSize, QSize(1280, 1024 - 20)); 0172 QCOMPARE(windowPlacement.finalGeometry, QRect(0, 20, 1280, 1024 - 20)); // under the panel 0173 surfaces.push_back(std::move(surface)); 0174 } 0175 } 0176 0177 void TestPlacement::testPlaceMaximizedLeavesFullscreen() 0178 { 0179 setPlacementPolicy(PlacementMaximizing); 0180 0181 // add a top panel 0182 std::unique_ptr<KWayland::Client::Surface> panelSurface(Test::createSurface()); 0183 std::unique_ptr<QObject> panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get())); 0184 std::unique_ptr<KWayland::Client::PlasmaShellSurface> plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get())); 0185 plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); 0186 plasmaSurface->setPosition(QPoint(0, 0)); 0187 Test::renderAndWaitForShown(panelSurface.get(), QSize(1280, 20), Qt::blue); 0188 0189 std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces; 0190 0191 // all windows should be initially fullscreen with an initial configure size sent, despite the policy 0192 for (int i = 0; i < 4; i++) { 0193 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0194 auto shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); 0195 shellSurface->set_fullscreen(nullptr); 0196 QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested); 0197 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0198 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0199 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0200 0201 auto initiallyConfiguredSize = toplevelConfigureRequestedSpy[0][0].toSize(); 0202 auto initiallyConfiguredStates = toplevelConfigureRequestedSpy[0][1].value<Test::XdgToplevel::States>(); 0203 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy[0][0].toUInt()); 0204 0205 auto window = Test::renderAndWaitForShown(surface.get(), initiallyConfiguredSize, Qt::red); 0206 0207 QVERIFY(initiallyConfiguredStates & Test::XdgToplevel::State::Fullscreen); 0208 QCOMPARE(initiallyConfiguredSize, QSize(1280, 1024)); 0209 QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); 0210 0211 surfaces.push_back(std::move(surface)); 0212 } 0213 } 0214 0215 void TestPlacement::testPlaceCentered() 0216 { 0217 // This test verifies that Centered placement policy works. 0218 0219 KConfigGroup group = kwinApp()->config()->group("Windows"); 0220 group.writeEntry("Placement", Placement::policyToString(PlacementCentered)); 0221 group.sync(); 0222 workspace()->slotReconfigure(); 0223 0224 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0225 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0226 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red); 0227 QVERIFY(window); 0228 QCOMPARE(window->frameGeometry(), QRect(590, 487, 100, 50)); 0229 0230 shellSurface.reset(); 0231 QVERIFY(Test::waitForWindowDestroyed(window)); 0232 } 0233 0234 void TestPlacement::testPlaceUnderMouse() 0235 { 0236 // This test verifies that Under Mouse placement policy works. 0237 0238 KConfigGroup group = kwinApp()->config()->group("Windows"); 0239 group.writeEntry("Placement", Placement::policyToString(PlacementUnderMouse)); 0240 group.sync(); 0241 workspace()->slotReconfigure(); 0242 0243 KWin::Cursors::self()->mouse()->setPos(QPoint(200, 300)); 0244 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), QPoint(200, 300)); 0245 0246 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0247 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0248 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red); 0249 QVERIFY(window); 0250 QCOMPARE(window->frameGeometry(), QRect(150, 275, 100, 50)); 0251 0252 shellSurface.reset(); 0253 QVERIFY(Test::waitForWindowDestroyed(window)); 0254 } 0255 0256 void TestPlacement::testPlaceZeroCornered() 0257 { 0258 // This test verifies that the Zero-Cornered placement policy works. 0259 0260 KConfigGroup group = kwinApp()->config()->group("Windows"); 0261 group.writeEntry("Placement", Placement::policyToString(PlacementZeroCornered)); 0262 group.sync(); 0263 workspace()->slotReconfigure(); 0264 0265 std::unique_ptr<KWayland::Client::Surface> surface1(Test::createSurface()); 0266 std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get())); 0267 Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::red); 0268 QVERIFY(window1); 0269 QCOMPARE(window1->pos(), QPoint(0, 0)); 0270 QCOMPARE(window1->size(), QSize(100, 50)); 0271 0272 std::unique_ptr<KWayland::Client::Surface> surface2(Test::createSurface()); 0273 std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get())); 0274 Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); 0275 QVERIFY(window2); 0276 QCOMPARE(window2->pos(), window1->pos() + workspace()->cascadeOffset(window2)); 0277 QCOMPARE(window2->size(), QSize(100, 50)); 0278 0279 std::unique_ptr<KWayland::Client::Surface> surface3(Test::createSurface()); 0280 std::unique_ptr<Test::XdgToplevel> shellSurface3(Test::createXdgToplevelSurface(surface3.get())); 0281 Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::green); 0282 QVERIFY(window3); 0283 QCOMPARE(window3->pos(), window2->pos() + workspace()->cascadeOffset(window3)); 0284 QCOMPARE(window3->size(), QSize(100, 50)); 0285 0286 shellSurface3.reset(); 0287 QVERIFY(Test::waitForWindowDestroyed(window3)); 0288 shellSurface2.reset(); 0289 QVERIFY(Test::waitForWindowDestroyed(window2)); 0290 shellSurface1.reset(); 0291 QVERIFY(Test::waitForWindowDestroyed(window1)); 0292 } 0293 0294 void TestPlacement::testPlaceRandom() 0295 { 0296 // This test verifies that Random placement policy works. 0297 0298 KConfigGroup group = kwinApp()->config()->group("Windows"); 0299 group.writeEntry("Placement", Placement::policyToString(PlacementRandom)); 0300 group.sync(); 0301 workspace()->slotReconfigure(); 0302 0303 std::unique_ptr<KWayland::Client::Surface> surface1(Test::createSurface()); 0304 std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get())); 0305 Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::red); 0306 QVERIFY(window1); 0307 QCOMPARE(window1->size(), QSize(100, 50)); 0308 0309 std::unique_ptr<KWayland::Client::Surface> surface2(Test::createSurface()); 0310 std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get())); 0311 Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); 0312 QVERIFY(window2); 0313 QVERIFY(window2->pos() != window1->pos()); 0314 QCOMPARE(window2->size(), QSize(100, 50)); 0315 0316 std::unique_ptr<KWayland::Client::Surface> surface3(Test::createSurface()); 0317 std::unique_ptr<Test::XdgToplevel> shellSurface3(Test::createXdgToplevelSurface(surface3.get())); 0318 Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::green); 0319 QVERIFY(window3); 0320 QVERIFY(window3->pos() != window1->pos()); 0321 QVERIFY(window3->pos() != window2->pos()); 0322 QCOMPARE(window3->size(), QSize(100, 50)); 0323 0324 shellSurface3.reset(); 0325 QVERIFY(Test::waitForWindowDestroyed(window3)); 0326 shellSurface2.reset(); 0327 QVERIFY(Test::waitForWindowDestroyed(window2)); 0328 shellSurface1.reset(); 0329 QVERIFY(Test::waitForWindowDestroyed(window1)); 0330 } 0331 0332 void TestPlacement::testFullscreen() 0333 { 0334 const QList<Output *> outputs = workspace()->outputs(); 0335 0336 setPlacementPolicy(PlacementSmart); 0337 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0338 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0339 0340 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red); 0341 QVERIFY(window); 0342 window->sendToOutput(outputs[0]); 0343 0344 // Wait for the configure event with the activated state. 0345 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0346 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0347 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0348 0349 window->setFullScreen(true); 0350 0351 QSignalSpy geometryChangedSpy(window, &Window::frameGeometryChanged); 0352 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0353 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0354 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).toSize(), Qt::red); 0355 QVERIFY(geometryChangedSpy.wait()); 0356 QCOMPARE(window->frameGeometry(), outputs[0]->geometry()); 0357 0358 // this doesn't require a round trip, so should be immediate 0359 window->sendToOutput(outputs[1]); 0360 QCOMPARE(window->frameGeometry(), outputs[1]->geometry()); 0361 QCOMPARE(geometryChangedSpy.count(), 2); 0362 } 0363 0364 WAYLANDTEST_MAIN(TestPlacement) 0365 #include "placement_test.moc"