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