KWin
Loading...
Searching...
No Matches
internal_window.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "kwin_wayland_test.h"
10
11#include "core/output.h"
13#include "internalwindow.h"
14#include "pointer_input.h"
15#include "wayland/surface.h"
16#include "wayland_server.h"
17#include "workspace.h"
18
19#include <QPainter>
20#include <QRasterWindow>
21#include <QSignalSpy>
22
23#include <KWayland/Client/keyboard.h>
24#include <KWayland/Client/seat.h>
25#include <KWayland/Client/surface.h>
26#include <KWindowSystem>
27
28#include <linux/input.h>
29
30namespace KWin
31{
32
33static const QString s_socketName = QStringLiteral("wayland_test_kwin_internal_window-0");
34
35class InternalWindowTest : public QObject
36{
37 Q_OBJECT
38private Q_SLOTS:
39 void initTestCase();
40 void init();
41 void cleanup();
42 void testEnterLeave();
43 void testPointerPressRelease();
44 void testPointerAxis();
45 void testKeyboard_data();
46 void testKeyboard();
47 void testKeyboardShowWithoutActivating();
48 void testKeyboardTriggersLeave();
49 void testTouch();
50 void testOpacity();
51 void testMove();
52 void testSkipCloseAnimation_data();
53 void testSkipCloseAnimation();
54 void testModifierClickUnrestrictedMove();
55 void testModifierScroll();
56 void testPopup();
57 void testScale();
58 void testEffectWindow();
59 void testReentrantMoveResize();
60 void testDismissPopup();
61};
62
63class HelperWindow : public QRasterWindow
64{
65 Q_OBJECT
66public:
68 ~HelperWindow() override;
69
70 QPoint latestGlobalMousePos() const
71 {
72 return m_latestGlobalMousePos;
73 }
74 Qt::MouseButtons pressedButtons() const
75 {
76 return m_pressedButtons;
77 }
78
79Q_SIGNALS:
80 void entered();
81 void left();
82 void mouseMoved(const QPoint &global);
85 void wheel();
86 void keyPressed();
88
89protected:
90 void paintEvent(QPaintEvent *event) override;
91 bool event(QEvent *event) override;
92 void mouseMoveEvent(QMouseEvent *event) override;
93 void mousePressEvent(QMouseEvent *event) override;
94 void mouseReleaseEvent(QMouseEvent *event) override;
95 void wheelEvent(QWheelEvent *event) override;
96 void keyPressEvent(QKeyEvent *event) override;
97 void keyReleaseEvent(QKeyEvent *event) override;
98
99private:
100 QPoint m_latestGlobalMousePos;
101 Qt::MouseButtons m_pressedButtons = Qt::MouseButtons();
102};
103
105 : QRasterWindow(nullptr)
106{
107 setFlags(Qt::FramelessWindowHint);
108}
109
111
112void HelperWindow::paintEvent(QPaintEvent *event)
113{
114 QPainter p(this);
115 p.fillRect(0, 0, width(), height(), Qt::red);
116}
117
118bool HelperWindow::event(QEvent *event)
119{
120 if (event->type() == QEvent::Enter) {
121 Q_EMIT entered();
122 }
123 if (event->type() == QEvent::Leave) {
124 Q_EMIT left();
125 }
126 return QRasterWindow::event(event);
127}
128
129void HelperWindow::mouseMoveEvent(QMouseEvent *event)
130{
131 m_latestGlobalMousePos = event->globalPos();
132 Q_EMIT mouseMoved(event->globalPos());
133}
134
135void HelperWindow::mousePressEvent(QMouseEvent *event)
136{
137 m_latestGlobalMousePos = event->globalPos();
138 m_pressedButtons = event->buttons();
139 Q_EMIT mousePressed();
140}
141
142void HelperWindow::mouseReleaseEvent(QMouseEvent *event)
143{
144 m_latestGlobalMousePos = event->globalPos();
145 m_pressedButtons = event->buttons();
146 Q_EMIT mouseReleased();
147}
148
149void HelperWindow::wheelEvent(QWheelEvent *event)
150{
151 Q_EMIT wheel();
152}
153
154void HelperWindow::keyPressEvent(QKeyEvent *event)
155{
156 Q_EMIT keyPressed();
157}
158
159void HelperWindow::keyReleaseEvent(QKeyEvent *event)
160{
161 Q_EMIT keyReleased();
162}
163
164void InternalWindowTest::initTestCase()
165{
166 qRegisterMetaType<KWin::Window *>();
167 qRegisterMetaType<KWin::InternalWindow *>();
168 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
169 QVERIFY(waylandServer()->init(s_socketName));
171 QRect(0, 0, 1280, 1024),
172 QRect(1280, 0, 1280, 1024),
173 });
174 kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
175
176 kwinApp()->start();
177 QVERIFY(applicationStartedSpy.wait());
178 const auto outputs = workspace()->outputs();
179 QCOMPARE(outputs.count(), 2);
180 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
181 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
182}
183
184void InternalWindowTest::init()
185{
186 input()->pointer()->warp(QPoint(512, 512));
189}
190
191void InternalWindowTest::cleanup()
192{
194}
195
196void InternalWindowTest::testEnterLeave()
197{
198 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
199 HelperWindow win;
200 QVERIFY(!workspace()->findInternal(nullptr));
201 QVERIFY(!workspace()->findInternal(&win));
202 win.setGeometry(0, 0, 100, 100);
203 win.show();
204
205 QTRY_COMPARE(windowAddedSpy.count(), 1);
206 QVERIFY(!workspace()->activeWindow());
207 InternalWindow *window = windowAddedSpy.first().first().value<InternalWindow *>();
208 QVERIFY(window);
209 QVERIFY(window->isInternal());
210 QVERIFY(!window->isDecorated());
211 QCOMPARE(workspace()->findInternal(&win), window);
212 QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 100));
213 QVERIFY(window->isShown());
214 QVERIFY(workspace()->stackingOrder().contains(window));
215
216 QSignalSpy enterSpy(&win, &HelperWindow::entered);
217 QSignalSpy leaveSpy(&win, &HelperWindow::left);
218 QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved);
219
220 quint32 timestamp = 1;
221 Test::pointerMotion(QPoint(50, 50), timestamp++);
222 QTRY_COMPARE(moveSpy.count(), 1);
223
224 Test::pointerMotion(QPoint(60, 50), timestamp++);
225 QTRY_COMPARE(moveSpy.count(), 2);
226 QCOMPARE(moveSpy[1].first().toPoint(), QPoint(60, 50));
227
228 Test::pointerMotion(QPoint(101, 50), timestamp++);
229 QTRY_COMPARE(leaveSpy.count(), 1);
230
231 // set a mask on the window
232 win.setMask(QRegion(10, 20, 30, 40));
233 // outside the mask we should not get an enter
234 Test::pointerMotion(QPoint(5, 5), timestamp++);
235 QVERIFY(!enterSpy.wait(100));
236 QCOMPARE(enterSpy.count(), 1);
237 // inside the mask we should still get an enter
238 Test::pointerMotion(QPoint(25, 27), timestamp++);
239 QTRY_COMPARE(enterSpy.count(), 2);
240}
241
242void InternalWindowTest::testPointerPressRelease()
243{
244 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
245 HelperWindow win;
246 win.setGeometry(0, 0, 100, 100);
247 win.show();
248 QSignalSpy pressSpy(&win, &HelperWindow::mousePressed);
249 QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased);
250
251 QTRY_COMPARE(windowAddedSpy.count(), 1);
252
253 quint32 timestamp = 1;
254 Test::pointerMotion(QPoint(50, 50), timestamp++);
255
256 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
257 QTRY_COMPARE(pressSpy.count(), 1);
258 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
259 QTRY_COMPARE(releaseSpy.count(), 1);
260}
261
262void InternalWindowTest::testPointerAxis()
263{
264 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
265 HelperWindow win;
266 win.setGeometry(0, 0, 100, 100);
267 win.show();
268 QSignalSpy wheelSpy(&win, &HelperWindow::wheel);
269 QTRY_COMPARE(windowAddedSpy.count(), 1);
270
271 quint32 timestamp = 1;
272 Test::pointerMotion(QPoint(50, 50), timestamp++);
273
274 Test::pointerAxisVertical(5.0, timestamp++);
275 QTRY_COMPARE(wheelSpy.count(), 1);
276 Test::pointerAxisHorizontal(5.0, timestamp++);
277 QTRY_COMPARE(wheelSpy.count(), 2);
278}
279
280void InternalWindowTest::testKeyboard_data()
281{
282 QTest::addColumn<QPoint>("cursorPos");
283
284 QTest::newRow("on Window") << QPoint(50, 50);
285 QTest::newRow("outside Window") << QPoint(250, 250);
286}
287
288void InternalWindowTest::testKeyboard()
289{
290 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
291 HelperWindow win;
292 win.setGeometry(0, 0, 100, 100);
293 win.show();
294 QSignalSpy pressSpy(&win, &HelperWindow::keyPressed);
295 QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased);
296 QTRY_COMPARE(windowAddedSpy.count(), 1);
297 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
298 QVERIFY(internalWindow);
299 QVERIFY(internalWindow->isInternal());
300 QVERIFY(internalWindow->readyForPainting());
301
302 quint32 timestamp = 1;
303 QFETCH(QPoint, cursorPos);
304 Test::pointerMotion(cursorPos, timestamp++);
305
306 Test::keyboardKeyPressed(KEY_A, timestamp++);
307 QTRY_COMPARE(pressSpy.count(), 1);
308 QCOMPARE(releaseSpy.count(), 0);
309 Test::keyboardKeyReleased(KEY_A, timestamp++);
310 QTRY_COMPARE(releaseSpy.count(), 1);
311 QCOMPARE(pressSpy.count(), 1);
312}
313
314void InternalWindowTest::testKeyboardShowWithoutActivating()
315{
316 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
317 HelperWindow win;
318 win.setProperty("_q_showWithoutActivating", true);
319 win.setGeometry(0, 0, 100, 100);
320 win.show();
321 QSignalSpy pressSpy(&win, &HelperWindow::keyPressed);
322 QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased);
323 QTRY_COMPARE(windowAddedSpy.count(), 1);
324 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
325 QVERIFY(internalWindow);
326 QVERIFY(internalWindow->isInternal());
327 QVERIFY(internalWindow->readyForPainting());
328
329 quint32 timestamp = 1;
330 const QPoint cursorPos = QPoint(50, 50);
331 Test::pointerMotion(cursorPos, timestamp++);
332
333 Test::keyboardKeyPressed(KEY_A, timestamp++);
334 QCOMPARE(pressSpy.count(), 0);
335 QVERIFY(!pressSpy.wait(100));
336 QCOMPARE(releaseSpy.count(), 0);
337 Test::keyboardKeyReleased(KEY_A, timestamp++);
338 QCOMPARE(releaseSpy.count(), 0);
339 QVERIFY(!releaseSpy.wait(100));
340 QCOMPARE(pressSpy.count(), 0);
341}
342
343void InternalWindowTest::testKeyboardTriggersLeave()
344{
345 // this test verifies that a leave event is sent to a window when an internal window
346 // gets a key event
347 std::unique_ptr<KWayland::Client::Keyboard> keyboard(Test::waylandSeat()->createKeyboard());
348 QVERIFY(keyboard != nullptr);
349 QVERIFY(keyboard->isValid());
350 QSignalSpy enteredSpy(keyboard.get(), &KWayland::Client::Keyboard::entered);
351 QSignalSpy leftSpy(keyboard.get(), &KWayland::Client::Keyboard::left);
352 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
353 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
354
355 // now let's render
356 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
357 QVERIFY(window);
358 QVERIFY(window->isActive());
359 QVERIFY(!window->isInternal());
360
361 if (enteredSpy.isEmpty()) {
362 QVERIFY(enteredSpy.wait());
363 }
364 QCOMPARE(enteredSpy.count(), 1);
365
366 // create internal window
367 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
368 HelperWindow win;
369 win.setGeometry(0, 0, 100, 100);
370 win.show();
371 QSignalSpy pressSpy(&win, &HelperWindow::keyPressed);
372 QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased);
373 QTRY_COMPARE(windowAddedSpy.count(), 1);
374 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
375 QVERIFY(internalWindow);
376 QVERIFY(internalWindow->isInternal());
377 QVERIFY(internalWindow->readyForPainting());
378
379 QVERIFY(leftSpy.isEmpty());
380 QVERIFY(!leftSpy.wait(100));
381
382 // now let's trigger a key, which should result in a leave
383 quint32 timestamp = 1;
384 Test::keyboardKeyPressed(KEY_A, timestamp++);
385 QVERIFY(leftSpy.wait());
386 QCOMPARE(pressSpy.count(), 1);
387
388 Test::keyboardKeyReleased(KEY_A, timestamp++);
389 QTRY_COMPARE(releaseSpy.count(), 1);
390
391 // after hiding the internal window, next key press should trigger an enter
392 win.hide();
393 Test::keyboardKeyPressed(KEY_A, timestamp++);
394 QVERIFY(enteredSpy.wait());
395 Test::keyboardKeyReleased(KEY_A, timestamp++);
396
397 // Destroy the test window.
398 shellSurface.reset();
399 QVERIFY(Test::waitForWindowClosed(window));
400}
401
402void InternalWindowTest::testTouch()
403{
404 // touch events for internal windows are emulated through mouse events
405 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
406 HelperWindow win;
407 win.setGeometry(0, 0, 100, 100);
408 win.show();
409 QTRY_COMPARE(windowAddedSpy.count(), 1);
410
411 QSignalSpy pressSpy(&win, &HelperWindow::mousePressed);
412 QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased);
413 QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved);
414
415 quint32 timestamp = 1;
416 QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
417 Test::touchDown(0, QPointF(50, 50), timestamp++);
418 QCOMPARE(pressSpy.count(), 1);
419 QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
420 QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
421
422 // further touch down should not trigger
423 Test::touchDown(1, QPointF(75, 75), timestamp++);
424 QCOMPARE(pressSpy.count(), 1);
425 Test::touchUp(1, timestamp++);
426 QCOMPARE(releaseSpy.count(), 0);
427 QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
428 QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
429
430 // another press
431 Test::touchDown(1, QPointF(10, 10), timestamp++);
432 QCOMPARE(pressSpy.count(), 1);
433 QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
434 QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
435
436 // simulate the move
437 QCOMPARE(moveSpy.count(), 0);
438 Test::touchMotion(0, QPointF(80, 90), timestamp++);
439 QCOMPARE(moveSpy.count(), 1);
440 QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
441 QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
442
443 // move on other ID should not do anything
444 Test::touchMotion(1, QPointF(20, 30), timestamp++);
445 QCOMPARE(moveSpy.count(), 1);
446 QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
447 QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
448
449 // now up our main point
450 Test::touchUp(0, timestamp++);
451 QCOMPARE(releaseSpy.count(), 1);
452 QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
453 QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
454
455 // and up the additional point
456 Test::touchUp(1, timestamp++);
457 QCOMPARE(releaseSpy.count(), 1);
458 QCOMPARE(moveSpy.count(), 1);
459 QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
460 QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
461}
462
463void InternalWindowTest::testOpacity()
464{
465 // this test verifies that opacity is properly synced from QWindow to InternalClient
466 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
467 HelperWindow win;
468 win.setOpacity(0.5);
469 win.setGeometry(0, 0, 100, 100);
470 win.show();
471 QTRY_COMPARE(windowAddedSpy.count(), 1);
472 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
473 QVERIFY(internalWindow);
474 QVERIFY(internalWindow->isInternal());
475 QCOMPARE(internalWindow->opacity(), 0.5);
476
477 QSignalSpy opacityChangedSpy(internalWindow, &InternalWindow::opacityChanged);
478 win.setOpacity(0.75);
479 QCOMPARE(opacityChangedSpy.count(), 1);
480 QCOMPARE(internalWindow->opacity(), 0.75);
481}
482
483void InternalWindowTest::testMove()
484{
485 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
486 HelperWindow win;
487 win.setOpacity(0.5);
488 win.setGeometry(0, 0, 100, 100);
489 win.show();
490 QTRY_COMPARE(windowAddedSpy.count(), 1);
491 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
492 QVERIFY(internalWindow);
493 QCOMPARE(internalWindow->frameGeometry(), QRect(0, 0, 100, 100));
494
495 // normal move should be synced
496 internalWindow->move(QPoint(5, 10));
497 QCOMPARE(internalWindow->frameGeometry(), QRect(5, 10, 100, 100));
498 QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100));
499 // another move should also be synced
500 internalWindow->move(QPoint(10, 20));
501 QCOMPARE(internalWindow->frameGeometry(), QRect(10, 20, 100, 100));
502 QTRY_COMPARE(win.geometry(), QRect(10, 20, 100, 100));
503
504 // now move with a Geometry update blocker
505 {
506 GeometryUpdatesBlocker blocker(internalWindow);
507 internalWindow->move(QPoint(5, 10));
508 // not synced!
509 QCOMPARE(win.geometry(), QRect(10, 20, 100, 100));
510 }
511 // after destroying the blocker it should be synced
512 QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100));
513}
514
515void InternalWindowTest::testSkipCloseAnimation_data()
516{
517 QTest::addColumn<bool>("initial");
518
519 QTest::newRow("set") << true;
520 QTest::newRow("not set") << false;
521}
522
523void InternalWindowTest::testSkipCloseAnimation()
524{
525 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
526 HelperWindow win;
527 win.setOpacity(0.5);
528 win.setGeometry(0, 0, 100, 100);
529 QFETCH(bool, initial);
530 win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial);
531 win.show();
532 QTRY_COMPARE(windowAddedSpy.count(), 1);
533 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
534 QVERIFY(internalWindow);
535 QCOMPARE(internalWindow->skipsCloseAnimation(), initial);
536 QSignalSpy skipCloseChangedSpy(internalWindow, &Window::skipCloseAnimationChanged);
537 win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", !initial);
538 QCOMPARE(skipCloseChangedSpy.count(), 1);
539 QCOMPARE(internalWindow->skipsCloseAnimation(), !initial);
540 win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial);
541 QCOMPARE(skipCloseChangedSpy.count(), 2);
542 QCOMPARE(internalWindow->skipsCloseAnimation(), initial);
543}
544
545void InternalWindowTest::testModifierClickUnrestrictedMove()
546{
547 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
548 HelperWindow win;
549 win.setGeometry(0, 0, 100, 100);
550 win.setFlags(win.flags() & ~Qt::FramelessWindowHint);
551 win.show();
552 QTRY_COMPARE(windowAddedSpy.count(), 1);
553 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
554 QVERIFY(internalWindow);
555 QVERIFY(internalWindow->isDecorated());
556
557 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
558 group.writeEntry("CommandAllKey", "Meta");
559 group.writeEntry("CommandAll1", "Move");
560 group.writeEntry("CommandAll2", "Move");
561 group.writeEntry("CommandAll3", "Move");
562 group.sync();
564 QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
568
569 // move cursor on window
570 input()->pointer()->warp(internalWindow->frameGeometry().center());
571
572 // simulate modifier+click
573 quint32 timestamp = 1;
574 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
575 QVERIFY(!internalWindow->isInteractiveMove());
576 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
577 QVERIFY(internalWindow->isInteractiveMove());
578 // release modifier should not change it
579 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
580 QVERIFY(internalWindow->isInteractiveMove());
581 // but releasing the key should end move/resize
582 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
583 QVERIFY(!internalWindow->isInteractiveMove());
584}
585
586void InternalWindowTest::testModifierScroll()
587{
588 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
589 HelperWindow win;
590 win.setGeometry(0, 0, 100, 100);
591 win.setFlags(win.flags() & ~Qt::FramelessWindowHint);
592 win.show();
593 QTRY_COMPARE(windowAddedSpy.count(), 1);
594 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
595 QVERIFY(internalWindow);
596 QVERIFY(internalWindow->isDecorated());
597
598 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
599 group.writeEntry("CommandAllKey", "Meta");
600 group.writeEntry("CommandAllWheel", "change opacity");
601 group.sync();
603
604 // move cursor on window
605 input()->pointer()->warp(internalWindow->frameGeometry().center());
606
607 // set the opacity to 0.5
608 internalWindow->setOpacity(0.5);
609 QCOMPARE(internalWindow->opacity(), 0.5);
610 quint32 timestamp = 1;
611 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
612 Test::pointerAxisVertical(-5, timestamp++);
613 QCOMPARE(internalWindow->opacity(), 0.6);
614 Test::pointerAxisVertical(5, timestamp++);
615 QCOMPARE(internalWindow->opacity(), 0.5);
616 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
617}
618
619void InternalWindowTest::testPopup()
620{
621 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
622 HelperWindow win;
623 win.setGeometry(0, 0, 100, 100);
624 win.setFlags(win.flags() | Qt::Popup);
625 win.show();
626 QTRY_COMPARE(windowAddedSpy.count(), 1);
627 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
628 QVERIFY(internalWindow);
629 QCOMPARE(internalWindow->isPopupWindow(), true);
630}
631
632void InternalWindowTest::testScale()
633{
635 Test::OutputInfo{
636 .geometry = QRect(0, 0, 1280, 1024),
637 .scale = 2.0,
638 },
639 Test::OutputInfo{
640 .geometry = QRect(1280, 0, 1280, 1024),
641 .scale = 2.0,
642 },
643 });
644
645 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
646 HelperWindow win;
647 win.setGeometry(0, 0, 100, 100);
648 win.setFlags(win.flags() | Qt::Popup);
649 win.show();
650 QCOMPARE(win.devicePixelRatio(), 2.0);
651 QTRY_COMPARE(windowAddedSpy.count(), 1);
652 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
653 QCOMPARE(internalWindow->bufferScale(), 2);
654}
655
656void InternalWindowTest::testEffectWindow()
657{
658 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
659 HelperWindow win;
660 win.setGeometry(0, 0, 100, 100);
661 win.show();
662 QTRY_COMPARE(windowAddedSpy.count(), 1);
663 auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
664 QVERIFY(internalWindow);
665 QVERIFY(internalWindow->effectWindow());
666 QCOMPARE(internalWindow->effectWindow()->internalWindow(), &win);
667
668 QCOMPARE(effects->findWindow(&win), internalWindow->effectWindow());
669 QCOMPARE(effects->findWindow(&win)->internalWindow(), &win);
670}
671
672void InternalWindowTest::testReentrantMoveResize()
673{
674 // This test verifies that calling moveResize() from a slot connected directly
675 // to the frameGeometryChanged() signal won't cause an infinite recursion.
676
677 // Create an internal window.
678 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
679 HelperWindow win;
680 win.setGeometry(0, 0, 100, 100);
681 win.show();
682 QTRY_COMPARE(windowAddedSpy.count(), 1);
683 auto window = windowAddedSpy.first().first().value<InternalWindow *>();
684 QVERIFY(window);
685 QCOMPARE(window->pos(), QPoint(0, 0));
686
687 // Let's pretend that there is a script that really wants the window to be at (100, 100).
688 connect(window, &Window::frameGeometryChanged, this, [window]() {
689 window->moveResize(QRectF(QPointF(100, 100), window->size()));
690 });
691
692 // Trigger the lambda above.
693 window->move(QPoint(40, 50));
694
695 // Eventually, the window will end up at (100, 100).
696 QCOMPARE(window->pos(), QPoint(100, 100));
697}
698
699void InternalWindowTest::testDismissPopup()
700{
701 // This test verifies that a popup window created by the compositor will be dismissed
702 // when user clicks another window.
703
704 // Create a toplevel window.
705 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
706 HelperWindow clientToplevel;
707 clientToplevel.setGeometry(0, 0, 100, 100);
708 clientToplevel.show();
709 QTRY_COMPARE(windowAddedSpy.count(), 1);
710 auto serverToplevel = windowAddedSpy.last().first().value<InternalWindow *>();
711 QVERIFY(serverToplevel);
712
713 // Create a popup window.
714 QRasterWindow clientPopup;
715 clientPopup.setFlag(Qt::Popup);
716 clientPopup.setTransientParent(&clientToplevel);
717 clientPopup.setGeometry(0, 0, 50, 50);
718 clientPopup.show();
719 QTRY_COMPARE(windowAddedSpy.count(), 2);
720 auto serverPopup = windowAddedSpy.last().first().value<InternalWindow *>();
721 QVERIFY(serverPopup);
722
723 // Create the other window to click
724 HelperWindow otherClientToplevel;
725 otherClientToplevel.setGeometry(100, 100, 100, 100);
726 otherClientToplevel.show();
727 QTRY_COMPARE(windowAddedSpy.count(), 3);
728 auto serverOtherToplevel = windowAddedSpy.last().first().value<InternalWindow *>();
729 QVERIFY(serverOtherToplevel);
730
731 // Click somewhere outside the popup window.
732 QSignalSpy popupClosedSpy(serverPopup, &InternalWindow::closed);
733 quint32 timestamp = 0;
734 Test::pointerMotion(serverOtherToplevel->frameGeometry().center(), timestamp++);
735 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
736 QTRY_COMPARE(popupClosedSpy.count(), 1);
737}
738
739}
740
742#include "internal_window.moc"
QWindow * internalWindow
Q_SCRIPTABLE KWin::EffectWindow * findWindow(WId id) const
void paintEvent(QPaintEvent *event) override
void mouseMoveEvent(QMouseEvent *event) override
Qt::MouseButtons pressedButtons() const
~HelperWindow() override=default
~HelperWindow() override
bool event(QEvent *event) override
void keyReleaseEvent(QKeyEvent *event) override
void mousePressEvent(QMouseEvent *event) override
QPoint latestGlobalMousePos() const
void keyPressEvent(QKeyEvent *event) override
void mouseReleaseEvent(QMouseEvent *event) override
void mouseMoved(const QPoint &global)
void wheelEvent(QWheelEvent *event) override
PointerInputRedirection * pointer() const
Definition input.h:220
@ MouseUnrestrictedMove
Definition options.h:456
Qt::KeyboardModifier commandAllModifier() const
Definition options.h:558
MouseCommand commandAll2
Definition options.h:153
MouseCommand commandAll3
Definition options.h:154
MouseCommand commandAll1
Definition options.h:152
void warp(const QPointF &pos)
void skipCloseAnimationChanged()
void opacityChanged(KWin::Window *window, qreal oldOpacity)
void frameGeometryChanged(const QRectF &oldGeometry)
void windowAdded(KWin::Window *)
QList< Output * > outputs() const
Definition workspace.h:762
void slotReconfigure()
#define WAYLANDTEST_MAIN(TestObject)
Window * renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32, int timeout=5000)
void keyboardKeyReleased(quint32 key, quint32 time)
void destroyWaylandConnection()
void setOutputConfig(const QList< QRect > &geometries)
void touchDown(qint32 id, const QPointF &pos, quint32 time)
void pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta=0, InputRedirection::PointerAxisSource source=InputRedirection::PointerAxisSourceUnknown)
void keyboardKeyPressed(quint32 key, quint32 time)
bool waitForWaylandKeyboard()
bool setupWaylandConnection(AdditionalWaylandInterfaces flags=AdditionalWaylandInterfaces())
void touchMotion(qint32 id, const QPointF &pos, quint32 time)
void pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta=0, InputRedirection::PointerAxisSource source=InputRedirection::PointerAxisSourceUnknown)
KWayland::Client::Seat * waylandSeat()
void pointerButtonPressed(quint32 button, quint32 time)
void pointerMotion(const QPointF &position, quint32 time)
std::unique_ptr< KWayland::Client::Surface > createSurface()
XdgToplevel * createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent=nullptr)
void pointerButtonReleased(quint32 button, quint32 time)
void touchUp(qint32 id, quint32 time)
bool waitForWindowClosed(Window *window)
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
Options * options
Definition main.cpp:73
InputRedirection * input()
Definition input.h:549
EffectsHandler * effects