File indexing completed on 2024-11-10 04:56:12
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org> 0006 SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@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 "decorations/decorationbridge.h" 0014 #include "decorations/settings.h" 0015 #include "pointer_input.h" 0016 #include "virtualdesktops.h" 0017 #include "wayland/clientconnection.h" 0018 #include "wayland/display.h" 0019 #include "wayland_server.h" 0020 #include "window.h" 0021 #include "workspace.h" 0022 0023 #include <KDecoration2/DecoratedClient> 0024 #include <KDecoration2/Decoration> 0025 #include <KDecoration2/DecorationSettings> 0026 0027 #include <KWayland/Client/appmenu.h> 0028 #include <KWayland/Client/compositor.h> 0029 #include <KWayland/Client/connection_thread.h> 0030 #include <KWayland/Client/output.h> 0031 #include <KWayland/Client/pointer.h> 0032 #include <KWayland/Client/seat.h> 0033 #include <KWayland/Client/subsurface.h> 0034 #include <KWayland/Client/surface.h> 0035 0036 #include <QDBusConnection> 0037 0038 // system 0039 #include <sys/socket.h> 0040 #include <sys/types.h> 0041 #include <unistd.h> 0042 0043 #include <csignal> 0044 0045 using namespace KWin; 0046 0047 static const QString s_socketName = QStringLiteral("wayland_test_kwin_xdgshellwindow-0"); 0048 0049 class TestXdgShellWindow : public QObject 0050 { 0051 Q_OBJECT 0052 private Q_SLOTS: 0053 void initTestCase(); 0054 void init(); 0055 void cleanup(); 0056 0057 void testMapUnmap(); 0058 void testWindowOutputs(); 0059 void testMinimizeActiveWindow(); 0060 void testFullscreen_data(); 0061 void testFullscreen(); 0062 void testUserCanSetFullscreen(); 0063 void testSendFullScreenWindowToAnotherOutput(); 0064 0065 void testMaximizeHorizontal(); 0066 void testMaximizeVertical(); 0067 void testMaximizeFull(); 0068 void testMaximizedToFullscreen_data(); 0069 void testMaximizedToFullscreen(); 0070 void testSendMaximizedWindowToAnotherOutput(); 0071 void testFullscreenMultipleOutputs(); 0072 void testHidden(); 0073 void testDesktopFileName(); 0074 void testCaptionSimplified(); 0075 void testUnresponsiveWindow_data(); 0076 void testUnresponsiveWindow(); 0077 void testAppMenu(); 0078 void testSendClientWithTransientToDesktop(); 0079 void testMinimizeWindowWithTransients(); 0080 void testXdgDecoration_data(); 0081 void testXdgDecoration(); 0082 void testXdgNeverCommitted(); 0083 void testXdgInitialState(); 0084 void testXdgInitiallyMaximised(); 0085 void testXdgInitiallyFullscreen(); 0086 void testXdgInitiallyMinimized(); 0087 void testXdgWindowGeometryIsntSet(); 0088 void testXdgWindowGeometryAttachBuffer(); 0089 void testXdgWindowGeometryAttachSubSurface(); 0090 void testXdgWindowGeometryInteractiveResize(); 0091 void testXdgWindowGeometryFullScreen(); 0092 void testXdgWindowGeometryMaximize(); 0093 void testXdgPopupReactive_data(); 0094 void testXdgPopupReactive(); 0095 void testXdgPopupReposition(); 0096 void testPointerInputTransform(); 0097 void testReentrantSetFrameGeometry(); 0098 void testDoubleMaximize(); 0099 void testDoubleFullscreenSeparatedByCommit(); 0100 void testMaximizeAndChangeDecorationModeAfterInitialCommit(); 0101 void testFullScreenAndChangeDecorationModeAfterInitialCommit(); 0102 void testChangeDecorationModeAfterInitialCommit(); 0103 }; 0104 0105 void TestXdgShellWindow::testXdgPopupReactive_data() 0106 { 0107 QTest::addColumn<bool>("reactive"); 0108 QTest::addColumn<QPointF>("parentPos"); 0109 QTest::addColumn<QPointF>("popupPos"); 0110 0111 QTest::addRow("reactive") << true << QPointF(0, 1024) << QPointF(50, 1024 - 10); 0112 QTest::addRow("not reactive") << false << QPointF(0, 1024) << QPointF(50, 1024 + 40); 0113 } 0114 0115 void TestXdgShellWindow::testXdgPopupReactive() 0116 { 0117 // This test verifies the behavior of reactive popups. If a popup is not reactive, 0118 // it only has to move together with its parent. If a popup is reactive, it moves 0119 // with its parent and it's reconstrained as needed. 0120 0121 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner()); 0122 positioner->set_size(10, 10); 0123 positioner->set_anchor_rect(10, 10, 10, 10); 0124 positioner->set_anchor_rect(0, 0, 50, 40); 0125 positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right); 0126 positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); 0127 positioner->set_constraint_adjustment(Test::XdgPositioner::constraint_adjustment_slide_y); 0128 0129 QFETCH(bool, reactive); 0130 if (reactive) { 0131 positioner->set_reactive(); 0132 } 0133 0134 std::unique_ptr<KWayland::Client::Surface> rootSurface(Test::createSurface()); 0135 std::unique_ptr<Test::XdgToplevel> root(Test::createXdgToplevelSurface(rootSurface.get())); 0136 auto rootWindow = Test::renderAndWaitForShown(rootSurface.get(), QSize(100, 100), Qt::cyan); 0137 QVERIFY(rootWindow); 0138 0139 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface()); 0140 std::unique_ptr<Test::XdgPopup> popup(Test::createXdgPopupSurface(childSurface.get(), root->xdgSurface(), positioner.get())); 0141 auto childWindow = Test::renderAndWaitForShown(childSurface.get(), QSize(10, 10), Qt::cyan); 0142 QVERIFY(childWindow); 0143 0144 QFETCH(QPointF, parentPos); 0145 QFETCH(QPointF, popupPos); 0146 0147 QSignalSpy popupConfigureRequested(popup.get(), &Test::XdgPopup::configureRequested); 0148 rootWindow->move(parentPos); 0149 QVERIFY(popupConfigureRequested.wait()); 0150 QCOMPARE(popupConfigureRequested.count(), 1); 0151 0152 QCOMPARE(childWindow->pos(), popupPos); 0153 } 0154 0155 void TestXdgShellWindow::testXdgPopupReposition() 0156 { 0157 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner()); 0158 positioner->set_size(10, 10); 0159 positioner->set_anchor_rect(10, 10, 10, 10); 0160 0161 std::unique_ptr<Test::XdgPositioner> otherPositioner(Test::createXdgPositioner()); 0162 otherPositioner->set_size(50, 50); 0163 otherPositioner->set_anchor_rect(10, 10, 10, 10); 0164 0165 std::unique_ptr<KWayland::Client::Surface> rootSurface(Test::createSurface()); 0166 std::unique_ptr<Test::XdgToplevel> root(Test::createXdgToplevelSurface(rootSurface.get())); 0167 auto rootWindow = Test::renderAndWaitForShown(rootSurface.get(), QSize(100, 100), Qt::cyan); 0168 QVERIFY(rootWindow); 0169 0170 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface()); 0171 std::unique_ptr<Test::XdgPopup> popup(Test::createXdgPopupSurface(childSurface.get(), root->xdgSurface(), positioner.get())); 0172 auto childWindow = Test::renderAndWaitForShown(childSurface.get(), QSize(10, 10), Qt::cyan); 0173 QVERIFY(childWindow); 0174 0175 QSignalSpy reconfigureSpy(popup.get(), &Test::XdgPopup::configureRequested); 0176 0177 popup->reposition(otherPositioner->object(), 500000); 0178 0179 QVERIFY(reconfigureSpy.wait()); 0180 QCOMPARE(reconfigureSpy.count(), 1); 0181 } 0182 0183 void TestXdgShellWindow::initTestCase() 0184 { 0185 qRegisterMetaType<KWin::Window *>(); 0186 qRegisterMetaType<KWayland::Client::Output *>(); 0187 0188 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0189 QVERIFY(waylandServer()->init(s_socketName)); 0190 Test::setOutputConfig({ 0191 QRect(0, 0, 1280, 1024), 0192 QRect(1280, 0, 1280, 1024), 0193 }); 0194 0195 kwinApp()->start(); 0196 QVERIFY(applicationStartedSpy.wait()); 0197 const auto outputs = workspace()->outputs(); 0198 QCOMPARE(outputs.count(), 2); 0199 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); 0200 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); 0201 } 0202 0203 void TestXdgShellWindow::init() 0204 { 0205 QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::XdgDecorationV1 | Test::AdditionalWaylandInterface::AppMenu)); 0206 QVERIFY(Test::waitForWaylandPointer()); 0207 0208 workspace()->setActiveOutput(QPoint(640, 512)); 0209 // put mouse in the middle of screen one 0210 KWin::input()->pointer()->warp(QPoint(640, 512)); 0211 } 0212 0213 void TestXdgShellWindow::cleanup() 0214 { 0215 Test::destroyWaylandConnection(); 0216 } 0217 0218 void TestXdgShellWindow::testMapUnmap() 0219 { 0220 // This test verifies that the compositor destroys XdgToplevelWindow when the 0221 // associated xdg_toplevel surface is unmapped. 0222 0223 // Create a wl_surface and an xdg_toplevel, but don't commit them yet! 0224 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0225 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 0226 0227 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); 0228 0229 QSignalSpy configureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0230 0231 // Tell the compositor that we want to map the surface. 0232 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0233 0234 // The compositor will respond with a configure event. 0235 QVERIFY(configureRequestedSpy.wait()); 0236 QCOMPARE(configureRequestedSpy.count(), 1); 0237 0238 // Now we can attach a buffer with actual data to the surface. 0239 Test::render(surface.get(), QSize(100, 50), Qt::blue); 0240 QVERIFY(windowAddedSpy.wait()); 0241 QCOMPARE(windowAddedSpy.count(), 1); 0242 Window *window = windowAddedSpy.last().first().value<Window *>(); 0243 QVERIFY(window); 0244 QCOMPARE(window->readyForPainting(), true); 0245 0246 // When the window becomes active, the compositor will send another configure event. 0247 QVERIFY(configureRequestedSpy.wait()); 0248 QCOMPARE(configureRequestedSpy.count(), 2); 0249 0250 // Unmap the xdg_toplevel surface by committing a null buffer. 0251 surface->attachBuffer(KWayland::Client::Buffer::Ptr()); 0252 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0253 QVERIFY(Test::waitForWindowClosed(window)); 0254 0255 // Tell the compositor that we want to re-map the xdg_toplevel surface. 0256 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0257 0258 // The compositor will respond with a configure event. 0259 QVERIFY(configureRequestedSpy.wait()); 0260 QCOMPARE(configureRequestedSpy.count(), 3); 0261 0262 // Now we can attach a buffer with actual data to the surface. 0263 Test::render(surface.get(), QSize(100, 50), Qt::blue); 0264 QVERIFY(windowAddedSpy.wait()); 0265 QCOMPARE(windowAddedSpy.count(), 2); 0266 window = windowAddedSpy.last().first().value<Window *>(); 0267 QVERIFY(window); 0268 QCOMPARE(window->readyForPainting(), true); 0269 0270 // The compositor will respond with a configure event. 0271 QVERIFY(configureRequestedSpy.wait()); 0272 QCOMPARE(configureRequestedSpy.count(), 4); 0273 0274 // Destroy the test window. 0275 shellSurface.reset(); 0276 QVERIFY(Test::waitForWindowClosed(window)); 0277 } 0278 0279 void TestXdgShellWindow::testWindowOutputs() 0280 { 0281 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0282 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0283 auto size = QSize(200, 200); 0284 0285 QSignalSpy outputEnteredSpy(surface.get(), &KWayland::Client::Surface::outputEntered); 0286 QSignalSpy outputLeftSpy(surface.get(), &KWayland::Client::Surface::outputLeft); 0287 0288 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue); 0289 // assumption: window is initially placed on first screen 0290 QVERIFY(outputEnteredSpy.wait()); 0291 QCOMPARE(outputEnteredSpy.count(), 1); 0292 QCOMPARE(surface->outputs().count(), 1); 0293 QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(0, 0)); 0294 0295 // move to overlapping both first and second screen 0296 window->moveResize(QRect(QPoint(1250, 100), size)); 0297 QVERIFY(outputEnteredSpy.wait()); 0298 QCOMPARE(outputEnteredSpy.count(), 2); 0299 QCOMPARE(outputLeftSpy.count(), 0); 0300 QCOMPARE(surface->outputs().count(), 2); 0301 QVERIFY(surface->outputs()[0] != surface->outputs()[1]); 0302 0303 // move entirely into second screen 0304 window->moveResize(QRect(QPoint(1400, 100), size)); 0305 QVERIFY(outputLeftSpy.wait()); 0306 QCOMPARE(outputEnteredSpy.count(), 2); 0307 QCOMPARE(outputLeftSpy.count(), 1); 0308 QCOMPARE(surface->outputs().count(), 1); 0309 QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(1280, 0)); 0310 } 0311 0312 void TestXdgShellWindow::testMinimizeActiveWindow() 0313 { 0314 // this test verifies that when minimizing the active window it gets deactivated 0315 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0316 std::unique_ptr<QObject> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0317 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0318 QVERIFY(window); 0319 QVERIFY(window->isActive()); 0320 QCOMPARE(workspace()->activeWindow(), window); 0321 QVERIFY(window->wantsInput()); 0322 QVERIFY(window->wantsTabFocus()); 0323 QVERIFY(window->isShown()); 0324 0325 workspace()->slotWindowMinimize(); 0326 QVERIFY(!window->isShown()); 0327 QVERIFY(window->wantsInput()); 0328 QVERIFY(window->wantsTabFocus()); 0329 QVERIFY(!window->isActive()); 0330 QVERIFY(!workspace()->activeWindow()); 0331 QVERIFY(window->isMinimized()); 0332 0333 // unminimize again 0334 window->setMinimized(false); 0335 QVERIFY(!window->isMinimized()); 0336 QVERIFY(!window->isActive()); 0337 QVERIFY(window->wantsInput()); 0338 QVERIFY(window->wantsTabFocus()); 0339 QVERIFY(window->isShown()); 0340 QCOMPARE(workspace()->activeWindow(), nullptr); 0341 } 0342 0343 void TestXdgShellWindow::testFullscreen_data() 0344 { 0345 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("decoMode"); 0346 0347 QTest::newRow("client-side deco") << Test::XdgToplevelDecorationV1::mode_client_side; 0348 QTest::newRow("server-side deco") << Test::XdgToplevelDecorationV1::mode_server_side; 0349 } 0350 0351 void TestXdgShellWindow::testFullscreen() 0352 { 0353 // this test verifies that a window can be properly fullscreened 0354 0355 Test::XdgToplevel::States states; 0356 0357 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0358 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 0359 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); 0360 QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); 0361 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0362 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0363 0364 // Initialize the xdg-toplevel surface. 0365 QFETCH(Test::XdgToplevelDecorationV1::mode, decoMode); 0366 decoration->set_mode(decoMode); 0367 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0368 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0369 0370 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0371 auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 250), Qt::blue); 0372 QVERIFY(window); 0373 QVERIFY(window->isActive()); 0374 QCOMPARE(window->layer(), NormalLayer); 0375 QVERIFY(!window->isFullScreen()); 0376 QCOMPARE(window->clientSize(), QSize(500, 250)); 0377 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); 0378 QCOMPARE(window->clientSizeToFrameSize(window->clientSize()), window->size()); 0379 0380 QSignalSpy fullScreenChangedSpy(window, &Window::fullScreenChanged); 0381 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0382 0383 // Wait for the compositor to send a configure event with the Activated state. 0384 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0385 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 0386 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0387 QVERIFY(states & Test::XdgToplevel::State::Activated); 0388 0389 // Ask the compositor to show the window in full screen mode. 0390 shellSurface->set_fullscreen(nullptr); 0391 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0392 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 0393 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0394 QVERIFY(states & Test::XdgToplevel::State::Fullscreen); 0395 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), window->output()->geometry().size()); 0396 0397 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0398 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red); 0399 0400 QVERIFY(fullScreenChangedSpy.wait()); 0401 QCOMPARE(fullScreenChangedSpy.count(), 1); 0402 QVERIFY(window->isFullScreen()); 0403 QVERIFY(!window->isDecorated()); 0404 QCOMPARE(window->layer(), ActiveLayer); 0405 QCOMPARE(window->frameGeometry(), QRect(QPoint(0, 0), window->output()->geometry().size())); 0406 0407 // Ask the compositor to show the window in normal mode. 0408 shellSurface->unset_fullscreen(); 0409 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0410 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); 0411 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0412 QVERIFY(!(states & Test::XdgToplevel::State::Fullscreen)); 0413 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(500, 250)); 0414 0415 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0416 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::blue); 0417 0418 QVERIFY(fullScreenChangedSpy.wait()); 0419 QCOMPARE(fullScreenChangedSpy.count(), 2); 0420 QCOMPARE(window->clientSize(), QSize(500, 250)); 0421 QVERIFY(!window->isFullScreen()); 0422 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); 0423 QCOMPARE(window->layer(), NormalLayer); 0424 0425 // Destroy the window. 0426 shellSurface.reset(); 0427 QVERIFY(Test::waitForWindowClosed(window)); 0428 } 0429 0430 void TestXdgShellWindow::testUserCanSetFullscreen() 0431 { 0432 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0433 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0434 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0435 QVERIFY(window); 0436 QVERIFY(window->isActive()); 0437 QVERIFY(!window->isFullScreen()); 0438 QVERIFY(window->isFullScreenable()); 0439 } 0440 0441 void TestXdgShellWindow::testSendFullScreenWindowToAnotherOutput() 0442 { 0443 // This test verifies that the fullscreen window will have correct geometry restore 0444 // after it's sent to another output. 0445 0446 const auto outputs = workspace()->outputs(); 0447 0448 // Create the window. 0449 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0450 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0451 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0452 QVERIFY(window); 0453 0454 // Wait for the compositor to send a configure event with the activated state. 0455 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0456 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0457 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0458 0459 // Move the window to the left monitor. 0460 window->move(QPointF(10, 20)); 0461 QCOMPARE(window->frameGeometry(), QRectF(10, 20, 100, 50)); 0462 QCOMPARE(window->output(), outputs[0]); 0463 0464 // Make the window fullscreen. 0465 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0466 shellSurface->set_fullscreen(nullptr); 0467 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0468 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0469 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red); 0470 QVERIFY(frameGeometryChangedSpy.wait()); 0471 QCOMPARE(window->isFullScreen(), true); 0472 QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024)); 0473 QCOMPARE(window->fullscreenGeometryRestore(), QRectF(10, 20, 100, 50)); 0474 QCOMPARE(window->output(), outputs[0]); 0475 0476 // Send the window to another output. 0477 workspace()->sendWindowToOutput(window, outputs[1]); 0478 QCOMPARE(window->isFullScreen(), true); 0479 QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); 0480 QCOMPARE(window->fullscreenGeometryRestore(), QRectF(1280 + 10, 20, 100, 50)); 0481 QCOMPARE(window->output(), outputs[1]); 0482 } 0483 0484 void TestXdgShellWindow::testMaximizedToFullscreen_data() 0485 { 0486 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("decoMode"); 0487 0488 QTest::newRow("client-side deco") << Test::XdgToplevelDecorationV1::mode_client_side; 0489 QTest::newRow("server-side deco") << Test::XdgToplevelDecorationV1::mode_server_side; 0490 } 0491 0492 void TestXdgShellWindow::testMaximizedToFullscreen() 0493 { 0494 // this test verifies that a window can be properly fullscreened after maximizing 0495 0496 Test::XdgToplevel::States states; 0497 0498 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0499 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 0500 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); 0501 QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); 0502 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0503 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0504 0505 // Initialize the xdg-toplevel surface. 0506 QFETCH(Test::XdgToplevelDecorationV1::mode, decoMode); 0507 decoration->set_mode(decoMode); 0508 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0509 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0510 0511 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0512 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0513 QVERIFY(window); 0514 QVERIFY(window->isActive()); 0515 QVERIFY(!window->isFullScreen()); 0516 QCOMPARE(window->clientSize(), QSize(100, 50)); 0517 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); 0518 0519 QSignalSpy fullscreenChangedSpy(window, &Window::fullScreenChanged); 0520 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0521 0522 // Wait for the compositor to send a configure event with the Activated state. 0523 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0524 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 0525 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0526 QVERIFY(states & Test::XdgToplevel::State::Activated); 0527 0528 // Ask the compositor to maximize the window. 0529 shellSurface->set_maximized(); 0530 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0531 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 0532 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0533 QVERIFY(states & Test::XdgToplevel::State::Maximized); 0534 0535 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0536 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red); 0537 QVERIFY(frameGeometryChangedSpy.wait()); 0538 QCOMPARE(window->maximizeMode(), MaximizeFull); 0539 0540 // Ask the compositor to show the window in full screen mode. 0541 shellSurface->set_fullscreen(nullptr); 0542 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0543 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); 0544 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), window->output()->geometry().size()); 0545 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0546 QVERIFY(states & Test::XdgToplevel::State::Maximized); 0547 QVERIFY(states & Test::XdgToplevel::State::Fullscreen); 0548 0549 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0550 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red); 0551 0552 QVERIFY(fullscreenChangedSpy.wait()); 0553 QCOMPARE(fullscreenChangedSpy.count(), 1); 0554 QCOMPARE(window->maximizeMode(), MaximizeFull); 0555 QVERIFY(window->isFullScreen()); 0556 QVERIFY(!window->isDecorated()); 0557 0558 // Switch back to normal mode. 0559 shellSurface->unset_fullscreen(); 0560 shellSurface->unset_maximized(); 0561 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0562 QCOMPARE(surfaceConfigureRequestedSpy.count(), 5); 0563 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(100, 50)); 0564 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0565 QVERIFY(!(states & Test::XdgToplevel::State::Maximized)); 0566 QVERIFY(!(states & Test::XdgToplevel::State::Fullscreen)); 0567 0568 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0569 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red); 0570 0571 QVERIFY(frameGeometryChangedSpy.wait()); 0572 QVERIFY(!window->isFullScreen()); 0573 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); 0574 QCOMPARE(window->maximizeMode(), MaximizeRestore); 0575 0576 // Destroy the window. 0577 shellSurface.reset(); 0578 QVERIFY(Test::waitForWindowClosed(window)); 0579 } 0580 0581 void TestXdgShellWindow::testFullscreenMultipleOutputs() 0582 { 0583 // this test verifies that kwin will place fullscreen windows in the outputs its instructed to 0584 0585 const auto outputs = workspace()->outputs(); 0586 for (KWin::Output *output : outputs) { 0587 Test::XdgToplevel::States states; 0588 0589 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0590 QVERIFY(surface); 0591 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0592 QVERIFY(shellSurface); 0593 0594 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0595 QVERIFY(window); 0596 QVERIFY(window->isActive()); 0597 QVERIFY(!window->isFullScreen()); 0598 QCOMPARE(window->clientSize(), QSize(100, 50)); 0599 QVERIFY(!window->isDecorated()); 0600 0601 QSignalSpy fullscreenChangedSpy(window, &Window::fullScreenChanged); 0602 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0603 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0604 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0605 0606 // Wait for the compositor to send a configure event with the Activated state. 0607 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0608 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 0609 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 0610 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); 0611 0612 // Ask the compositor to show the window in full screen mode. 0613 shellSurface->set_fullscreen(*Test::waylandOutput(output->name())); 0614 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0615 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 0616 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), output->geometry().size()); 0617 0618 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0619 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red); 0620 0621 QVERIFY(!fullscreenChangedSpy.isEmpty() || fullscreenChangedSpy.wait()); 0622 QCOMPARE(fullscreenChangedSpy.count(), 1); 0623 0624 QVERIFY(!frameGeometryChangedSpy.isEmpty() || frameGeometryChangedSpy.wait()); 0625 0626 QVERIFY(window->isFullScreen()); 0627 0628 QCOMPARE(window->frameGeometry(), output->geometry()); 0629 } 0630 } 0631 0632 void TestXdgShellWindow::testHidden() 0633 { 0634 // this test verifies that when hiding window it doesn't get shown 0635 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0636 std::unique_ptr<QObject> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0637 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0638 QVERIFY(window); 0639 QVERIFY(window->isActive()); 0640 QCOMPARE(workspace()->activeWindow(), window); 0641 QVERIFY(window->wantsInput()); 0642 QVERIFY(window->wantsTabFocus()); 0643 QVERIFY(window->isShown()); 0644 0645 window->setHidden(true); 0646 QVERIFY(!window->isShown()); 0647 QVERIFY(!window->isActive()); 0648 QVERIFY(window->wantsInput()); 0649 QVERIFY(window->wantsTabFocus()); 0650 0651 // unhide again 0652 window->setHidden(false); 0653 QVERIFY(window->isShown()); 0654 QVERIFY(window->wantsInput()); 0655 QVERIFY(window->wantsTabFocus()); 0656 0657 // QCOMPARE(workspace()->activeClient(), c); 0658 } 0659 0660 void TestXdgShellWindow::testDesktopFileName() 0661 { 0662 QIcon::setThemeName(QStringLiteral("breeze")); 0663 // this test verifies that desktop file name is passed correctly to the window 0664 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0665 // only xdg-shell as ShellSurface misses the setter 0666 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0667 shellSurface->set_app_id(QStringLiteral("org.kde.foo")); 0668 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0669 QVERIFY(window); 0670 QCOMPARE(window->desktopFileName(), QStringLiteral("org.kde.foo")); 0671 QCOMPARE(window->resourceClass(), QStringLiteral("org.kde.foo")); 0672 QVERIFY(window->resourceName().startsWith("testXdgShellWindow")); 0673 // the desktop file does not exist, so icon should be generic Wayland 0674 QCOMPARE(window->icon().name(), QStringLiteral("wayland")); 0675 0676 QSignalSpy desktopFileNameChangedSpy(window, &Window::desktopFileNameChanged); 0677 QSignalSpy iconChangedSpy(window, &Window::iconChanged); 0678 shellSurface->set_app_id(QStringLiteral("org.kde.bar")); 0679 QVERIFY(desktopFileNameChangedSpy.wait()); 0680 QCOMPARE(window->desktopFileName(), QStringLiteral("org.kde.bar")); 0681 QCOMPARE(window->resourceClass(), QStringLiteral("org.kde.bar")); 0682 QVERIFY(window->resourceName().startsWith("testXdgShellWindow")); 0683 // icon should still be wayland 0684 QCOMPARE(window->icon().name(), QStringLiteral("wayland")); 0685 QVERIFY(iconChangedSpy.isEmpty()); 0686 0687 const QString dfPath = QFINDTESTDATA("data/example.desktop"); 0688 shellSurface->set_app_id(dfPath.toUtf8()); 0689 QVERIFY(desktopFileNameChangedSpy.wait()); 0690 QCOMPARE(iconChangedSpy.count(), 1); 0691 QCOMPARE(window->desktopFileName(), dfPath); 0692 QCOMPARE(window->icon().name(), QStringLiteral("kwin")); 0693 } 0694 0695 void TestXdgShellWindow::testCaptionSimplified() 0696 { 0697 // this test verifies that caption is properly trimmed 0698 // see BUG 323798 comment #12 0699 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0700 // only done for xdg-shell as ShellSurface misses the setter 0701 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0702 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")); 0703 shellSurface->set_title(origTitle); 0704 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0705 QVERIFY(window); 0706 QVERIFY(window->caption() != origTitle); 0707 QCOMPARE(window->caption(), origTitle.simplified()); 0708 } 0709 0710 void TestXdgShellWindow::testUnresponsiveWindow_data() 0711 { 0712 QTest::addColumn<QString>("shellInterface"); // see env selection in qwaylandintegration.cpp 0713 QTest::addColumn<bool>("socketMode"); 0714 0715 QTest::newRow("xdg display") << "xdg-shell" << false; 0716 QTest::newRow("xdg socket") << "xdg-shell" << true; 0717 } 0718 0719 void TestXdgShellWindow::testUnresponsiveWindow() 0720 { 0721 // this test verifies that killWindow properly terminates a process 0722 // for this an external binary is launched 0723 const QString kill = QFINDTESTDATA(QStringLiteral("kill")); 0724 QVERIFY(!kill.isEmpty()); 0725 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); 0726 0727 std::unique_ptr<QProcess> process(new QProcess); 0728 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0729 0730 QFETCH(QString, shellInterface); 0731 QFETCH(bool, socketMode); 0732 env.insert("QT_WAYLAND_SHELL_INTEGRATION", shellInterface); 0733 if (socketMode) { 0734 int sx[2]; 0735 QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0); 0736 waylandServer()->display()->createClient(sx[0]); 0737 int socket = dup(sx[1]); 0738 QVERIFY(socket != -1); 0739 env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); 0740 env.remove("WAYLAND_DISPLAY"); 0741 } else { 0742 env.insert("WAYLAND_DISPLAY", s_socketName); 0743 } 0744 process->setProcessEnvironment(env); 0745 process->setProcessChannelMode(QProcess::ForwardedChannels); 0746 process->setProgram(kill); 0747 QSignalSpy processStartedSpy{process.get(), &QProcess::started}; 0748 process->start(); 0749 QVERIFY(processStartedSpy.wait()); 0750 0751 Window *killWindow = nullptr; 0752 if (windowAddedSpy.isEmpty()) { 0753 QVERIFY(windowAddedSpy.wait()); 0754 } 0755 ::kill(process->processId(), SIGUSR1); // send a signal to freeze the process 0756 0757 killWindow = windowAddedSpy.first().first().value<Window *>(); 0758 QVERIFY(killWindow); 0759 QSignalSpy unresponsiveSpy(killWindow, &Window::unresponsiveChanged); 0760 QSignalSpy killedSpy(process.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished)); 0761 QSignalSpy deletedSpy(killWindow, &QObject::destroyed); 0762 0763 qint64 startTime = QDateTime::currentMSecsSinceEpoch(); 0764 0765 // wait for the process to be frozen 0766 QTest::qWait(10); 0767 0768 // pretend the user clicked the close button 0769 killWindow->closeWindow(); 0770 0771 // window should not yet be marked unresponsive nor killed 0772 QVERIFY(!killWindow->unresponsive()); 0773 QVERIFY(killedSpy.isEmpty()); 0774 0775 QVERIFY(unresponsiveSpy.wait()); 0776 // window should be marked unresponsive but not killed 0777 auto elapsed1 = QDateTime::currentMSecsSinceEpoch() - startTime; 0778 const int timeout = options->killPingTimeout() / 2; // first timeout at half the time is for "unresponsive". 0779 QVERIFY(elapsed1 > timeout - 200 && elapsed1 < timeout + 200); // coarse timers on a test across two processes means we need a fuzzy compare 0780 QVERIFY(killWindow->unresponsive()); 0781 QVERIFY(killedSpy.isEmpty()); 0782 0783 // TODO verify that kill prompt works. 0784 killWindow->killWindow(); 0785 process->kill(); 0786 0787 QVERIFY(killedSpy.wait()); 0788 0789 if (deletedSpy.isEmpty()) { 0790 QVERIFY(deletedSpy.wait()); 0791 } 0792 } 0793 0794 void TestXdgShellWindow::testAppMenu() 0795 { 0796 // register a faux appmenu client 0797 QVERIFY(QDBusConnection::sessionBus().registerService("org.kde.kappmenu")); 0798 0799 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0800 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0801 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0802 QVERIFY(window); 0803 std::unique_ptr<KWayland::Client::AppMenu> menu(Test::waylandAppMenuManager()->create(surface.get())); 0804 QSignalSpy spy(window, &Window::hasApplicationMenuChanged); 0805 menu->setAddress("service.name", "object/path"); 0806 spy.wait(); 0807 QCOMPARE(window->hasApplicationMenu(), true); 0808 QCOMPARE(window->applicationMenuServiceName(), QString("service.name")); 0809 QCOMPARE(window->applicationMenuObjectPath(), QString("object/path")); 0810 0811 QVERIFY(QDBusConnection::sessionBus().unregisterService("org.kde.kappmenu")); 0812 } 0813 0814 void TestXdgShellWindow::testSendClientWithTransientToDesktop() 0815 { 0816 // this test verifies that when sending a window to a desktop all transients are also send to that desktop 0817 0818 VirtualDesktopManager *vds = VirtualDesktopManager::self(); 0819 vds->setCount(2); 0820 const QList<VirtualDesktop *> desktops = vds->desktops(); 0821 0822 std::unique_ptr<KWayland::Client::Surface> surface{Test::createSurface()}; 0823 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0824 0825 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0826 QVERIFY(window); 0827 0828 // let's create a transient window 0829 std::unique_ptr<KWayland::Client::Surface> transientSurface{Test::createSurface()}; 0830 std::unique_ptr<Test::XdgToplevel> transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get())); 0831 transientShellSurface->set_parent(shellSurface->object()); 0832 0833 auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(100, 50), Qt::blue); 0834 QVERIFY(transient); 0835 QCOMPARE(workspace()->activeWindow(), transient); 0836 QCOMPARE(transient->transientFor(), window); 0837 QVERIFY(window->transients().contains(transient)); 0838 0839 // initially, the parent and the transient are on the first virtual desktop 0840 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]}); 0841 QVERIFY(!window->isOnAllDesktops()); 0842 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[0]}); 0843 QVERIFY(!transient->isOnAllDesktops()); 0844 0845 // send the transient to the second virtual desktop 0846 workspace()->slotWindowToDesktop(desktops[1]); 0847 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]}); 0848 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[1]}); 0849 0850 // activate c 0851 workspace()->activateWindow(window); 0852 QCOMPARE(workspace()->activeWindow(), window); 0853 QVERIFY(window->isActive()); 0854 0855 // and send it to the desktop it's already on 0856 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]}); 0857 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[1]}); 0858 workspace()->slotWindowToDesktop(desktops[0]); 0859 0860 // which should move the transient back to the desktop 0861 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]}); 0862 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[0]}); 0863 } 0864 0865 void TestXdgShellWindow::testMinimizeWindowWithTransients() 0866 { 0867 // this test verifies that when minimizing/unminimizing a window all its 0868 // transients will be minimized/unminimized as well 0869 0870 // create the main window 0871 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0872 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0873 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0874 QVERIFY(window); 0875 QVERIFY(!window->isMinimized()); 0876 0877 // create a transient window 0878 std::unique_ptr<KWayland::Client::Surface> transientSurface(Test::createSurface()); 0879 std::unique_ptr<Test::XdgToplevel> transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get())); 0880 transientShellSurface->set_parent(shellSurface->object()); 0881 auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(100, 50), Qt::red); 0882 QVERIFY(transient); 0883 QVERIFY(!transient->isMinimized()); 0884 QCOMPARE(transient->transientFor(), window); 0885 QVERIFY(window->hasTransient(transient, false)); 0886 0887 // minimize the main window, the transient should be minimized as well 0888 window->setMinimized(true); 0889 QVERIFY(window->isMinimized()); 0890 QVERIFY(transient->isMinimized()); 0891 0892 // unminimize the main window, the transient should be unminimized as well 0893 window->setMinimized(false); 0894 QVERIFY(!window->isMinimized()); 0895 QVERIFY(!transient->isMinimized()); 0896 } 0897 0898 void TestXdgShellWindow::testXdgDecoration_data() 0899 { 0900 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("requestedMode"); 0901 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("expectedMode"); 0902 0903 QTest::newRow("client side requested") << Test::XdgToplevelDecorationV1::mode_client_side << Test::XdgToplevelDecorationV1::mode_client_side; 0904 QTest::newRow("server side requested") << Test::XdgToplevelDecorationV1::mode_server_side << Test::XdgToplevelDecorationV1::mode_server_side; 0905 } 0906 0907 void TestXdgShellWindow::testXdgDecoration() 0908 { 0909 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0910 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0911 std::unique_ptr<Test::XdgToplevelDecorationV1> deco(Test::createXdgToplevelDecorationV1(shellSurface.get())); 0912 0913 QSignalSpy decorationConfigureRequestedSpy(deco.get(), &Test::XdgToplevelDecorationV1::configureRequested); 0914 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0915 0916 QFETCH(Test::XdgToplevelDecorationV1::mode, requestedMode); 0917 QFETCH(Test::XdgToplevelDecorationV1::mode, expectedMode); 0918 0919 // request a mode 0920 deco->set_mode(requestedMode); 0921 0922 // kwin will send a configure 0923 QVERIFY(surfaceConfigureRequestedSpy.wait()); 0924 0925 QCOMPARE(decorationConfigureRequestedSpy.count(), 1); 0926 QCOMPARE(decorationConfigureRequestedSpy.last()[0].value<Test::XdgToplevelDecorationV1::mode>(), expectedMode); 0927 QVERIFY(decorationConfigureRequestedSpy.count() > 0); 0928 0929 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); 0930 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0931 QCOMPARE(window->isDecorated(), expectedMode == Test::XdgToplevelDecorationV1::mode_server_side); 0932 } 0933 0934 void TestXdgShellWindow::testXdgNeverCommitted() 0935 { 0936 // check we don't crash if we create a shell object but delete the XdgShellClient before committing it 0937 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0938 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 0939 } 0940 0941 void TestXdgShellWindow::testXdgInitialState() 0942 { 0943 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0944 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 0945 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0946 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0947 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0948 0949 surfaceConfigureRequestedSpy.wait(); 0950 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 0951 0952 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>(); 0953 0954 QCOMPARE(size, QSize(0, 0)); // window should chose it's preferred size 0955 0956 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); 0957 0958 auto window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::blue); 0959 QCOMPARE(window->size(), QSize(200, 100)); 0960 } 0961 0962 void TestXdgShellWindow::testXdgInitiallyMaximised() 0963 { 0964 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0965 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 0966 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0967 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0968 0969 shellSurface->set_maximized(); 0970 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0971 0972 surfaceConfigureRequestedSpy.wait(); 0973 0974 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 0975 0976 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>(); 0977 const auto state = toplevelConfigureRequestedSpy.first()[1].value<Test::XdgToplevel::States>(); 0978 0979 QCOMPARE(size, QSize(1280, 1024)); 0980 QVERIFY(state & Test::XdgToplevel::State::Maximized); 0981 0982 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); 0983 0984 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue); 0985 QCOMPARE(window->maximizeMode(), MaximizeFull); 0986 QCOMPARE(window->size(), QSize(1280, 1024)); 0987 } 0988 0989 void TestXdgShellWindow::testXdgInitiallyFullscreen() 0990 { 0991 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0992 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 0993 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 0994 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0995 0996 shellSurface->set_fullscreen(nullptr); 0997 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0998 0999 surfaceConfigureRequestedSpy.wait(); 1000 1001 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1002 1003 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>(); 1004 const auto state = toplevelConfigureRequestedSpy.first()[1].value<Test::XdgToplevel::States>(); 1005 1006 QCOMPARE(size, QSize(1280, 1024)); 1007 QVERIFY(state & Test::XdgToplevel::State::Fullscreen); 1008 1009 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); 1010 1011 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue); 1012 QCOMPARE(window->isFullScreen(), true); 1013 QCOMPARE(window->size(), QSize(1280, 1024)); 1014 } 1015 1016 void TestXdgShellWindow::testXdgInitiallyMinimized() 1017 { 1018 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1019 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 1020 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1021 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1022 shellSurface->set_minimized(); 1023 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1024 1025 surfaceConfigureRequestedSpy.wait(); 1026 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1027 1028 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>(); 1029 const auto state = toplevelConfigureRequestedSpy.first()[1].value<Test::XdgToplevel::States>(); 1030 1031 QCOMPARE(size, QSize(0, 0)); 1032 QCOMPARE(state, 0); 1033 1034 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); 1035 1036 QEXPECT_FAIL("", "Client created in a minimised state is not exposed to kwin bug 404838", Abort); 1037 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue, QImage::Format_ARGB32, 10); 1038 QVERIFY(window); 1039 QVERIFY(window->isMinimized()); 1040 } 1041 1042 void TestXdgShellWindow::testXdgWindowGeometryIsntSet() 1043 { 1044 // This test verifies that the effective window geometry corresponds to the 1045 // bounding rectangle of the main surface and its sub-surfaces if no window 1046 // geometry is set by the window. 1047 1048 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1049 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1050 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1051 QVERIFY(window); 1052 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1053 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1054 1055 const QPointF oldPosition = window->pos(); 1056 1057 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1058 Test::render(surface.get(), QSize(100, 50), Qt::blue); 1059 QVERIFY(frameGeometryChangedSpy.wait()); 1060 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1061 QCOMPARE(window->frameGeometry().size(), QSize(100, 50)); 1062 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition); 1063 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); 1064 1065 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface()); 1066 std::unique_ptr<KWayland::Client::SubSurface> subSurface(Test::createSubSurface(childSurface.get(), surface.get())); 1067 QVERIFY(subSurface); 1068 subSurface->setPosition(QPoint(-20, -10)); 1069 Test::render(childSurface.get(), QSize(100, 50), Qt::blue); 1070 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1071 QVERIFY(frameGeometryChangedSpy.wait()); 1072 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1073 QCOMPARE(window->frameGeometry().size(), QSize(120, 60)); 1074 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition + QPoint(20, 10)); 1075 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); 1076 } 1077 1078 void TestXdgShellWindow::testXdgWindowGeometryAttachBuffer() 1079 { 1080 // This test verifies that the effective window geometry remains the same when 1081 // a new buffer is attached and xdg_surface.set_window_geometry is not called 1082 // again. Notice that the window geometry must remain the same even if the new 1083 // buffer is smaller. 1084 1085 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1086 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1087 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1088 QVERIFY(window); 1089 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1090 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1091 1092 const QPointF oldPosition = window->pos(); 1093 1094 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1095 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); 1096 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1097 QVERIFY(frameGeometryChangedSpy.wait()); 1098 QCOMPARE(frameGeometryChangedSpy.count(), 1); 1099 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1100 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1101 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); 1102 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1103 1104 Test::render(surface.get(), QSize(100, 50), Qt::blue); 1105 QVERIFY(frameGeometryChangedSpy.wait()); 1106 QCOMPARE(frameGeometryChangedSpy.count(), 2); 1107 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1108 QCOMPARE(window->frameGeometry().size(), QSize(90, 40)); 1109 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); 1110 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); 1111 1112 shellSurface->xdgSurface()->set_window_geometry(0, 0, 100, 50); 1113 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1114 QVERIFY(frameGeometryChangedSpy.wait()); 1115 QCOMPARE(frameGeometryChangedSpy.count(), 3); 1116 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1117 QCOMPARE(window->frameGeometry().size(), QSize(100, 50)); 1118 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition); 1119 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); 1120 1121 shellSurface.reset(); 1122 QVERIFY(Test::waitForWindowClosed(window)); 1123 } 1124 1125 void TestXdgShellWindow::testXdgWindowGeometryAttachSubSurface() 1126 { 1127 // This test verifies that the effective window geometry remains the same 1128 // when a new sub-surface is added and xdg_surface.set_window_geometry is 1129 // not called again. 1130 1131 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1132 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1133 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1134 QVERIFY(window); 1135 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1136 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1137 1138 const QPointF oldPosition = window->pos(); 1139 1140 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1141 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); 1142 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1143 QVERIFY(frameGeometryChangedSpy.wait()); 1144 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1145 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1146 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); 1147 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1148 1149 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface()); 1150 std::unique_ptr<KWayland::Client::SubSurface> subSurface(Test::createSubSurface(childSurface.get(), surface.get())); 1151 QVERIFY(subSurface); 1152 subSurface->setPosition(QPoint(-20, -20)); 1153 Test::render(childSurface.get(), QSize(100, 50), Qt::blue); 1154 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1155 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1156 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1157 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); 1158 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1159 1160 shellSurface->xdgSurface()->set_window_geometry(-15, -15, 50, 40); 1161 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1162 QVERIFY(frameGeometryChangedSpy.wait()); 1163 QCOMPARE(window->frameGeometry().topLeft(), oldPosition); 1164 QCOMPARE(window->frameGeometry().size(), QSize(50, 40)); 1165 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(-15, -15)); 1166 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1167 } 1168 1169 void TestXdgShellWindow::testXdgWindowGeometryInteractiveResize() 1170 { 1171 // This test verifies that correct window geometry is provided along each 1172 // configure event when an xdg-shell is being interactively resized. 1173 1174 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1175 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1176 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1177 QVERIFY(window); 1178 QVERIFY(window->isActive()); 1179 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1180 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1181 1182 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1183 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1184 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1185 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1186 1187 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1188 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); 1189 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1190 QVERIFY(frameGeometryChangedSpy.wait()); 1191 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1192 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1193 1194 QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted); 1195 QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped); 1196 QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished); 1197 1198 // Start interactively resizing the window. 1199 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 1200 workspace()->slotWindowResize(); 1201 QCOMPARE(workspace()->moveResizeWindow(), window); 1202 QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1); 1203 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1204 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 1205 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1206 QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); 1207 1208 // Go right. 1209 QPointF cursorPos = KWin::Cursors::self()->mouse()->pos(); 1210 window->keyPressEvent(Qt::Key_Right); 1211 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 1212 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); 1213 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1214 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 1215 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1216 QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); 1217 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(188, 80)); 1218 shellSurface->xdgSurface()->set_window_geometry(10, 10, 188, 80); 1219 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1220 Test::render(surface.get(), QSize(208, 100), Qt::blue); 1221 QVERIFY(frameGeometryChangedSpy.wait()); 1222 QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); 1223 QCOMPARE(window->bufferGeometry().size(), QSize(208, 100)); 1224 QCOMPARE(window->frameGeometry().size(), QSize(188, 80)); 1225 1226 // Go down. 1227 cursorPos = KWin::Cursors::self()->mouse()->pos(); 1228 window->keyPressEvent(Qt::Key_Down); 1229 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 1230 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(0, 8)); 1231 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1232 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); 1233 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1234 QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); 1235 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(188, 88)); 1236 shellSurface->xdgSurface()->set_window_geometry(10, 10, 188, 88); 1237 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1238 Test::render(surface.get(), QSize(208, 108), Qt::blue); 1239 QVERIFY(frameGeometryChangedSpy.wait()); 1240 QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2); 1241 QCOMPARE(window->bufferGeometry().size(), QSize(208, 108)); 1242 QCOMPARE(window->frameGeometry().size(), QSize(188, 88)); 1243 1244 // Finish resizing the window. 1245 window->keyPressEvent(Qt::Key_Enter); 1246 QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1); 1247 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 1248 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1249 QCOMPARE(surfaceConfigureRequestedSpy.count(), 5); 1250 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1251 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); 1252 1253 shellSurface.reset(); 1254 QVERIFY(Test::waitForWindowClosed(window)); 1255 } 1256 1257 void TestXdgShellWindow::testXdgWindowGeometryFullScreen() 1258 { 1259 // This test verifies that an xdg-shell receives correct window geometry when 1260 // its fullscreen state gets changed. 1261 1262 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1263 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1264 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1265 QVERIFY(window); 1266 QVERIFY(window->isActive()); 1267 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1268 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1269 1270 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1271 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1272 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1273 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1274 1275 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1276 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); 1277 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1278 QVERIFY(frameGeometryChangedSpy.wait()); 1279 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1280 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1281 1282 workspace()->slotWindowFullScreen(); 1283 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1284 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 1285 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); 1286 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1287 QVERIFY(states.testFlag(Test::XdgToplevel::State::Fullscreen)); 1288 shellSurface->xdgSurface()->set_window_geometry(0, 0, 1280, 1024); 1289 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1290 Test::render(surface.get(), QSize(1280, 1024), Qt::blue); 1291 QVERIFY(frameGeometryChangedSpy.wait()); 1292 QCOMPARE(window->bufferGeometry().size(), QSize(1280, 1024)); 1293 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); 1294 1295 workspace()->slotWindowFullScreen(); 1296 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1297 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 1298 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(180, 80)); 1299 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1300 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Fullscreen)); 1301 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); 1302 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1303 Test::render(surface.get(), QSize(200, 100), Qt::blue); 1304 QVERIFY(frameGeometryChangedSpy.wait()); 1305 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1306 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1307 1308 shellSurface.reset(); 1309 QVERIFY(Test::waitForWindowClosed(window)); 1310 } 1311 1312 void TestXdgShellWindow::testXdgWindowGeometryMaximize() 1313 { 1314 // This test verifies that an xdg-shell receives correct window geometry when 1315 // its maximized state gets changed. 1316 1317 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1318 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1319 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1320 QVERIFY(window); 1321 QVERIFY(window->isActive()); 1322 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1323 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1324 1325 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1326 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1327 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1328 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1329 1330 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1331 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); 1332 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1333 QVERIFY(frameGeometryChangedSpy.wait()); 1334 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1335 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1336 1337 workspace()->slotWindowMaximize(); 1338 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1339 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 1340 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); 1341 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1342 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); 1343 shellSurface->xdgSurface()->set_window_geometry(0, 0, 1280, 1024); 1344 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1345 Test::render(surface.get(), QSize(1280, 1024), Qt::blue); 1346 QVERIFY(frameGeometryChangedSpy.wait()); 1347 QCOMPARE(window->bufferGeometry().size(), QSize(1280, 1024)); 1348 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); 1349 1350 workspace()->slotWindowMaximize(); 1351 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1352 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 1353 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(180, 80)); 1354 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1355 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1356 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); 1357 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1358 Test::render(surface.get(), QSize(200, 100), Qt::blue); 1359 QVERIFY(frameGeometryChangedSpy.wait()); 1360 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1361 QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); 1362 1363 shellSurface.reset(); 1364 QVERIFY(Test::waitForWindowClosed(window)); 1365 } 1366 1367 void TestXdgShellWindow::testPointerInputTransform() 1368 { 1369 // This test verifies that XdgToplevelWindow provides correct input transform matrix. 1370 // The input transform matrix is used by seat to map pointer events from the global 1371 // screen coordinates to the surface-local coordinates. 1372 1373 // Get a wl_pointer object on the client side. 1374 std::unique_ptr<KWayland::Client::Pointer> pointer(Test::waylandSeat()->createPointer()); 1375 QVERIFY(pointer); 1376 QVERIFY(pointer->isValid()); 1377 QSignalSpy pointerEnteredSpy(pointer.get(), &KWayland::Client::Pointer::entered); 1378 QSignalSpy pointerMotionSpy(pointer.get(), &KWayland::Client::Pointer::motion); 1379 1380 // Create an xdg_toplevel surface and wait for the compositor to catch up. 1381 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1382 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1383 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1384 QVERIFY(window); 1385 QVERIFY(window->isActive()); 1386 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); 1387 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1388 1389 // Enter the surface. 1390 quint32 timestamp = 0; 1391 Test::pointerMotion(window->pos(), timestamp++); 1392 QVERIFY(pointerEnteredSpy.wait()); 1393 1394 // Move the pointer to (10, 5) relative to the upper left frame corner, which is located 1395 // at (0, 0) in the surface-local coordinates. 1396 Test::pointerMotion(window->pos() + QPointF(10, 5), timestamp++); 1397 QVERIFY(pointerMotionSpy.wait()); 1398 QCOMPARE(pointerMotionSpy.last().first().toPointF(), QPointF(10, 5)); 1399 1400 // Let's pretend that the window has changed the extents of the client-side drop-shadow 1401 // but the frame geometry didn't change. 1402 QSignalSpy bufferGeometryChangedSpy(window, &Window::bufferGeometryChanged); 1403 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1404 shellSurface->xdgSurface()->set_window_geometry(10, 20, 200, 100); 1405 Test::render(surface.get(), QSize(220, 140), Qt::blue); 1406 QVERIFY(bufferGeometryChangedSpy.wait()); 1407 QCOMPARE(frameGeometryChangedSpy.count(), 0); 1408 QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); 1409 QCOMPARE(window->bufferGeometry().size(), QSize(220, 140)); 1410 1411 // Move the pointer to (20, 50) relative to the upper left frame corner, which is located 1412 // at (10, 20) in the surface-local coordinates. 1413 Test::pointerMotion(window->pos() + QPointF(20, 50), timestamp++); 1414 QVERIFY(pointerMotionSpy.wait()); 1415 QCOMPARE(pointerMotionSpy.last().first().toPointF(), QPointF(10, 20) + QPointF(20, 50)); 1416 1417 // Destroy the xdg-toplevel surface. 1418 shellSurface.reset(); 1419 QVERIFY(Test::waitForWindowClosed(window)); 1420 } 1421 1422 void TestXdgShellWindow::testReentrantSetFrameGeometry() 1423 { 1424 // This test verifies that calling moveResize() from a slot connected directly 1425 // to the frameGeometryChanged() signal won't cause an infinite recursion. 1426 1427 // Create an xdg-toplevel surface and wait for the compositor to catch up. 1428 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1429 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1430 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); 1431 QVERIFY(window); 1432 QCOMPARE(window->pos(), QPoint(0, 0)); 1433 1434 // Let's pretend that there is a script that really wants the window to be at (100, 100). 1435 connect(window, &Window::frameGeometryChanged, this, [window]() { 1436 window->moveResize(QRectF(QPointF(100, 100), window->size())); 1437 }); 1438 1439 // Trigger the lambda above. 1440 window->move(QPoint(40, 50)); 1441 1442 // Eventually, the window will end up at (100, 100). 1443 QCOMPARE(window->pos(), QPoint(100, 100)); 1444 1445 // Destroy the xdg-toplevel surface. 1446 shellSurface.reset(); 1447 QVERIFY(Test::waitForWindowClosed(window)); 1448 } 1449 1450 void TestXdgShellWindow::testDoubleMaximize() 1451 { 1452 // This test verifies that the case where a window issues two set_maximized() requests 1453 // separated by the initial commit is handled properly. 1454 1455 // Create the test surface. 1456 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1457 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1458 shellSurface->set_maximized(); 1459 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1460 1461 // Wait for the compositor to respond with a configure event. 1462 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1463 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1464 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1465 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1466 1467 QSize size = toplevelConfigureRequestedSpy.last().at(0).toSize(); 1468 QCOMPARE(size, QSize(1280, 1024)); 1469 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1470 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); 1471 1472 // Send another set_maximized() request, but do not attach any buffer yet. 1473 shellSurface->set_maximized(); 1474 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1475 1476 // The compositor must respond with another configure event even if the state hasn't changed. 1477 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1478 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 1479 size = toplevelConfigureRequestedSpy.last().at(0).toSize(); 1480 QCOMPARE(size, QSize(1280, 1024)); 1481 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1482 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); 1483 } 1484 1485 void TestXdgShellWindow::testDoubleFullscreenSeparatedByCommit() 1486 { 1487 // Some applications do weird things at startup and this is one of them. This test verifies 1488 // that the window will have good frame geometry if the window has issued several 1489 // xdg_toplevel.set_fullscreen requests and they are separated by a surface commit with 1490 // no attached buffer. 1491 1492 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1493 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1494 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1495 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1496 1497 // Tell the compositor that we want the window to be shown in fullscreen mode. 1498 shellSurface->set_fullscreen(nullptr); 1499 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1500 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024)); 1501 QVERIFY(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>() & Test::XdgToplevel::State::Fullscreen); 1502 1503 // Ask again. 1504 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1505 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1506 shellSurface->set_fullscreen(nullptr); 1507 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1508 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024)); 1509 QVERIFY(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>() & Test::XdgToplevel::State::Fullscreen); 1510 1511 // Map the window. 1512 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1513 auto window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::blue); 1514 QVERIFY(window->isFullScreen()); 1515 QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); 1516 } 1517 1518 void TestXdgShellWindow::testMaximizeHorizontal() 1519 { 1520 // Create the test window. 1521 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1522 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 1523 1524 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1525 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1526 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1527 1528 // Wait for the initial configure event. 1529 Test::XdgToplevel::States states; 1530 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1531 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1532 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); 1533 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1534 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); 1535 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1536 1537 // Map the window. 1538 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1539 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue); 1540 QVERIFY(window); 1541 QVERIFY(window->isActive()); 1542 QVERIFY(window->isMaximizable()); 1543 QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); 1544 QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); 1545 QCOMPARE(window->size(), QSize(800, 600)); 1546 1547 // We should receive a configure event when the window becomes active. 1548 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1549 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 1550 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1551 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); 1552 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1553 1554 // Maximize the test window in horizontal direction. 1555 workspace()->slotWindowMaximizeHorizontal(); 1556 QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal); 1557 QCOMPARE(window->maximizeMode(), MaximizeRestore); 1558 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1559 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 1560 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 600)); 1561 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1562 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1563 1564 // Draw contents of the maximized window. 1565 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1566 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1567 Test::render(surface.get(), QSize(1280, 600), Qt::blue); 1568 QVERIFY(frameGeometryChangedSpy.wait()); 1569 QCOMPARE(window->size(), QSize(1280, 600)); 1570 QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal); 1571 QCOMPARE(window->maximizeMode(), MaximizeHorizontal); 1572 1573 // Restore the window. 1574 workspace()->slotWindowMaximizeHorizontal(); 1575 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); 1576 QCOMPARE(window->maximizeMode(), MaximizeHorizontal); 1577 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1578 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); 1579 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600)); 1580 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1581 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1582 1583 // Draw contents of the restored window. 1584 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1585 Test::render(surface.get(), QSize(800, 600), Qt::blue); 1586 QVERIFY(frameGeometryChangedSpy.wait()); 1587 QCOMPARE(window->size(), QSize(800, 600)); 1588 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); 1589 QCOMPARE(window->maximizeMode(), MaximizeRestore); 1590 1591 // Destroy the window. 1592 shellSurface.reset(); 1593 surface.reset(); 1594 QVERIFY(Test::waitForWindowClosed(window)); 1595 } 1596 1597 void TestXdgShellWindow::testMaximizeVertical() 1598 { 1599 // Create the test window. 1600 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1601 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 1602 1603 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1604 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1605 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1606 1607 // Wait for the initial configure event. 1608 Test::XdgToplevel::States states; 1609 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1610 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1611 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); 1612 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1613 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); 1614 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1615 1616 // Map the window. 1617 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1618 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue); 1619 QVERIFY(window); 1620 QVERIFY(window->isActive()); 1621 QVERIFY(window->isMaximizable()); 1622 QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); 1623 QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); 1624 QCOMPARE(window->size(), QSize(800, 600)); 1625 1626 // We should receive a configure event when the window becomes active. 1627 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1628 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 1629 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1630 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); 1631 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1632 1633 // Maximize the test window in vertical direction. 1634 workspace()->slotWindowMaximizeVertical(); 1635 QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical); 1636 QCOMPARE(window->maximizeMode(), MaximizeRestore); 1637 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1638 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 1639 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 1024)); 1640 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1641 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1642 1643 // Draw contents of the maximized window. 1644 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1645 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1646 Test::render(surface.get(), QSize(800, 1024), Qt::blue); 1647 QVERIFY(frameGeometryChangedSpy.wait()); 1648 QCOMPARE(window->size(), QSize(800, 1024)); 1649 QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical); 1650 QCOMPARE(window->maximizeMode(), MaximizeVertical); 1651 1652 // Restore the window. 1653 workspace()->slotWindowMaximizeVertical(); 1654 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); 1655 QCOMPARE(window->maximizeMode(), MaximizeVertical); 1656 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1657 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); 1658 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600)); 1659 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1660 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1661 1662 // Draw contents of the restored window. 1663 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1664 Test::render(surface.get(), QSize(800, 600), Qt::blue); 1665 QVERIFY(frameGeometryChangedSpy.wait()); 1666 QCOMPARE(window->size(), QSize(800, 600)); 1667 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); 1668 QCOMPARE(window->maximizeMode(), MaximizeRestore); 1669 1670 // Destroy the window. 1671 shellSurface.reset(); 1672 surface.reset(); 1673 QVERIFY(Test::waitForWindowClosed(window)); 1674 } 1675 1676 void TestXdgShellWindow::testMaximizeFull() 1677 { 1678 // Create the test window. 1679 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1680 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 1681 1682 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1683 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1684 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1685 1686 // Wait for the initial configure event. 1687 Test::XdgToplevel::States states; 1688 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1689 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); 1690 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); 1691 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1692 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); 1693 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1694 1695 // Map the window. 1696 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1697 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue); 1698 QVERIFY(window); 1699 QVERIFY(window->isActive()); 1700 QVERIFY(window->isMaximizable()); 1701 QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); 1702 QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); 1703 QCOMPARE(window->size(), QSize(800, 600)); 1704 1705 // We should receive a configure event when the window becomes active. 1706 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1707 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); 1708 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1709 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); 1710 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1711 1712 // Maximize the test window. 1713 workspace()->slotWindowMaximize(); 1714 QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); 1715 QCOMPARE(window->maximizeMode(), MaximizeRestore); 1716 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1717 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); 1718 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); 1719 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1720 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); 1721 1722 // Draw contents of the maximized window. 1723 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1724 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1725 Test::render(surface.get(), QSize(1280, 1024), Qt::blue); 1726 QVERIFY(frameGeometryChangedSpy.wait()); 1727 QCOMPARE(window->size(), QSize(1280, 1024)); 1728 QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); 1729 QCOMPARE(window->maximizeMode(), MaximizeFull); 1730 1731 // Restore the window. 1732 workspace()->slotWindowMaximize(); 1733 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); 1734 QCOMPARE(window->maximizeMode(), MaximizeFull); 1735 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1736 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); 1737 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600)); 1738 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(); 1739 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); 1740 1741 // Draw contents of the restored window. 1742 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1743 Test::render(surface.get(), QSize(800, 600), Qt::blue); 1744 QVERIFY(frameGeometryChangedSpy.wait()); 1745 QCOMPARE(window->size(), QSize(800, 600)); 1746 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); 1747 QCOMPARE(window->maximizeMode(), MaximizeRestore); 1748 1749 // Destroy the window. 1750 shellSurface.reset(); 1751 surface.reset(); 1752 QVERIFY(Test::waitForWindowClosed(window)); 1753 } 1754 1755 void TestXdgShellWindow::testSendMaximizedWindowToAnotherOutput() 1756 { 1757 // This test verifies that the maximized window will have correct geometry restore 1758 // after it's sent to another output. 1759 1760 const auto outputs = workspace()->outputs(); 1761 1762 // Create the window. 1763 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1764 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 1765 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 1766 QVERIFY(window); 1767 1768 // Wait for the compositor to send a configure event with the activated state. 1769 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1770 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1771 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1772 1773 // Move the window to the left monitor. 1774 window->move(QPointF(10, 20)); 1775 QCOMPARE(window->frameGeometry(), QRectF(10, 20, 100, 50)); 1776 QCOMPARE(window->output(), outputs[0]); 1777 1778 // Make the window maximized. 1779 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 1780 shellSurface->set_maximized(); 1781 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1782 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 1783 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red); 1784 QVERIFY(frameGeometryChangedSpy.wait()); 1785 QCOMPARE(window->maximizeMode(), MaximizeFull); 1786 QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024)); 1787 QCOMPARE(window->geometryRestore(), QRectF(10, 20, 100, 50)); 1788 QCOMPARE(window->output(), outputs[0]); 1789 1790 // Send the window to another output. 1791 workspace()->sendWindowToOutput(window, outputs[1]); 1792 QCOMPARE(window->maximizeMode(), MaximizeFull); 1793 QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); 1794 QCOMPARE(window->geometryRestore(), QRectF(1280 + 10, 20, 100, 50)); 1795 QCOMPARE(window->output(), outputs[1]); 1796 } 1797 1798 void TestXdgShellWindow::testMaximizeAndChangeDecorationModeAfterInitialCommit() 1799 { 1800 // Ideally, the app would initialize the xdg-toplevel surface before the initial commit, but 1801 // many don't do it. They initialize the surface after the first commit. 1802 // This test verifies that the window will receive a configure event with correct size 1803 // if an xdg-toplevel surface is set maximized and decoration mode changes after initial commit. 1804 1805 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1806 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 1807 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); 1808 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1809 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1810 1811 // Commit the initial state. 1812 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1813 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1814 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0)); 1815 1816 // Request maximized mode and set decoration mode, i.e. perform late initialization. 1817 shellSurface->set_maximized(); 1818 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side); 1819 1820 // The compositor will respond with a new configure event, which should contain maximized state. 1821 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1822 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024)); 1823 QCOMPARE(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(), Test::XdgToplevel::State::Maximized); 1824 } 1825 1826 void TestXdgShellWindow::testFullScreenAndChangeDecorationModeAfterInitialCommit() 1827 { 1828 // Ideally, the app would initialize the xdg-toplevel surface before the initial commit, but 1829 // many don't do it. They initialize the surface after the first commit. 1830 // This test verifies that the window will receive a configure event with correct size 1831 // if an xdg-toplevel surface is set fullscreen and decoration mode changes after initial commit. 1832 1833 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1834 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 1835 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); 1836 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1837 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1838 1839 // Commit the initial state. 1840 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1841 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1842 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0)); 1843 1844 // Request fullscreen mode and set decoration mode, i.e. perform late initialization. 1845 shellSurface->set_fullscreen(nullptr); 1846 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side); 1847 1848 // The compositor will respond with a new configure event, which should contain fullscreen state. 1849 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1850 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024)); 1851 QCOMPARE(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(), Test::XdgToplevel::State::Fullscreen); 1852 } 1853 1854 void TestXdgShellWindow::testChangeDecorationModeAfterInitialCommit() 1855 { 1856 // This test verifies that the compositor will respond with a good configure event when 1857 // the decoration mode changes after the first surface commit but before the surface is mapped. 1858 1859 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 1860 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); 1861 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); 1862 QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); 1863 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); 1864 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 1865 1866 // Perform the initial commit. 1867 surface->commit(KWayland::Client::Surface::CommitFlag::None); 1868 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1869 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0)); 1870 QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value<Test::XdgToplevelDecorationV1::mode>(), Test::XdgToplevelDecorationV1::mode_server_side); 1871 1872 // Change decoration mode. 1873 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side); 1874 1875 // The configure event should still have 0x0 size. 1876 QVERIFY(surfaceConfigureRequestedSpy.wait()); 1877 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0)); 1878 QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value<Test::XdgToplevelDecorationV1::mode>(), Test::XdgToplevelDecorationV1::mode_client_side); 1879 } 1880 1881 WAYLANDTEST_MAIN(TestXdgShellWindow) 1882 #include "xdgshellwindow_test.moc"