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