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