KWin
Loading...
Searching...
No Matches
xdgshellwindow_test.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6 SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10#include "kwin_wayland_test.h"
11
12#include "core/output.h"
15#include "pointer_input.h"
16#include "virtualdesktops.h"
18#include "wayland/display.h"
19#include "wayland_server.h"
20#include "window.h"
21#include "workspace.h"
22
23#include <KDecoration2/DecoratedClient>
24#include <KDecoration2/Decoration>
25#include <KDecoration2/DecorationSettings>
26
27#include <KWayland/Client/appmenu.h>
28#include <KWayland/Client/compositor.h>
29#include <KWayland/Client/connection_thread.h>
30#include <KWayland/Client/output.h>
31#include <KWayland/Client/pointer.h>
32#include <KWayland/Client/seat.h>
33#include <KWayland/Client/subsurface.h>
34#include <KWayland/Client/surface.h>
35
36#include <QDBusConnection>
37
38// system
39#include <sys/socket.h>
40#include <sys/types.h>
41#include <unistd.h>
42
43#include <csignal>
44
45using namespace KWin;
46
47static const QString s_socketName = QStringLiteral("wayland_test_kwin_xdgshellwindow-0");
48
49class TestXdgShellWindow : public QObject
50{
51 Q_OBJECT
52private Q_SLOTS:
53 void initTestCase();
54 void init();
55 void cleanup();
56
57 void testMapUnmap();
58 void testWindowOutputs();
59 void testMinimizeActiveWindow();
60 void testFullscreen_data();
61 void testFullscreen();
62 void testUserCanSetFullscreen();
63 void testSendFullScreenWindowToAnotherOutput();
64
65 void testMaximizeHorizontal();
66 void testMaximizeVertical();
67 void testMaximizeFull();
68 void testMaximizedToFullscreen_data();
69 void testMaximizedToFullscreen();
70 void testSendMaximizedWindowToAnotherOutput();
71 void testFullscreenMultipleOutputs();
72 void testHidden();
73 void testDesktopFileName();
74 void testCaptionSimplified();
75 void testUnresponsiveWindow_data();
76 void testUnresponsiveWindow();
77 void testAppMenu();
78 void testSendClientWithTransientToDesktop();
79 void testMinimizeWindowWithTransients();
80 void testXdgDecoration_data();
81 void testXdgDecoration();
82 void testXdgNeverCommitted();
83 void testXdgInitialState();
84 void testXdgInitiallyMaximised();
85 void testXdgInitiallyFullscreen();
86 void testXdgInitiallyMinimized();
87 void testXdgWindowGeometryIsntSet();
88 void testXdgWindowGeometryAttachBuffer();
89 void testXdgWindowGeometryAttachSubSurface();
90 void testXdgWindowGeometryInteractiveResize();
91 void testXdgWindowGeometryFullScreen();
92 void testXdgWindowGeometryMaximize();
93 void testXdgPopupReactive_data();
94 void testXdgPopupReactive();
95 void testXdgPopupReposition();
96 void testPointerInputTransform();
97 void testReentrantSetFrameGeometry();
98 void testDoubleMaximize();
99 void testDoubleFullscreenSeparatedByCommit();
100 void testMaximizeAndChangeDecorationModeAfterInitialCommit();
101 void testFullScreenAndChangeDecorationModeAfterInitialCommit();
102 void testChangeDecorationModeAfterInitialCommit();
103};
104
105void TestXdgShellWindow::testXdgPopupReactive_data()
106{
107 QTest::addColumn<bool>("reactive");
108 QTest::addColumn<QPointF>("parentPos");
109 QTest::addColumn<QPointF>("popupPos");
110
111 QTest::addRow("reactive") << true << QPointF(0, 1024) << QPointF(50, 1024 - 10);
112 QTest::addRow("not reactive") << false << QPointF(0, 1024) << QPointF(50, 1024 + 40);
113}
114
115void TestXdgShellWindow::testXdgPopupReactive()
116{
117 // This test verifies the behavior of reactive popups. If a popup is not reactive,
118 // it only has to move together with its parent. If a popup is reactive, it moves
119 // with its parent and it's reconstrained as needed.
120
121 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
122 positioner->set_size(10, 10);
123 positioner->set_anchor_rect(10, 10, 10, 10);
124 positioner->set_anchor_rect(0, 0, 50, 40);
125 positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right);
126 positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right);
127 positioner->set_constraint_adjustment(Test::XdgPositioner::constraint_adjustment_slide_y);
128
129 QFETCH(bool, reactive);
130 if (reactive) {
131 positioner->set_reactive();
132 }
133
134 std::unique_ptr<KWayland::Client::Surface> rootSurface(Test::createSurface());
135 std::unique_ptr<Test::XdgToplevel> root(Test::createXdgToplevelSurface(rootSurface.get()));
136 auto rootWindow = Test::renderAndWaitForShown(rootSurface.get(), QSize(100, 100), Qt::cyan);
137 QVERIFY(rootWindow);
138
139 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface());
140 std::unique_ptr<Test::XdgPopup> popup(Test::createXdgPopupSurface(childSurface.get(), root->xdgSurface(), positioner.get()));
141 auto childWindow = Test::renderAndWaitForShown(childSurface.get(), QSize(10, 10), Qt::cyan);
142 QVERIFY(childWindow);
143
144 QFETCH(QPointF, parentPos);
145 QFETCH(QPointF, popupPos);
146
147 QSignalSpy popupConfigureRequested(popup.get(), &Test::XdgPopup::configureRequested);
148 rootWindow->move(parentPos);
149 QVERIFY(popupConfigureRequested.wait());
150 QCOMPARE(popupConfigureRequested.count(), 1);
151
152 QCOMPARE(childWindow->pos(), popupPos);
153}
154
155void TestXdgShellWindow::testXdgPopupReposition()
156{
157 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
158 positioner->set_size(10, 10);
159 positioner->set_anchor_rect(10, 10, 10, 10);
160
161 std::unique_ptr<Test::XdgPositioner> otherPositioner(Test::createXdgPositioner());
162 otherPositioner->set_size(50, 50);
163 otherPositioner->set_anchor_rect(10, 10, 10, 10);
164
165 std::unique_ptr<KWayland::Client::Surface> rootSurface(Test::createSurface());
166 std::unique_ptr<Test::XdgToplevel> root(Test::createXdgToplevelSurface(rootSurface.get()));
167 auto rootWindow = Test::renderAndWaitForShown(rootSurface.get(), QSize(100, 100), Qt::cyan);
168 QVERIFY(rootWindow);
169
170 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface());
171 std::unique_ptr<Test::XdgPopup> popup(Test::createXdgPopupSurface(childSurface.get(), root->xdgSurface(), positioner.get()));
172 auto childWindow = Test::renderAndWaitForShown(childSurface.get(), QSize(10, 10), Qt::cyan);
173 QVERIFY(childWindow);
174
175 QSignalSpy reconfigureSpy(popup.get(), &Test::XdgPopup::configureRequested);
176
177 popup->reposition(otherPositioner->object(), 500000);
178
179 QVERIFY(reconfigureSpy.wait());
180 QCOMPARE(reconfigureSpy.count(), 1);
181}
182
183void TestXdgShellWindow::initTestCase()
184{
185 qRegisterMetaType<KWin::Window *>();
186 qRegisterMetaType<KWayland::Client::Output *>();
187
188 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
189 QVERIFY(waylandServer()->init(s_socketName));
191 QRect(0, 0, 1280, 1024),
192 QRect(1280, 0, 1280, 1024),
193 });
194
195 kwinApp()->start();
196 QVERIFY(applicationStartedSpy.wait());
197 const auto outputs = workspace()->outputs();
198 QCOMPARE(outputs.count(), 2);
199 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
200 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
201}
202
203void TestXdgShellWindow::init()
204{
205 QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::XdgDecorationV1 | Test::AdditionalWaylandInterface::AppMenu));
207
208 workspace()->setActiveOutput(QPoint(640, 512));
209 // put mouse in the middle of screen one
210 KWin::input()->pointer()->warp(QPoint(640, 512));
211}
212
213void TestXdgShellWindow::cleanup()
214{
216}
217
218void TestXdgShellWindow::testMapUnmap()
219{
220 // This test verifies that the compositor destroys XdgToplevelWindow when the
221 // associated xdg_toplevel surface is unmapped.
222
223 // Create a wl_surface and an xdg_toplevel, but don't commit them yet!
224 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
225 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
226
227 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
228
229 QSignalSpy configureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
230
231 // Tell the compositor that we want to map the surface.
232 surface->commit(KWayland::Client::Surface::CommitFlag::None);
233
234 // The compositor will respond with a configure event.
235 QVERIFY(configureRequestedSpy.wait());
236 QCOMPARE(configureRequestedSpy.count(), 1);
237
238 // Now we can attach a buffer with actual data to the surface.
239 Test::render(surface.get(), QSize(100, 50), Qt::blue);
240 QVERIFY(windowAddedSpy.wait());
241 QCOMPARE(windowAddedSpy.count(), 1);
242 Window *window = windowAddedSpy.last().first().value<Window *>();
243 QVERIFY(window);
244 QCOMPARE(window->readyForPainting(), true);
245
246 // When the window becomes active, the compositor will send another configure event.
247 QVERIFY(configureRequestedSpy.wait());
248 QCOMPARE(configureRequestedSpy.count(), 2);
249
250 // Unmap the xdg_toplevel surface by committing a null buffer.
251 surface->attachBuffer(KWayland::Client::Buffer::Ptr());
252 surface->commit(KWayland::Client::Surface::CommitFlag::None);
253 QVERIFY(Test::waitForWindowClosed(window));
254
255 // Tell the compositor that we want to re-map the xdg_toplevel surface.
256 surface->commit(KWayland::Client::Surface::CommitFlag::None);
257
258 // The compositor will respond with a configure event.
259 QVERIFY(configureRequestedSpy.wait());
260 QCOMPARE(configureRequestedSpy.count(), 3);
261
262 // Now we can attach a buffer with actual data to the surface.
263 Test::render(surface.get(), QSize(100, 50), Qt::blue);
264 QVERIFY(windowAddedSpy.wait());
265 QCOMPARE(windowAddedSpy.count(), 2);
266 window = windowAddedSpy.last().first().value<Window *>();
267 QVERIFY(window);
268 QCOMPARE(window->readyForPainting(), true);
269
270 // The compositor will respond with a configure event.
271 QVERIFY(configureRequestedSpy.wait());
272 QCOMPARE(configureRequestedSpy.count(), 4);
273
274 // Destroy the test window.
275 shellSurface.reset();
276 QVERIFY(Test::waitForWindowClosed(window));
277}
278
279void TestXdgShellWindow::testWindowOutputs()
280{
281 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
282 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
283 auto size = QSize(200, 200);
284
285 QSignalSpy outputEnteredSpy(surface.get(), &KWayland::Client::Surface::outputEntered);
286 QSignalSpy outputLeftSpy(surface.get(), &KWayland::Client::Surface::outputLeft);
287
288 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue);
289 // assumption: window is initially placed on first screen
290 QVERIFY(outputEnteredSpy.wait());
291 QCOMPARE(outputEnteredSpy.count(), 1);
292 QCOMPARE(surface->outputs().count(), 1);
293 QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(0, 0));
294
295 // move to overlapping both first and second screen
296 window->moveResize(QRect(QPoint(1250, 100), size));
297 QVERIFY(outputEnteredSpy.wait());
298 QCOMPARE(outputEnteredSpy.count(), 2);
299 QCOMPARE(outputLeftSpy.count(), 0);
300 QCOMPARE(surface->outputs().count(), 2);
301 QVERIFY(surface->outputs()[0] != surface->outputs()[1]);
302
303 // move entirely into second screen
304 window->moveResize(QRect(QPoint(1400, 100), size));
305 QVERIFY(outputLeftSpy.wait());
306 QCOMPARE(outputEnteredSpy.count(), 2);
307 QCOMPARE(outputLeftSpy.count(), 1);
308 QCOMPARE(surface->outputs().count(), 1);
309 QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(1280, 0));
310}
311
312void TestXdgShellWindow::testMinimizeActiveWindow()
313{
314 // this test verifies that when minimizing the active window it gets deactivated
315 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
316 std::unique_ptr<QObject> shellSurface(Test::createXdgToplevelSurface(surface.get()));
317 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
318 QVERIFY(window);
319 QVERIFY(window->isActive());
320 QCOMPARE(workspace()->activeWindow(), window);
321 QVERIFY(window->wantsInput());
322 QVERIFY(window->wantsTabFocus());
323 QVERIFY(window->isShown());
324
326 QVERIFY(!window->isShown());
327 QVERIFY(window->wantsInput());
328 QVERIFY(window->wantsTabFocus());
329 QVERIFY(!window->isActive());
330 QVERIFY(!workspace()->activeWindow());
331 QVERIFY(window->isMinimized());
332
333 // unminimize again
334 window->setMinimized(false);
335 QVERIFY(!window->isMinimized());
336 QVERIFY(!window->isActive());
337 QVERIFY(window->wantsInput());
338 QVERIFY(window->wantsTabFocus());
339 QVERIFY(window->isShown());
340 QCOMPARE(workspace()->activeWindow(), nullptr);
341}
342
343void TestXdgShellWindow::testFullscreen_data()
344{
345 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("decoMode");
346
347 QTest::newRow("client-side deco") << Test::XdgToplevelDecorationV1::mode_client_side;
348 QTest::newRow("server-side deco") << Test::XdgToplevelDecorationV1::mode_server_side;
349}
350
351void TestXdgShellWindow::testFullscreen()
352{
353 // this test verifies that a window can be properly fullscreened
354
355 Test::XdgToplevel::States states;
356
357 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
358 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
359 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
360 QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested);
361 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
362 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
363
364 // Initialize the xdg-toplevel surface.
365 QFETCH(Test::XdgToplevelDecorationV1::mode, decoMode);
366 decoration->set_mode(decoMode);
367 surface->commit(KWayland::Client::Surface::CommitFlag::None);
368 QVERIFY(surfaceConfigureRequestedSpy.wait());
369
370 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
371 auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 250), Qt::blue);
372 QVERIFY(window);
373 QVERIFY(window->isActive());
374 QCOMPARE(window->layer(), NormalLayer);
375 QVERIFY(!window->isFullScreen());
376 QCOMPARE(window->clientSize(), QSize(500, 250));
377 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side);
378 QCOMPARE(window->clientSizeToFrameSize(window->clientSize()), window->size());
379
380 QSignalSpy fullScreenChangedSpy(window, &Window::fullScreenChanged);
381 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
382
383 // Wait for the compositor to send a configure event with the Activated state.
384 QVERIFY(surfaceConfigureRequestedSpy.wait());
385 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
386 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
387 QVERIFY(states & Test::XdgToplevel::State::Activated);
388
389 // Ask the compositor to show the window in full screen mode.
390 shellSurface->set_fullscreen(nullptr);
391 QVERIFY(surfaceConfigureRequestedSpy.wait());
392 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
393 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
394 QVERIFY(states & Test::XdgToplevel::State::Fullscreen);
395 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), window->output()->geometry().size());
396
397 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
398 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red);
399
400 QVERIFY(fullScreenChangedSpy.wait());
401 QCOMPARE(fullScreenChangedSpy.count(), 1);
402 QVERIFY(window->isFullScreen());
403 QVERIFY(!window->isDecorated());
404 QCOMPARE(window->layer(), ActiveLayer);
405 QCOMPARE(window->frameGeometry(), QRect(QPoint(0, 0), window->output()->geometry().size()));
406
407 // Ask the compositor to show the window in normal mode.
408 shellSurface->unset_fullscreen();
409 QVERIFY(surfaceConfigureRequestedSpy.wait());
410 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4);
411 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
412 QVERIFY(!(states & Test::XdgToplevel::State::Fullscreen));
413 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(500, 250));
414
415 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
416 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::blue);
417
418 QVERIFY(fullScreenChangedSpy.wait());
419 QCOMPARE(fullScreenChangedSpy.count(), 2);
420 QCOMPARE(window->clientSize(), QSize(500, 250));
421 QVERIFY(!window->isFullScreen());
422 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side);
423 QCOMPARE(window->layer(), NormalLayer);
424
425 // Destroy the window.
426 shellSurface.reset();
427 QVERIFY(Test::waitForWindowClosed(window));
428}
429
430void TestXdgShellWindow::testUserCanSetFullscreen()
431{
432 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
433 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
434 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
435 QVERIFY(window);
436 QVERIFY(window->isActive());
437 QVERIFY(!window->isFullScreen());
438 QVERIFY(window->isFullScreenable());
439}
440
441void TestXdgShellWindow::testSendFullScreenWindowToAnotherOutput()
442{
443 // This test verifies that the fullscreen window will have correct geometry restore
444 // after it's sent to another output.
445
446 const auto outputs = workspace()->outputs();
447
448 // Create the window.
449 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
450 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
451 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
452 QVERIFY(window);
453
454 // Wait for the compositor to send a configure event with the activated state.
455 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
456 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
457 QVERIFY(surfaceConfigureRequestedSpy.wait());
458
459 // Move the window to the left monitor.
460 window->move(QPointF(10, 20));
461 QCOMPARE(window->frameGeometry(), QRectF(10, 20, 100, 50));
462 QCOMPARE(window->output(), outputs[0]);
463
464 // Make the window fullscreen.
465 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
466 shellSurface->set_fullscreen(nullptr);
467 QVERIFY(surfaceConfigureRequestedSpy.wait());
468 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
469 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red);
470 QVERIFY(frameGeometryChangedSpy.wait());
471 QCOMPARE(window->isFullScreen(), true);
472 QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024));
473 QCOMPARE(window->fullscreenGeometryRestore(), QRectF(10, 20, 100, 50));
474 QCOMPARE(window->output(), outputs[0]);
475
476 // Send the window to another output.
477 workspace()->sendWindowToOutput(window, outputs[1]);
478 QCOMPARE(window->isFullScreen(), true);
479 QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
480 QCOMPARE(window->fullscreenGeometryRestore(), QRectF(1280 + 10, 20, 100, 50));
481 QCOMPARE(window->output(), outputs[1]);
482}
483
484void TestXdgShellWindow::testMaximizedToFullscreen_data()
485{
486 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("decoMode");
487
488 QTest::newRow("client-side deco") << Test::XdgToplevelDecorationV1::mode_client_side;
489 QTest::newRow("server-side deco") << Test::XdgToplevelDecorationV1::mode_server_side;
490}
491
492void TestXdgShellWindow::testMaximizedToFullscreen()
493{
494 // this test verifies that a window can be properly fullscreened after maximizing
495
496 Test::XdgToplevel::States states;
497
498 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
499 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
500 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
501 QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested);
502 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
503 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
504
505 // Initialize the xdg-toplevel surface.
506 QFETCH(Test::XdgToplevelDecorationV1::mode, decoMode);
507 decoration->set_mode(decoMode);
508 surface->commit(KWayland::Client::Surface::CommitFlag::None);
509 QVERIFY(surfaceConfigureRequestedSpy.wait());
510
511 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
512 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
513 QVERIFY(window);
514 QVERIFY(window->isActive());
515 QVERIFY(!window->isFullScreen());
516 QCOMPARE(window->clientSize(), QSize(100, 50));
517 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side);
518
519 QSignalSpy fullscreenChangedSpy(window, &Window::fullScreenChanged);
520 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
521
522 // Wait for the compositor to send a configure event with the Activated state.
523 QVERIFY(surfaceConfigureRequestedSpy.wait());
524 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
525 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
526 QVERIFY(states & Test::XdgToplevel::State::Activated);
527
528 // Ask the compositor to maximize the window.
529 shellSurface->set_maximized();
530 QVERIFY(surfaceConfigureRequestedSpy.wait());
531 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
532 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
533 QVERIFY(states & Test::XdgToplevel::State::Maximized);
534
535 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
536 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red);
537 QVERIFY(frameGeometryChangedSpy.wait());
538 QCOMPARE(window->maximizeMode(), MaximizeFull);
539
540 // Ask the compositor to show the window in full screen mode.
541 shellSurface->set_fullscreen(nullptr);
542 QVERIFY(surfaceConfigureRequestedSpy.wait());
543 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4);
544 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), window->output()->geometry().size());
545 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
546 QVERIFY(states & Test::XdgToplevel::State::Maximized);
547 QVERIFY(states & Test::XdgToplevel::State::Fullscreen);
548
549 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
550 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red);
551
552 QVERIFY(fullscreenChangedSpy.wait());
553 QCOMPARE(fullscreenChangedSpy.count(), 1);
554 QCOMPARE(window->maximizeMode(), MaximizeFull);
555 QVERIFY(window->isFullScreen());
556 QVERIFY(!window->isDecorated());
557
558 // Switch back to normal mode.
559 shellSurface->unset_fullscreen();
560 shellSurface->unset_maximized();
561 QVERIFY(surfaceConfigureRequestedSpy.wait());
562 QCOMPARE(surfaceConfigureRequestedSpy.count(), 5);
563 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(100, 50));
564 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
565 QVERIFY(!(states & Test::XdgToplevel::State::Maximized));
566 QVERIFY(!(states & Test::XdgToplevel::State::Fullscreen));
567
568 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
569 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red);
570
571 QVERIFY(frameGeometryChangedSpy.wait());
572 QVERIFY(!window->isFullScreen());
573 QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side);
574 QCOMPARE(window->maximizeMode(), MaximizeRestore);
575
576 // Destroy the window.
577 shellSurface.reset();
578 QVERIFY(Test::waitForWindowClosed(window));
579}
580
581void TestXdgShellWindow::testFullscreenMultipleOutputs()
582{
583 // this test verifies that kwin will place fullscreen windows in the outputs its instructed to
584
585 const auto outputs = workspace()->outputs();
586 for (KWin::Output *output : outputs) {
587 Test::XdgToplevel::States states;
588
589 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
590 QVERIFY(surface);
591 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
592 QVERIFY(shellSurface);
593
594 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
595 QVERIFY(window);
596 QVERIFY(window->isActive());
597 QVERIFY(!window->isFullScreen());
598 QCOMPARE(window->clientSize(), QSize(100, 50));
599 QVERIFY(!window->isDecorated());
600
601 QSignalSpy fullscreenChangedSpy(window, &Window::fullScreenChanged);
602 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
603 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
604 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
605
606 // Wait for the compositor to send a configure event with the Activated state.
607 QVERIFY(surfaceConfigureRequestedSpy.wait());
608 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
609 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
610 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
611
612 // Ask the compositor to show the window in full screen mode.
613 shellSurface->set_fullscreen(*Test::waylandOutput(output->name()));
614 QVERIFY(surfaceConfigureRequestedSpy.wait());
615 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
616 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), output->geometry().size());
617
618 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
619 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red);
620
621 QVERIFY(!fullscreenChangedSpy.isEmpty() || fullscreenChangedSpy.wait());
622 QCOMPARE(fullscreenChangedSpy.count(), 1);
623
624 QVERIFY(!frameGeometryChangedSpy.isEmpty() || frameGeometryChangedSpy.wait());
625
626 QVERIFY(window->isFullScreen());
627
628 QCOMPARE(window->frameGeometry(), output->geometry());
629 }
630}
631
632void TestXdgShellWindow::testHidden()
633{
634 // this test verifies that when hiding window it doesn't get shown
635 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
636 std::unique_ptr<QObject> shellSurface(Test::createXdgToplevelSurface(surface.get()));
637 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
638 QVERIFY(window);
639 QVERIFY(window->isActive());
640 QCOMPARE(workspace()->activeWindow(), window);
641 QVERIFY(window->wantsInput());
642 QVERIFY(window->wantsTabFocus());
643 QVERIFY(window->isShown());
644
645 window->setHidden(true);
646 QVERIFY(!window->isShown());
647 QVERIFY(!window->isActive());
648 QVERIFY(window->wantsInput());
649 QVERIFY(window->wantsTabFocus());
650
651 // unhide again
652 window->setHidden(false);
653 QVERIFY(window->isShown());
654 QVERIFY(window->wantsInput());
655 QVERIFY(window->wantsTabFocus());
656
657 // QCOMPARE(workspace()->activeClient(), c);
658}
659
660void TestXdgShellWindow::testDesktopFileName()
661{
662 QIcon::setThemeName(QStringLiteral("breeze"));
663 // this test verifies that desktop file name is passed correctly to the window
664 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
665 // only xdg-shell as ShellSurface misses the setter
666 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
667 shellSurface->set_app_id(QStringLiteral("org.kde.foo"));
668 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
669 QVERIFY(window);
670 QCOMPARE(window->desktopFileName(), QStringLiteral("org.kde.foo"));
671 QCOMPARE(window->resourceClass(), QStringLiteral("org.kde.foo"));
672 QVERIFY(window->resourceName().startsWith("testXdgShellWindow"));
673 // the desktop file does not exist, so icon should be generic Wayland
674 QCOMPARE(window->icon().name(), QStringLiteral("wayland"));
675
676 QSignalSpy desktopFileNameChangedSpy(window, &Window::desktopFileNameChanged);
677 QSignalSpy iconChangedSpy(window, &Window::iconChanged);
678 shellSurface->set_app_id(QStringLiteral("org.kde.bar"));
679 QVERIFY(desktopFileNameChangedSpy.wait());
680 QCOMPARE(window->desktopFileName(), QStringLiteral("org.kde.bar"));
681 QCOMPARE(window->resourceClass(), QStringLiteral("org.kde.bar"));
682 QVERIFY(window->resourceName().startsWith("testXdgShellWindow"));
683 // icon should still be wayland
684 QCOMPARE(window->icon().name(), QStringLiteral("wayland"));
685 QVERIFY(iconChangedSpy.isEmpty());
686
687 const QString dfPath = QFINDTESTDATA("data/example.desktop");
688 shellSurface->set_app_id(dfPath.toUtf8());
689 QVERIFY(desktopFileNameChangedSpy.wait());
690 QCOMPARE(iconChangedSpy.count(), 1);
691 QCOMPARE(window->desktopFileName(), dfPath);
692 QCOMPARE(window->icon().name(), QStringLiteral("kwin"));
693}
694
695void TestXdgShellWindow::testCaptionSimplified()
696{
697 // this test verifies that caption is properly trimmed
698 // see BUG 323798 comment #12
699 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
700 // only done for xdg-shell as ShellSurface misses the setter
701 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
702 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"));
703 shellSurface->set_title(origTitle);
704 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
705 QVERIFY(window);
706 QVERIFY(window->caption() != origTitle);
707 QCOMPARE(window->caption(), origTitle.simplified());
708}
709
710void TestXdgShellWindow::testUnresponsiveWindow_data()
711{
712 QTest::addColumn<QString>("shellInterface"); // see env selection in qwaylandintegration.cpp
713 QTest::addColumn<bool>("socketMode");
714
715 QTest::newRow("xdg display") << "xdg-shell" << false;
716 QTest::newRow("xdg socket") << "xdg-shell" << true;
717}
718
719void TestXdgShellWindow::testUnresponsiveWindow()
720{
721 // this test verifies that killWindow properly terminates a process
722 // for this an external binary is launched
723 const QString kill = QFINDTESTDATA(QStringLiteral("kill"));
724 QVERIFY(!kill.isEmpty());
725 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
726
727 std::unique_ptr<QProcess> process(new QProcess);
728 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
729
730 QFETCH(QString, shellInterface);
731 QFETCH(bool, socketMode);
732 env.insert("QT_WAYLAND_SHELL_INTEGRATION", shellInterface);
733 if (socketMode) {
734 int sx[2];
735 QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0);
737 int socket = dup(sx[1]);
738 QVERIFY(socket != -1);
739 env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
740 env.remove("WAYLAND_DISPLAY");
741 } else {
742 env.insert("WAYLAND_DISPLAY", s_socketName);
743 }
744 process->setProcessEnvironment(env);
745 process->setProcessChannelMode(QProcess::ForwardedChannels);
746 process->setProgram(kill);
747 QSignalSpy processStartedSpy{process.get(), &QProcess::started};
748 process->start();
749 QVERIFY(processStartedSpy.wait());
750
751 Window *killWindow = nullptr;
752 if (windowAddedSpy.isEmpty()) {
753 QVERIFY(windowAddedSpy.wait());
754 }
755 ::kill(process->processId(), SIGUSR1); // send a signal to freeze the process
756
757 killWindow = windowAddedSpy.first().first().value<Window *>();
758 QVERIFY(killWindow);
759 QSignalSpy unresponsiveSpy(killWindow, &Window::unresponsiveChanged);
760 QSignalSpy killedSpy(process.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished));
761 QSignalSpy deletedSpy(killWindow, &QObject::destroyed);
762
763 qint64 startTime = QDateTime::currentMSecsSinceEpoch();
764
765 // wait for the process to be frozen
766 QTest::qWait(10);
767
768 // pretend the user clicked the close button
769 killWindow->closeWindow();
770
771 // window should not yet be marked unresponsive nor killed
772 QVERIFY(!killWindow->unresponsive());
773 QVERIFY(killedSpy.isEmpty());
774
775 QVERIFY(unresponsiveSpy.wait());
776 // window should be marked unresponsive but not killed
777 auto elapsed1 = QDateTime::currentMSecsSinceEpoch() - startTime;
778 const int timeout = options->killPingTimeout() / 2; // first timeout at half the time is for "unresponsive".
779 QVERIFY(elapsed1 > timeout - 200 && elapsed1 < timeout + 200); // coarse timers on a test across two processes means we need a fuzzy compare
780 QVERIFY(killWindow->unresponsive());
781 QVERIFY(killedSpy.isEmpty());
782
783 // TODO verify that kill prompt works.
784 killWindow->killWindow();
785 process->kill();
786
787 QVERIFY(killedSpy.wait());
788
789 if (deletedSpy.isEmpty()) {
790 QVERIFY(deletedSpy.wait());
791 }
792}
793
794void TestXdgShellWindow::testAppMenu()
795{
796 // register a faux appmenu client
797 QVERIFY(QDBusConnection::sessionBus().registerService("org.kde.kappmenu"));
798
799 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
800 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
801 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
802 QVERIFY(window);
803 std::unique_ptr<KWayland::Client::AppMenu> menu(Test::waylandAppMenuManager()->create(surface.get()));
804 QSignalSpy spy(window, &Window::hasApplicationMenuChanged);
805 menu->setAddress("service.name", "object/path");
806 spy.wait();
807 QCOMPARE(window->hasApplicationMenu(), true);
808 QCOMPARE(window->applicationMenuServiceName(), QString("service.name"));
809 QCOMPARE(window->applicationMenuObjectPath(), QString("object/path"));
810
811 QVERIFY(QDBusConnection::sessionBus().unregisterService("org.kde.kappmenu"));
812}
813
814void TestXdgShellWindow::testSendClientWithTransientToDesktop()
815{
816 // this test verifies that when sending a window to a desktop all transients are also send to that desktop
817
818 VirtualDesktopManager *vds = VirtualDesktopManager::self();
819 vds->setCount(2);
820 const QList<VirtualDesktop *> desktops = vds->desktops();
821
822 std::unique_ptr<KWayland::Client::Surface> surface{Test::createSurface()};
823 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
824
825 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
826 QVERIFY(window);
827
828 // let's create a transient window
829 std::unique_ptr<KWayland::Client::Surface> transientSurface{Test::createSurface()};
830 std::unique_ptr<Test::XdgToplevel> transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get()));
831 transientShellSurface->set_parent(shellSurface->object());
832
833 auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(100, 50), Qt::blue);
834 QVERIFY(transient);
835 QCOMPARE(workspace()->activeWindow(), transient);
836 QCOMPARE(transient->transientFor(), window);
837 QVERIFY(window->transients().contains(transient));
838
839 // initially, the parent and the transient are on the first virtual desktop
840 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]});
841 QVERIFY(!window->isOnAllDesktops());
842 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[0]});
843 QVERIFY(!transient->isOnAllDesktops());
844
845 // send the transient to the second virtual desktop
846 workspace()->slotWindowToDesktop(desktops[1]);
847 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]});
848 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[1]});
849
850 // activate c
851 workspace()->activateWindow(window);
852 QCOMPARE(workspace()->activeWindow(), window);
853 QVERIFY(window->isActive());
854
855 // and send it to the desktop it's already on
856 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]});
857 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[1]});
858 workspace()->slotWindowToDesktop(desktops[0]);
859
860 // which should move the transient back to the desktop
861 QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]});
862 QCOMPARE(transient->desktops(), QList<VirtualDesktop *>{desktops[0]});
863}
864
865void TestXdgShellWindow::testMinimizeWindowWithTransients()
866{
867 // this test verifies that when minimizing/unminimizing a window all its
868 // transients will be minimized/unminimized as well
869
870 // create the main window
871 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
872 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
873 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
874 QVERIFY(window);
875 QVERIFY(!window->isMinimized());
876
877 // create a transient window
878 std::unique_ptr<KWayland::Client::Surface> transientSurface(Test::createSurface());
879 std::unique_ptr<Test::XdgToplevel> transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get()));
880 transientShellSurface->set_parent(shellSurface->object());
881 auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(100, 50), Qt::red);
882 QVERIFY(transient);
883 QVERIFY(!transient->isMinimized());
884 QCOMPARE(transient->transientFor(), window);
885 QVERIFY(window->hasTransient(transient, false));
886
887 // minimize the main window, the transient should be minimized as well
888 window->setMinimized(true);
889 QVERIFY(window->isMinimized());
890 QVERIFY(transient->isMinimized());
891
892 // unminimize the main window, the transient should be unminimized as well
893 window->setMinimized(false);
894 QVERIFY(!window->isMinimized());
895 QVERIFY(!transient->isMinimized());
896}
897
898void TestXdgShellWindow::testXdgDecoration_data()
899{
900 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("requestedMode");
901 QTest::addColumn<Test::XdgToplevelDecorationV1::mode>("expectedMode");
902
903 QTest::newRow("client side requested") << Test::XdgToplevelDecorationV1::mode_client_side << Test::XdgToplevelDecorationV1::mode_client_side;
904 QTest::newRow("server side requested") << Test::XdgToplevelDecorationV1::mode_server_side << Test::XdgToplevelDecorationV1::mode_server_side;
905}
906
907void TestXdgShellWindow::testXdgDecoration()
908{
909 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
910 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
911 std::unique_ptr<Test::XdgToplevelDecorationV1> deco(Test::createXdgToplevelDecorationV1(shellSurface.get()));
912
913 QSignalSpy decorationConfigureRequestedSpy(deco.get(), &Test::XdgToplevelDecorationV1::configureRequested);
914 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
915
916 QFETCH(Test::XdgToplevelDecorationV1::mode, requestedMode);
917 QFETCH(Test::XdgToplevelDecorationV1::mode, expectedMode);
918
919 // request a mode
920 deco->set_mode(requestedMode);
921
922 // kwin will send a configure
923 QVERIFY(surfaceConfigureRequestedSpy.wait());
924
925 QCOMPARE(decorationConfigureRequestedSpy.count(), 1);
926 QCOMPARE(decorationConfigureRequestedSpy.last()[0].value<Test::XdgToplevelDecorationV1::mode>(), expectedMode);
927 QVERIFY(decorationConfigureRequestedSpy.count() > 0);
928
929 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt());
930 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
931 QCOMPARE(window->isDecorated(), expectedMode == Test::XdgToplevelDecorationV1::mode_server_side);
932}
933
934void TestXdgShellWindow::testXdgNeverCommitted()
935{
936 // check we don't crash if we create a shell object but delete the XdgShellClient before committing it
937 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
938 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
939}
940
941void TestXdgShellWindow::testXdgInitialState()
942{
943 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
944 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
945 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
946 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
947 surface->commit(KWayland::Client::Surface::CommitFlag::None);
948
949 surfaceConfigureRequestedSpy.wait();
950 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
951
952 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>();
953
954 QCOMPARE(size, QSize(0, 0)); // window should chose it's preferred size
955
956 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt());
957
958 auto window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::blue);
959 QCOMPARE(window->size(), QSize(200, 100));
960}
961
962void TestXdgShellWindow::testXdgInitiallyMaximised()
963{
964 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
965 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
966 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
967 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
968
969 shellSurface->set_maximized();
970 surface->commit(KWayland::Client::Surface::CommitFlag::None);
971
972 surfaceConfigureRequestedSpy.wait();
973
974 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
975
976 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>();
977 const auto state = toplevelConfigureRequestedSpy.first()[1].value<Test::XdgToplevel::States>();
978
979 QCOMPARE(size, QSize(1280, 1024));
980 QVERIFY(state & Test::XdgToplevel::State::Maximized);
981
982 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt());
983
984 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue);
985 QCOMPARE(window->maximizeMode(), MaximizeFull);
986 QCOMPARE(window->size(), QSize(1280, 1024));
987}
988
989void TestXdgShellWindow::testXdgInitiallyFullscreen()
990{
991 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
992 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
993 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
994 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
995
996 shellSurface->set_fullscreen(nullptr);
997 surface->commit(KWayland::Client::Surface::CommitFlag::None);
998
999 surfaceConfigureRequestedSpy.wait();
1000
1001 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1002
1003 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>();
1004 const auto state = toplevelConfigureRequestedSpy.first()[1].value<Test::XdgToplevel::States>();
1005
1006 QCOMPARE(size, QSize(1280, 1024));
1007 QVERIFY(state & Test::XdgToplevel::State::Fullscreen);
1008
1009 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt());
1010
1011 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue);
1012 QCOMPARE(window->isFullScreen(), true);
1013 QCOMPARE(window->size(), QSize(1280, 1024));
1014}
1015
1016void TestXdgShellWindow::testXdgInitiallyMinimized()
1017{
1018 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1019 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1020 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1021 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1022 shellSurface->set_minimized();
1023 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1024
1025 surfaceConfigureRequestedSpy.wait();
1026 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1027
1028 const auto size = toplevelConfigureRequestedSpy.first()[0].value<QSize>();
1029 const auto state = toplevelConfigureRequestedSpy.first()[1].value<Test::XdgToplevel::States>();
1030
1031 QCOMPARE(size, QSize(0, 0));
1032 QCOMPARE(state, 0);
1033
1034 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt());
1035
1036 QEXPECT_FAIL("", "Client created in a minimised state is not exposed to kwin bug 404838", Abort);
1037 auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue, QImage::Format_ARGB32, 10);
1038 QVERIFY(window);
1039 QVERIFY(window->isMinimized());
1040}
1041
1042void TestXdgShellWindow::testXdgWindowGeometryIsntSet()
1043{
1044 // This test verifies that the effective window geometry corresponds to the
1045 // bounding rectangle of the main surface and its sub-surfaces if no window
1046 // geometry is set by the window.
1047
1048 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1049 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1050 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1051 QVERIFY(window);
1052 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1053 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1054
1055 const QPointF oldPosition = window->pos();
1056
1057 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1058 Test::render(surface.get(), QSize(100, 50), Qt::blue);
1059 QVERIFY(frameGeometryChangedSpy.wait());
1060 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1061 QCOMPARE(window->frameGeometry().size(), QSize(100, 50));
1062 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition);
1063 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50));
1064
1065 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface());
1066 std::unique_ptr<KWayland::Client::SubSurface> subSurface(Test::createSubSurface(childSurface.get(), surface.get()));
1067 QVERIFY(subSurface);
1068 subSurface->setPosition(QPoint(-20, -10));
1069 Test::render(childSurface.get(), QSize(100, 50), Qt::blue);
1070 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1071 QVERIFY(frameGeometryChangedSpy.wait());
1072 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1073 QCOMPARE(window->frameGeometry().size(), QSize(120, 60));
1074 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition + QPoint(20, 10));
1075 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50));
1076}
1077
1078void TestXdgShellWindow::testXdgWindowGeometryAttachBuffer()
1079{
1080 // This test verifies that the effective window geometry remains the same when
1081 // a new buffer is attached and xdg_surface.set_window_geometry is not called
1082 // again. Notice that the window geometry must remain the same even if the new
1083 // buffer is smaller.
1084
1085 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1086 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1087 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1088 QVERIFY(window);
1089 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1090 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1091
1092 const QPointF oldPosition = window->pos();
1093
1094 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1095 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80);
1096 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1097 QVERIFY(frameGeometryChangedSpy.wait());
1098 QCOMPARE(frameGeometryChangedSpy.count(), 1);
1099 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1100 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1101 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10));
1102 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1103
1104 Test::render(surface.get(), QSize(100, 50), Qt::blue);
1105 QVERIFY(frameGeometryChangedSpy.wait());
1106 QCOMPARE(frameGeometryChangedSpy.count(), 2);
1107 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1108 QCOMPARE(window->frameGeometry().size(), QSize(90, 40));
1109 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10));
1110 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50));
1111
1112 shellSurface->xdgSurface()->set_window_geometry(0, 0, 100, 50);
1113 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1114 QVERIFY(frameGeometryChangedSpy.wait());
1115 QCOMPARE(frameGeometryChangedSpy.count(), 3);
1116 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1117 QCOMPARE(window->frameGeometry().size(), QSize(100, 50));
1118 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition);
1119 QCOMPARE(window->bufferGeometry().size(), QSize(100, 50));
1120
1121 shellSurface.reset();
1122 QVERIFY(Test::waitForWindowClosed(window));
1123}
1124
1125void TestXdgShellWindow::testXdgWindowGeometryAttachSubSurface()
1126{
1127 // This test verifies that the effective window geometry remains the same
1128 // when a new sub-surface is added and xdg_surface.set_window_geometry is
1129 // not called again.
1130
1131 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1132 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1133 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1134 QVERIFY(window);
1135 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1136 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1137
1138 const QPointF oldPosition = window->pos();
1139
1140 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1141 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80);
1142 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1143 QVERIFY(frameGeometryChangedSpy.wait());
1144 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1145 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1146 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10));
1147 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1148
1149 std::unique_ptr<KWayland::Client::Surface> childSurface(Test::createSurface());
1150 std::unique_ptr<KWayland::Client::SubSurface> subSurface(Test::createSubSurface(childSurface.get(), surface.get()));
1151 QVERIFY(subSurface);
1152 subSurface->setPosition(QPoint(-20, -20));
1153 Test::render(childSurface.get(), QSize(100, 50), Qt::blue);
1154 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1155 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1156 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1157 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10));
1158 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1159
1160 shellSurface->xdgSurface()->set_window_geometry(-15, -15, 50, 40);
1161 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1162 QVERIFY(frameGeometryChangedSpy.wait());
1163 QCOMPARE(window->frameGeometry().topLeft(), oldPosition);
1164 QCOMPARE(window->frameGeometry().size(), QSize(50, 40));
1165 QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(-15, -15));
1166 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1167}
1168
1169void TestXdgShellWindow::testXdgWindowGeometryInteractiveResize()
1170{
1171 // This test verifies that correct window geometry is provided along each
1172 // configure event when an xdg-shell is being interactively resized.
1173
1174 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1175 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1176 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1177 QVERIFY(window);
1178 QVERIFY(window->isActive());
1179 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1180 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1181
1182 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1183 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1184 QVERIFY(surfaceConfigureRequestedSpy.wait());
1185 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1186
1187 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1188 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80);
1189 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1190 QVERIFY(frameGeometryChangedSpy.wait());
1191 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1192 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1193
1194 QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
1195 QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
1196 QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
1197
1198 // Start interactively resizing the window.
1199 QCOMPARE(workspace()->moveResizeWindow(), nullptr);
1201 QCOMPARE(workspace()->moveResizeWindow(), window);
1202 QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
1203 QVERIFY(surfaceConfigureRequestedSpy.wait());
1204 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
1205 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1206 QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing));
1207
1208 // Go right.
1209 QPointF cursorPos = KWin::Cursors::self()->mouse()->pos();
1210 window->keyPressEvent(Qt::Key_Right);
1211 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
1212 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
1213 QVERIFY(surfaceConfigureRequestedSpy.wait());
1214 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
1215 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1216 QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing));
1217 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(188, 80));
1218 shellSurface->xdgSurface()->set_window_geometry(10, 10, 188, 80);
1219 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1220 Test::render(surface.get(), QSize(208, 100), Qt::blue);
1221 QVERIFY(frameGeometryChangedSpy.wait());
1222 QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
1223 QCOMPARE(window->bufferGeometry().size(), QSize(208, 100));
1224 QCOMPARE(window->frameGeometry().size(), QSize(188, 80));
1225
1226 // Go down.
1227 cursorPos = KWin::Cursors::self()->mouse()->pos();
1228 window->keyPressEvent(Qt::Key_Down);
1229 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
1230 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(0, 8));
1231 QVERIFY(surfaceConfigureRequestedSpy.wait());
1232 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4);
1233 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1234 QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing));
1235 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(188, 88));
1236 shellSurface->xdgSurface()->set_window_geometry(10, 10, 188, 88);
1237 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1238 Test::render(surface.get(), QSize(208, 108), Qt::blue);
1239 QVERIFY(frameGeometryChangedSpy.wait());
1240 QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2);
1241 QCOMPARE(window->bufferGeometry().size(), QSize(208, 108));
1242 QCOMPARE(window->frameGeometry().size(), QSize(188, 88));
1243
1244 // Finish resizing the window.
1245 window->keyPressEvent(Qt::Key_Enter);
1246 QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
1247 QCOMPARE(workspace()->moveResizeWindow(), nullptr);
1248 QVERIFY(surfaceConfigureRequestedSpy.wait());
1249 QCOMPARE(surfaceConfigureRequestedSpy.count(), 5);
1250 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1251 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing));
1252
1253 shellSurface.reset();
1254 QVERIFY(Test::waitForWindowClosed(window));
1255}
1256
1257void TestXdgShellWindow::testXdgWindowGeometryFullScreen()
1258{
1259 // This test verifies that an xdg-shell receives correct window geometry when
1260 // its fullscreen state gets changed.
1261
1262 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1263 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1264 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1265 QVERIFY(window);
1266 QVERIFY(window->isActive());
1267 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1268 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1269
1270 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1271 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1272 QVERIFY(surfaceConfigureRequestedSpy.wait());
1273 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1274
1275 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1276 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80);
1277 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1278 QVERIFY(frameGeometryChangedSpy.wait());
1279 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1280 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1281
1283 QVERIFY(surfaceConfigureRequestedSpy.wait());
1284 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
1285 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024));
1286 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1287 QVERIFY(states.testFlag(Test::XdgToplevel::State::Fullscreen));
1288 shellSurface->xdgSurface()->set_window_geometry(0, 0, 1280, 1024);
1289 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1290 Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
1291 QVERIFY(frameGeometryChangedSpy.wait());
1292 QCOMPARE(window->bufferGeometry().size(), QSize(1280, 1024));
1293 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
1294
1296 QVERIFY(surfaceConfigureRequestedSpy.wait());
1297 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
1298 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(180, 80));
1299 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1300 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Fullscreen));
1301 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80);
1302 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1303 Test::render(surface.get(), QSize(200, 100), Qt::blue);
1304 QVERIFY(frameGeometryChangedSpy.wait());
1305 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1306 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1307
1308 shellSurface.reset();
1309 QVERIFY(Test::waitForWindowClosed(window));
1310}
1311
1312void TestXdgShellWindow::testXdgWindowGeometryMaximize()
1313{
1314 // This test verifies that an xdg-shell receives correct window geometry when
1315 // its maximized state gets changed.
1316
1317 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1318 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1319 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1320 QVERIFY(window);
1321 QVERIFY(window->isActive());
1322 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1323 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1324
1325 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1326 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1327 QVERIFY(surfaceConfigureRequestedSpy.wait());
1328 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1329
1330 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1331 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80);
1332 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1333 QVERIFY(frameGeometryChangedSpy.wait());
1334 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1335 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1336
1338 QVERIFY(surfaceConfigureRequestedSpy.wait());
1339 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
1340 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024));
1341 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1342 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized));
1343 shellSurface->xdgSurface()->set_window_geometry(0, 0, 1280, 1024);
1344 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1345 Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
1346 QVERIFY(frameGeometryChangedSpy.wait());
1347 QCOMPARE(window->bufferGeometry().size(), QSize(1280, 1024));
1348 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
1349
1351 QVERIFY(surfaceConfigureRequestedSpy.wait());
1352 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
1353 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(180, 80));
1354 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1355 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1356 shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80);
1357 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1358 Test::render(surface.get(), QSize(200, 100), Qt::blue);
1359 QVERIFY(frameGeometryChangedSpy.wait());
1360 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1361 QCOMPARE(window->frameGeometry().size(), QSize(180, 80));
1362
1363 shellSurface.reset();
1364 QVERIFY(Test::waitForWindowClosed(window));
1365}
1366
1367void TestXdgShellWindow::testPointerInputTransform()
1368{
1369 // This test verifies that XdgToplevelWindow provides correct input transform matrix.
1370 // The input transform matrix is used by seat to map pointer events from the global
1371 // screen coordinates to the surface-local coordinates.
1372
1373 // Get a wl_pointer object on the client side.
1374 std::unique_ptr<KWayland::Client::Pointer> pointer(Test::waylandSeat()->createPointer());
1375 QVERIFY(pointer);
1376 QVERIFY(pointer->isValid());
1377 QSignalSpy pointerEnteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
1378 QSignalSpy pointerMotionSpy(pointer.get(), &KWayland::Client::Pointer::motion);
1379
1380 // Create an xdg_toplevel surface and wait for the compositor to catch up.
1381 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1382 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1383 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1384 QVERIFY(window);
1385 QVERIFY(window->isActive());
1386 QCOMPARE(window->bufferGeometry().size(), QSize(200, 100));
1387 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1388
1389 // Enter the surface.
1390 quint32 timestamp = 0;
1391 Test::pointerMotion(window->pos(), timestamp++);
1392 QVERIFY(pointerEnteredSpy.wait());
1393
1394 // Move the pointer to (10, 5) relative to the upper left frame corner, which is located
1395 // at (0, 0) in the surface-local coordinates.
1396 Test::pointerMotion(window->pos() + QPointF(10, 5), timestamp++);
1397 QVERIFY(pointerMotionSpy.wait());
1398 QCOMPARE(pointerMotionSpy.last().first().toPointF(), QPointF(10, 5));
1399
1400 // Let's pretend that the window has changed the extents of the client-side drop-shadow
1401 // but the frame geometry didn't change.
1402 QSignalSpy bufferGeometryChangedSpy(window, &Window::bufferGeometryChanged);
1403 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1404 shellSurface->xdgSurface()->set_window_geometry(10, 20, 200, 100);
1405 Test::render(surface.get(), QSize(220, 140), Qt::blue);
1406 QVERIFY(bufferGeometryChangedSpy.wait());
1407 QCOMPARE(frameGeometryChangedSpy.count(), 0);
1408 QCOMPARE(window->frameGeometry().size(), QSize(200, 100));
1409 QCOMPARE(window->bufferGeometry().size(), QSize(220, 140));
1410
1411 // Move the pointer to (20, 50) relative to the upper left frame corner, which is located
1412 // at (10, 20) in the surface-local coordinates.
1413 Test::pointerMotion(window->pos() + QPointF(20, 50), timestamp++);
1414 QVERIFY(pointerMotionSpy.wait());
1415 QCOMPARE(pointerMotionSpy.last().first().toPointF(), QPointF(10, 20) + QPointF(20, 50));
1416
1417 // Destroy the xdg-toplevel surface.
1418 shellSurface.reset();
1419 QVERIFY(Test::waitForWindowClosed(window));
1420}
1421
1422void TestXdgShellWindow::testReentrantSetFrameGeometry()
1423{
1424 // This test verifies that calling moveResize() from a slot connected directly
1425 // to the frameGeometryChanged() signal won't cause an infinite recursion.
1426
1427 // Create an xdg-toplevel surface and wait for the compositor to catch up.
1428 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1429 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1430 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red);
1431 QVERIFY(window);
1432 QCOMPARE(window->pos(), QPoint(0, 0));
1433
1434 // Let's pretend that there is a script that really wants the window to be at (100, 100).
1435 connect(window, &Window::frameGeometryChanged, this, [window]() {
1436 window->moveResize(QRectF(QPointF(100, 100), window->size()));
1437 });
1438
1439 // Trigger the lambda above.
1440 window->move(QPoint(40, 50));
1441
1442 // Eventually, the window will end up at (100, 100).
1443 QCOMPARE(window->pos(), QPoint(100, 100));
1444
1445 // Destroy the xdg-toplevel surface.
1446 shellSurface.reset();
1447 QVERIFY(Test::waitForWindowClosed(window));
1448}
1449
1450void TestXdgShellWindow::testDoubleMaximize()
1451{
1452 // This test verifies that the case where a window issues two set_maximized() requests
1453 // separated by the initial commit is handled properly.
1454
1455 // Create the test surface.
1456 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1457 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1458 shellSurface->set_maximized();
1459 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1460
1461 // Wait for the compositor to respond with a configure event.
1462 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1463 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1464 QVERIFY(surfaceConfigureRequestedSpy.wait());
1465 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1466
1467 QSize size = toplevelConfigureRequestedSpy.last().at(0).toSize();
1468 QCOMPARE(size, QSize(1280, 1024));
1469 Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1470 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized));
1471
1472 // Send another set_maximized() request, but do not attach any buffer yet.
1473 shellSurface->set_maximized();
1474 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1475
1476 // The compositor must respond with another configure event even if the state hasn't changed.
1477 QVERIFY(surfaceConfigureRequestedSpy.wait());
1478 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
1479 size = toplevelConfigureRequestedSpy.last().at(0).toSize();
1480 QCOMPARE(size, QSize(1280, 1024));
1481 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1482 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized));
1483}
1484
1485void TestXdgShellWindow::testDoubleFullscreenSeparatedByCommit()
1486{
1487 // Some applications do weird things at startup and this is one of them. This test verifies
1488 // that the window will have good frame geometry if the window has issued several
1489 // xdg_toplevel.set_fullscreen requests and they are separated by a surface commit with
1490 // no attached buffer.
1491
1492 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1493 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1494 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1495 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1496
1497 // Tell the compositor that we want the window to be shown in fullscreen mode.
1498 shellSurface->set_fullscreen(nullptr);
1499 QVERIFY(surfaceConfigureRequestedSpy.wait());
1500 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024));
1501 QVERIFY(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>() & Test::XdgToplevel::State::Fullscreen);
1502
1503 // Ask again.
1504 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1505 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1506 shellSurface->set_fullscreen(nullptr);
1507 QVERIFY(surfaceConfigureRequestedSpy.wait());
1508 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024));
1509 QVERIFY(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>() & Test::XdgToplevel::State::Fullscreen);
1510
1511 // Map the window.
1512 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1513 auto window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::blue);
1514 QVERIFY(window->isFullScreen());
1515 QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024));
1516}
1517
1518void TestXdgShellWindow::testMaximizeHorizontal()
1519{
1520 // Create the test window.
1521 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1522 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1523
1524 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1525 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1526 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1527
1528 // Wait for the initial configure event.
1529 Test::XdgToplevel::States states;
1530 QVERIFY(surfaceConfigureRequestedSpy.wait());
1531 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1532 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0));
1533 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1534 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated));
1535 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1536
1537 // Map the window.
1538 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1539 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue);
1540 QVERIFY(window);
1541 QVERIFY(window->isActive());
1542 QVERIFY(window->isMaximizable());
1543 QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore);
1544 QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore);
1545 QCOMPARE(window->size(), QSize(800, 600));
1546
1547 // We should receive a configure event when the window becomes active.
1548 QVERIFY(surfaceConfigureRequestedSpy.wait());
1549 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
1550 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1551 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
1552 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1553
1554 // Maximize the test window in horizontal direction.
1556 QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
1557 QCOMPARE(window->maximizeMode(), MaximizeRestore);
1558 QVERIFY(surfaceConfigureRequestedSpy.wait());
1559 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
1560 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 600));
1561 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1562 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1563
1564 // Draw contents of the maximized window.
1565 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1566 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1567 Test::render(surface.get(), QSize(1280, 600), Qt::blue);
1568 QVERIFY(frameGeometryChangedSpy.wait());
1569 QCOMPARE(window->size(), QSize(1280, 600));
1570 QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
1571 QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
1572
1573 // Restore the window.
1575 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
1576 QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
1577 QVERIFY(surfaceConfigureRequestedSpy.wait());
1578 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4);
1579 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600));
1580 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1581 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1582
1583 // Draw contents of the restored window.
1584 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1585 Test::render(surface.get(), QSize(800, 600), Qt::blue);
1586 QVERIFY(frameGeometryChangedSpy.wait());
1587 QCOMPARE(window->size(), QSize(800, 600));
1588 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
1589 QCOMPARE(window->maximizeMode(), MaximizeRestore);
1590
1591 // Destroy the window.
1592 shellSurface.reset();
1593 surface.reset();
1594 QVERIFY(Test::waitForWindowClosed(window));
1595}
1596
1597void TestXdgShellWindow::testMaximizeVertical()
1598{
1599 // Create the test window.
1600 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1601 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1602
1603 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1604 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1605 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1606
1607 // Wait for the initial configure event.
1608 Test::XdgToplevel::States states;
1609 QVERIFY(surfaceConfigureRequestedSpy.wait());
1610 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1611 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0));
1612 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1613 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated));
1614 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1615
1616 // Map the window.
1617 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1618 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue);
1619 QVERIFY(window);
1620 QVERIFY(window->isActive());
1621 QVERIFY(window->isMaximizable());
1622 QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore);
1623 QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore);
1624 QCOMPARE(window->size(), QSize(800, 600));
1625
1626 // We should receive a configure event when the window becomes active.
1627 QVERIFY(surfaceConfigureRequestedSpy.wait());
1628 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
1629 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1630 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
1631 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1632
1633 // Maximize the test window in vertical direction.
1635 QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
1636 QCOMPARE(window->maximizeMode(), MaximizeRestore);
1637 QVERIFY(surfaceConfigureRequestedSpy.wait());
1638 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
1639 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 1024));
1640 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1641 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1642
1643 // Draw contents of the maximized window.
1644 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1645 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1646 Test::render(surface.get(), QSize(800, 1024), Qt::blue);
1647 QVERIFY(frameGeometryChangedSpy.wait());
1648 QCOMPARE(window->size(), QSize(800, 1024));
1649 QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
1650 QCOMPARE(window->maximizeMode(), MaximizeVertical);
1651
1652 // Restore the window.
1654 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
1655 QCOMPARE(window->maximizeMode(), MaximizeVertical);
1656 QVERIFY(surfaceConfigureRequestedSpy.wait());
1657 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4);
1658 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600));
1659 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1660 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1661
1662 // Draw contents of the restored window.
1663 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1664 Test::render(surface.get(), QSize(800, 600), Qt::blue);
1665 QVERIFY(frameGeometryChangedSpy.wait());
1666 QCOMPARE(window->size(), QSize(800, 600));
1667 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
1668 QCOMPARE(window->maximizeMode(), MaximizeRestore);
1669
1670 // Destroy the window.
1671 shellSurface.reset();
1672 surface.reset();
1673 QVERIFY(Test::waitForWindowClosed(window));
1674}
1675
1676void TestXdgShellWindow::testMaximizeFull()
1677{
1678 // Create the test window.
1679 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1680 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1681
1682 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1683 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1684 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1685
1686 // Wait for the initial configure event.
1687 Test::XdgToplevel::States states;
1688 QVERIFY(surfaceConfigureRequestedSpy.wait());
1689 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
1690 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0));
1691 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1692 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated));
1693 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1694
1695 // Map the window.
1696 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1697 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue);
1698 QVERIFY(window);
1699 QVERIFY(window->isActive());
1700 QVERIFY(window->isMaximizable());
1701 QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore);
1702 QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore);
1703 QCOMPARE(window->size(), QSize(800, 600));
1704
1705 // We should receive a configure event when the window becomes active.
1706 QVERIFY(surfaceConfigureRequestedSpy.wait());
1707 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
1708 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1709 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
1710 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1711
1712 // Maximize the test window.
1714 QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
1715 QCOMPARE(window->maximizeMode(), MaximizeRestore);
1716 QVERIFY(surfaceConfigureRequestedSpy.wait());
1717 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
1718 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024));
1719 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1720 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized));
1721
1722 // Draw contents of the maximized window.
1723 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1724 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1725 Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
1726 QVERIFY(frameGeometryChangedSpy.wait());
1727 QCOMPARE(window->size(), QSize(1280, 1024));
1728 QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
1729 QCOMPARE(window->maximizeMode(), MaximizeFull);
1730
1731 // Restore the window.
1733 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
1734 QCOMPARE(window->maximizeMode(), MaximizeFull);
1735 QVERIFY(surfaceConfigureRequestedSpy.wait());
1736 QCOMPARE(surfaceConfigureRequestedSpy.count(), 4);
1737 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600));
1738 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
1739 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
1740
1741 // Draw contents of the restored window.
1742 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1743 Test::render(surface.get(), QSize(800, 600), Qt::blue);
1744 QVERIFY(frameGeometryChangedSpy.wait());
1745 QCOMPARE(window->size(), QSize(800, 600));
1746 QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
1747 QCOMPARE(window->maximizeMode(), MaximizeRestore);
1748
1749 // Destroy the window.
1750 shellSurface.reset();
1751 surface.reset();
1752 QVERIFY(Test::waitForWindowClosed(window));
1753}
1754
1755void TestXdgShellWindow::testSendMaximizedWindowToAnotherOutput()
1756{
1757 // This test verifies that the maximized window will have correct geometry restore
1758 // after it's sent to another output.
1759
1760 const auto outputs = workspace()->outputs();
1761
1762 // Create the window.
1763 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1764 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1765 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1766 QVERIFY(window);
1767
1768 // Wait for the compositor to send a configure event with the activated state.
1769 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1770 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1771 QVERIFY(surfaceConfigureRequestedSpy.wait());
1772
1773 // Move the window to the left monitor.
1774 window->move(QPointF(10, 20));
1775 QCOMPARE(window->frameGeometry(), QRectF(10, 20, 100, 50));
1776 QCOMPARE(window->output(), outputs[0]);
1777
1778 // Make the window maximized.
1779 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
1780 shellSurface->set_maximized();
1781 QVERIFY(surfaceConfigureRequestedSpy.wait());
1782 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1783 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::red);
1784 QVERIFY(frameGeometryChangedSpy.wait());
1785 QCOMPARE(window->maximizeMode(), MaximizeFull);
1786 QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024));
1787 QCOMPARE(window->geometryRestore(), QRectF(10, 20, 100, 50));
1788 QCOMPARE(window->output(), outputs[0]);
1789
1790 // Send the window to another output.
1791 workspace()->sendWindowToOutput(window, outputs[1]);
1792 QCOMPARE(window->maximizeMode(), MaximizeFull);
1793 QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024));
1794 QCOMPARE(window->geometryRestore(), QRectF(1280 + 10, 20, 100, 50));
1795 QCOMPARE(window->output(), outputs[1]);
1796}
1797
1798void TestXdgShellWindow::testMaximizeAndChangeDecorationModeAfterInitialCommit()
1799{
1800 // Ideally, the app would initialize the xdg-toplevel surface before the initial commit, but
1801 // many don't do it. They initialize the surface after the first commit.
1802 // This test verifies that the window will receive a configure event with correct size
1803 // if an xdg-toplevel surface is set maximized and decoration mode changes after initial commit.
1804
1805 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1806 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1807 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
1808 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1809 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1810
1811 // Commit the initial state.
1812 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1813 QVERIFY(surfaceConfigureRequestedSpy.wait());
1814 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
1815
1816 // Request maximized mode and set decoration mode, i.e. perform late initialization.
1817 shellSurface->set_maximized();
1818 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side);
1819
1820 // The compositor will respond with a new configure event, which should contain maximized state.
1821 QVERIFY(surfaceConfigureRequestedSpy.wait());
1822 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024));
1823 QCOMPARE(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(), Test::XdgToplevel::State::Maximized);
1824}
1825
1826void TestXdgShellWindow::testFullScreenAndChangeDecorationModeAfterInitialCommit()
1827{
1828 // Ideally, the app would initialize the xdg-toplevel surface before the initial commit, but
1829 // many don't do it. They initialize the surface after the first commit.
1830 // This test verifies that the window will receive a configure event with correct size
1831 // if an xdg-toplevel surface is set fullscreen and decoration mode changes after initial commit.
1832
1833 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1834 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1835 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
1836 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1837 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1838
1839 // Commit the initial state.
1840 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1841 QVERIFY(surfaceConfigureRequestedSpy.wait());
1842 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
1843
1844 // Request fullscreen mode and set decoration mode, i.e. perform late initialization.
1845 shellSurface->set_fullscreen(nullptr);
1846 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side);
1847
1848 // The compositor will respond with a new configure event, which should contain fullscreen state.
1849 QVERIFY(surfaceConfigureRequestedSpy.wait());
1850 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(1280, 1024));
1851 QCOMPARE(toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>(), Test::XdgToplevel::State::Fullscreen);
1852}
1853
1854void TestXdgShellWindow::testChangeDecorationModeAfterInitialCommit()
1855{
1856 // This test verifies that the compositor will respond with a good configure event when
1857 // the decoration mode changes after the first surface commit but before the surface is mapped.
1858
1859 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1860 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1861 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
1862 QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested);
1863 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
1864 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1865
1866 // Perform the initial commit.
1867 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1868 QVERIFY(surfaceConfigureRequestedSpy.wait());
1869 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
1870 QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value<Test::XdgToplevelDecorationV1::mode>(), Test::XdgToplevelDecorationV1::mode_server_side);
1871
1872 // Change decoration mode.
1873 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side);
1874
1875 // The configure event should still have 0x0 size.
1876 QVERIFY(surfaceConfigureRequestedSpy.wait());
1877 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
1878 QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value<Test::XdgToplevelDecorationV1::mode>(), Test::XdgToplevelDecorationV1::mode_client_side);
1879}
1880
1882#include "xdgshellwindow_test.moc"
QPointF pos()
Definition cursor.cpp:204
static Cursors * self()
Definition cursor.cpp:35
Cursor * mouse() const
Definition cursor.h:266
ClientConnection * createClient(int fd)
Definition display.cpp:228
PointerInputRedirection * pointer() const
Definition input.h:220
int killPingTimeout
Definition options.h:176
void warp(const QPointF &pos)
void configureRequested(const QRect &rect)
void configureRequested(quint32 serial)
void configureRequested(QtWayland::zxdg_toplevel_decoration_v1::mode mode)
void configureRequested(const QSize &size, KWin::Test::XdgToplevel::States states)
Manages the number of available virtual desktops, the layout of those and which virtual desktop is th...
QList< VirtualDesktop * > desktops() const
Display * display() const
void activateWindow(Window *window, bool force=false)
void sendWindowToOutput(Window *window, Output *output)
void slotWindowMaximizeVertical()
void windowAdded(KWin::Window *)
void slotWindowFullScreen()
QList< Output * > outputs() const
Definition workspace.h:762
void setActiveOutput(Output *output)
void slotWindowMaximizeHorizontal()
void slotWindowToDesktop(VirtualDesktop *desktop)
#define WAYLANDTEST_MAIN(TestObject)
XdgPositioner * createXdgPositioner()
Window * renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32, int timeout=5000)
XdgPopup * createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, CreationSetup configureMode=CreationSetup::CreateAndConfigure, QObject *parent=nullptr)
void destroyWaylandConnection()
void setOutputConfig(const QList< QRect > &geometries)
bool setupWaylandConnection(AdditionalWaylandInterfaces flags=AdditionalWaylandInterfaces())
void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32_Premultiplied)
KWayland::Client::Seat * waylandSeat()
QList< KWayland::Client::Output * > outputs
void pointerMotion(const QPointF &position, quint32 time)
std::unique_ptr< KWayland::Client::Surface > createSurface()
XdgToplevel * createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent=nullptr)
XdgToplevelDecorationV1 * createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent=nullptr)
KWayland::Client::AppMenuManager * waylandAppMenuManager()
KWayland::Client::SubSurface * createSubSurface(KWayland::Client::Surface *surface, KWayland::Client::Surface *parentSurface, QObject *parent=nullptr)
bool waitForWaylandPointer()
bool waitForWindowClosed(Window *window)
KWayland::Client::Output * waylandOutput(const QString &name)
KWIN_EXPORT xcb_window_t rootWindow()
Definition xcb.h:24
@ MaximizeVertical
The window is maximized vertically.
Definition common.h:76
@ MaximizeRestore
The window is not maximized in any direction.
Definition common.h:75
@ MaximizeHorizontal
Definition common.h:77
@ MaximizeFull
Equal to MaximizeVertical | MaximizeHorizontal.
Definition common.h:79
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
Options * options
Definition main.cpp:73
InputRedirection * input()
Definition input.h:549
@ ActiveLayer
Definition globals.h:170
@ NormalLayer
Definition globals.h:167