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