File indexing completed on 2024-11-10 04:55:59

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "kwin_wayland_test.h"
0010 
0011 #include "core/output.h"
0012 #include "effect/effecthandler.h"
0013 #include "internalwindow.h"
0014 #include "pointer_input.h"
0015 #include "wayland/surface.h"
0016 #include "wayland_server.h"
0017 #include "workspace.h"
0018 
0019 #include <QPainter>
0020 #include <QRasterWindow>
0021 #include <QSignalSpy>
0022 
0023 #include <KWayland/Client/keyboard.h>
0024 #include <KWayland/Client/seat.h>
0025 #include <KWayland/Client/surface.h>
0026 #include <KWindowSystem>
0027 
0028 #include <linux/input.h>
0029 
0030 namespace KWin
0031 {
0032 
0033 static const QString s_socketName = QStringLiteral("wayland_test_kwin_internal_window-0");
0034 
0035 class InternalWindowTest : public QObject
0036 {
0037     Q_OBJECT
0038 private Q_SLOTS:
0039     void initTestCase();
0040     void init();
0041     void cleanup();
0042     void testEnterLeave();
0043     void testPointerPressRelease();
0044     void testPointerAxis();
0045     void testKeyboard_data();
0046     void testKeyboard();
0047     void testKeyboardShowWithoutActivating();
0048     void testKeyboardTriggersLeave();
0049     void testTouch();
0050     void testOpacity();
0051     void testMove();
0052     void testSkipCloseAnimation_data();
0053     void testSkipCloseAnimation();
0054     void testModifierClickUnrestrictedMove();
0055     void testModifierScroll();
0056     void testPopup();
0057     void testScale();
0058     void testEffectWindow();
0059     void testReentrantMoveResize();
0060     void testDismissPopup();
0061 };
0062 
0063 class HelperWindow : public QRasterWindow
0064 {
0065     Q_OBJECT
0066 public:
0067     HelperWindow();
0068     ~HelperWindow() override;
0069 
0070     QPoint latestGlobalMousePos() const
0071     {
0072         return m_latestGlobalMousePos;
0073     }
0074     Qt::MouseButtons pressedButtons() const
0075     {
0076         return m_pressedButtons;
0077     }
0078 
0079 Q_SIGNALS:
0080     void entered();
0081     void left();
0082     void mouseMoved(const QPoint &global);
0083     void mousePressed();
0084     void mouseReleased();
0085     void wheel();
0086     void keyPressed();
0087     void keyReleased();
0088 
0089 protected:
0090     void paintEvent(QPaintEvent *event) override;
0091     bool event(QEvent *event) override;
0092     void mouseMoveEvent(QMouseEvent *event) override;
0093     void mousePressEvent(QMouseEvent *event) override;
0094     void mouseReleaseEvent(QMouseEvent *event) override;
0095     void wheelEvent(QWheelEvent *event) override;
0096     void keyPressEvent(QKeyEvent *event) override;
0097     void keyReleaseEvent(QKeyEvent *event) override;
0098 
0099 private:
0100     QPoint m_latestGlobalMousePos;
0101     Qt::MouseButtons m_pressedButtons = Qt::MouseButtons();
0102 };
0103 
0104 HelperWindow::HelperWindow()
0105     : QRasterWindow(nullptr)
0106 {
0107     setFlags(Qt::FramelessWindowHint);
0108 }
0109 
0110 HelperWindow::~HelperWindow() = default;
0111 
0112 void HelperWindow::paintEvent(QPaintEvent *event)
0113 {
0114     QPainter p(this);
0115     p.fillRect(0, 0, width(), height(), Qt::red);
0116 }
0117 
0118 bool HelperWindow::event(QEvent *event)
0119 {
0120     if (event->type() == QEvent::Enter) {
0121         Q_EMIT entered();
0122     }
0123     if (event->type() == QEvent::Leave) {
0124         Q_EMIT left();
0125     }
0126     return QRasterWindow::event(event);
0127 }
0128 
0129 void HelperWindow::mouseMoveEvent(QMouseEvent *event)
0130 {
0131     m_latestGlobalMousePos = event->globalPos();
0132     Q_EMIT mouseMoved(event->globalPos());
0133 }
0134 
0135 void HelperWindow::mousePressEvent(QMouseEvent *event)
0136 {
0137     m_latestGlobalMousePos = event->globalPos();
0138     m_pressedButtons = event->buttons();
0139     Q_EMIT mousePressed();
0140 }
0141 
0142 void HelperWindow::mouseReleaseEvent(QMouseEvent *event)
0143 {
0144     m_latestGlobalMousePos = event->globalPos();
0145     m_pressedButtons = event->buttons();
0146     Q_EMIT mouseReleased();
0147 }
0148 
0149 void HelperWindow::wheelEvent(QWheelEvent *event)
0150 {
0151     Q_EMIT wheel();
0152 }
0153 
0154 void HelperWindow::keyPressEvent(QKeyEvent *event)
0155 {
0156     Q_EMIT keyPressed();
0157 }
0158 
0159 void HelperWindow::keyReleaseEvent(QKeyEvent *event)
0160 {
0161     Q_EMIT keyReleased();
0162 }
0163 
0164 void InternalWindowTest::initTestCase()
0165 {
0166     qRegisterMetaType<KWin::Window *>();
0167     qRegisterMetaType<KWin::InternalWindow *>();
0168     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0169     QVERIFY(waylandServer()->init(s_socketName));
0170     Test::setOutputConfig({
0171         QRect(0, 0, 1280, 1024),
0172         QRect(1280, 0, 1280, 1024),
0173     });
0174     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
0175 
0176     kwinApp()->start();
0177     QVERIFY(applicationStartedSpy.wait());
0178     const auto outputs = workspace()->outputs();
0179     QCOMPARE(outputs.count(), 2);
0180     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0181     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0182 }
0183 
0184 void InternalWindowTest::init()
0185 {
0186     input()->pointer()->warp(QPoint(512, 512));
0187     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat));
0188     QVERIFY(Test::waitForWaylandKeyboard());
0189 }
0190 
0191 void InternalWindowTest::cleanup()
0192 {
0193     Test::destroyWaylandConnection();
0194 }
0195 
0196 void InternalWindowTest::testEnterLeave()
0197 {
0198     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0199     HelperWindow win;
0200     QVERIFY(!workspace()->findInternal(nullptr));
0201     QVERIFY(!workspace()->findInternal(&win));
0202     win.setGeometry(0, 0, 100, 100);
0203     win.show();
0204 
0205     QTRY_COMPARE(windowAddedSpy.count(), 1);
0206     QVERIFY(!workspace()->activeWindow());
0207     InternalWindow *window = windowAddedSpy.first().first().value<InternalWindow *>();
0208     QVERIFY(window);
0209     QVERIFY(window->isInternal());
0210     QVERIFY(!window->isDecorated());
0211     QCOMPARE(workspace()->findInternal(&win), window);
0212     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 100));
0213     QVERIFY(window->isShown());
0214     QVERIFY(workspace()->stackingOrder().contains(window));
0215 
0216     QSignalSpy enterSpy(&win, &HelperWindow::entered);
0217     QSignalSpy leaveSpy(&win, &HelperWindow::left);
0218     QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved);
0219 
0220     quint32 timestamp = 1;
0221     Test::pointerMotion(QPoint(50, 50), timestamp++);
0222     QTRY_COMPARE(moveSpy.count(), 1);
0223 
0224     Test::pointerMotion(QPoint(60, 50), timestamp++);
0225     QTRY_COMPARE(moveSpy.count(), 2);
0226     QCOMPARE(moveSpy[1].first().toPoint(), QPoint(60, 50));
0227 
0228     Test::pointerMotion(QPoint(101, 50), timestamp++);
0229     QTRY_COMPARE(leaveSpy.count(), 1);
0230 
0231     // set a mask on the window
0232     win.setMask(QRegion(10, 20, 30, 40));
0233     // outside the mask we should not get an enter
0234     Test::pointerMotion(QPoint(5, 5), timestamp++);
0235     QVERIFY(!enterSpy.wait(100));
0236     QCOMPARE(enterSpy.count(), 1);
0237     // inside the mask we should still get an enter
0238     Test::pointerMotion(QPoint(25, 27), timestamp++);
0239     QTRY_COMPARE(enterSpy.count(), 2);
0240 }
0241 
0242 void InternalWindowTest::testPointerPressRelease()
0243 {
0244     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0245     HelperWindow win;
0246     win.setGeometry(0, 0, 100, 100);
0247     win.show();
0248     QSignalSpy pressSpy(&win, &HelperWindow::mousePressed);
0249     QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased);
0250 
0251     QTRY_COMPARE(windowAddedSpy.count(), 1);
0252 
0253     quint32 timestamp = 1;
0254     Test::pointerMotion(QPoint(50, 50), timestamp++);
0255 
0256     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0257     QTRY_COMPARE(pressSpy.count(), 1);
0258     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0259     QTRY_COMPARE(releaseSpy.count(), 1);
0260 }
0261 
0262 void InternalWindowTest::testPointerAxis()
0263 {
0264     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0265     HelperWindow win;
0266     win.setGeometry(0, 0, 100, 100);
0267     win.show();
0268     QSignalSpy wheelSpy(&win, &HelperWindow::wheel);
0269     QTRY_COMPARE(windowAddedSpy.count(), 1);
0270 
0271     quint32 timestamp = 1;
0272     Test::pointerMotion(QPoint(50, 50), timestamp++);
0273 
0274     Test::pointerAxisVertical(5.0, timestamp++);
0275     QTRY_COMPARE(wheelSpy.count(), 1);
0276     Test::pointerAxisHorizontal(5.0, timestamp++);
0277     QTRY_COMPARE(wheelSpy.count(), 2);
0278 }
0279 
0280 void InternalWindowTest::testKeyboard_data()
0281 {
0282     QTest::addColumn<QPoint>("cursorPos");
0283 
0284     QTest::newRow("on Window") << QPoint(50, 50);
0285     QTest::newRow("outside Window") << QPoint(250, 250);
0286 }
0287 
0288 void InternalWindowTest::testKeyboard()
0289 {
0290     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0291     HelperWindow win;
0292     win.setGeometry(0, 0, 100, 100);
0293     win.show();
0294     QSignalSpy pressSpy(&win, &HelperWindow::keyPressed);
0295     QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased);
0296     QTRY_COMPARE(windowAddedSpy.count(), 1);
0297     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0298     QVERIFY(internalWindow);
0299     QVERIFY(internalWindow->isInternal());
0300     QVERIFY(internalWindow->readyForPainting());
0301 
0302     quint32 timestamp = 1;
0303     QFETCH(QPoint, cursorPos);
0304     Test::pointerMotion(cursorPos, timestamp++);
0305 
0306     Test::keyboardKeyPressed(KEY_A, timestamp++);
0307     QTRY_COMPARE(pressSpy.count(), 1);
0308     QCOMPARE(releaseSpy.count(), 0);
0309     Test::keyboardKeyReleased(KEY_A, timestamp++);
0310     QTRY_COMPARE(releaseSpy.count(), 1);
0311     QCOMPARE(pressSpy.count(), 1);
0312 }
0313 
0314 void InternalWindowTest::testKeyboardShowWithoutActivating()
0315 {
0316     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0317     HelperWindow win;
0318     win.setProperty("_q_showWithoutActivating", true);
0319     win.setGeometry(0, 0, 100, 100);
0320     win.show();
0321     QSignalSpy pressSpy(&win, &HelperWindow::keyPressed);
0322     QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased);
0323     QTRY_COMPARE(windowAddedSpy.count(), 1);
0324     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0325     QVERIFY(internalWindow);
0326     QVERIFY(internalWindow->isInternal());
0327     QVERIFY(internalWindow->readyForPainting());
0328 
0329     quint32 timestamp = 1;
0330     const QPoint cursorPos = QPoint(50, 50);
0331     Test::pointerMotion(cursorPos, timestamp++);
0332 
0333     Test::keyboardKeyPressed(KEY_A, timestamp++);
0334     QCOMPARE(pressSpy.count(), 0);
0335     QVERIFY(!pressSpy.wait(100));
0336     QCOMPARE(releaseSpy.count(), 0);
0337     Test::keyboardKeyReleased(KEY_A, timestamp++);
0338     QCOMPARE(releaseSpy.count(), 0);
0339     QVERIFY(!releaseSpy.wait(100));
0340     QCOMPARE(pressSpy.count(), 0);
0341 }
0342 
0343 void InternalWindowTest::testKeyboardTriggersLeave()
0344 {
0345     // this test verifies that a leave event is sent to a window when an internal window
0346     // gets a key event
0347     std::unique_ptr<KWayland::Client::Keyboard> keyboard(Test::waylandSeat()->createKeyboard());
0348     QVERIFY(keyboard != nullptr);
0349     QVERIFY(keyboard->isValid());
0350     QSignalSpy enteredSpy(keyboard.get(), &KWayland::Client::Keyboard::entered);
0351     QSignalSpy leftSpy(keyboard.get(), &KWayland::Client::Keyboard::left);
0352     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0353     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0354 
0355     // now let's render
0356     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0357     QVERIFY(window);
0358     QVERIFY(window->isActive());
0359     QVERIFY(!window->isInternal());
0360 
0361     if (enteredSpy.isEmpty()) {
0362         QVERIFY(enteredSpy.wait());
0363     }
0364     QCOMPARE(enteredSpy.count(), 1);
0365 
0366     // create internal window
0367     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0368     HelperWindow win;
0369     win.setGeometry(0, 0, 100, 100);
0370     win.show();
0371     QSignalSpy pressSpy(&win, &HelperWindow::keyPressed);
0372     QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased);
0373     QTRY_COMPARE(windowAddedSpy.count(), 1);
0374     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0375     QVERIFY(internalWindow);
0376     QVERIFY(internalWindow->isInternal());
0377     QVERIFY(internalWindow->readyForPainting());
0378 
0379     QVERIFY(leftSpy.isEmpty());
0380     QVERIFY(!leftSpy.wait(100));
0381 
0382     // now let's trigger a key, which should result in a leave
0383     quint32 timestamp = 1;
0384     Test::keyboardKeyPressed(KEY_A, timestamp++);
0385     QVERIFY(leftSpy.wait());
0386     QCOMPARE(pressSpy.count(), 1);
0387 
0388     Test::keyboardKeyReleased(KEY_A, timestamp++);
0389     QTRY_COMPARE(releaseSpy.count(), 1);
0390 
0391     // after hiding the internal window, next key press should trigger an enter
0392     win.hide();
0393     Test::keyboardKeyPressed(KEY_A, timestamp++);
0394     QVERIFY(enteredSpy.wait());
0395     Test::keyboardKeyReleased(KEY_A, timestamp++);
0396 
0397     // Destroy the test window.
0398     shellSurface.reset();
0399     QVERIFY(Test::waitForWindowClosed(window));
0400 }
0401 
0402 void InternalWindowTest::testTouch()
0403 {
0404     // touch events for internal windows are emulated through mouse events
0405     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0406     HelperWindow win;
0407     win.setGeometry(0, 0, 100, 100);
0408     win.show();
0409     QTRY_COMPARE(windowAddedSpy.count(), 1);
0410 
0411     QSignalSpy pressSpy(&win, &HelperWindow::mousePressed);
0412     QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased);
0413     QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved);
0414 
0415     quint32 timestamp = 1;
0416     QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
0417     Test::touchDown(0, QPointF(50, 50), timestamp++);
0418     QCOMPARE(pressSpy.count(), 1);
0419     QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
0420     QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
0421 
0422     // further touch down should not trigger
0423     Test::touchDown(1, QPointF(75, 75), timestamp++);
0424     QCOMPARE(pressSpy.count(), 1);
0425     Test::touchUp(1, timestamp++);
0426     QCOMPARE(releaseSpy.count(), 0);
0427     QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
0428     QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
0429 
0430     // another press
0431     Test::touchDown(1, QPointF(10, 10), timestamp++);
0432     QCOMPARE(pressSpy.count(), 1);
0433     QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
0434     QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
0435 
0436     // simulate the move
0437     QCOMPARE(moveSpy.count(), 0);
0438     Test::touchMotion(0, QPointF(80, 90), timestamp++);
0439     QCOMPARE(moveSpy.count(), 1);
0440     QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
0441     QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
0442 
0443     // move on other ID should not do anything
0444     Test::touchMotion(1, QPointF(20, 30), timestamp++);
0445     QCOMPARE(moveSpy.count(), 1);
0446     QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
0447     QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
0448 
0449     // now up our main point
0450     Test::touchUp(0, timestamp++);
0451     QCOMPARE(releaseSpy.count(), 1);
0452     QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
0453     QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
0454 
0455     // and up the additional point
0456     Test::touchUp(1, timestamp++);
0457     QCOMPARE(releaseSpy.count(), 1);
0458     QCOMPARE(moveSpy.count(), 1);
0459     QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
0460     QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
0461 }
0462 
0463 void InternalWindowTest::testOpacity()
0464 {
0465     // this test verifies that opacity is properly synced from QWindow to InternalClient
0466     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0467     HelperWindow win;
0468     win.setOpacity(0.5);
0469     win.setGeometry(0, 0, 100, 100);
0470     win.show();
0471     QTRY_COMPARE(windowAddedSpy.count(), 1);
0472     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0473     QVERIFY(internalWindow);
0474     QVERIFY(internalWindow->isInternal());
0475     QCOMPARE(internalWindow->opacity(), 0.5);
0476 
0477     QSignalSpy opacityChangedSpy(internalWindow, &InternalWindow::opacityChanged);
0478     win.setOpacity(0.75);
0479     QCOMPARE(opacityChangedSpy.count(), 1);
0480     QCOMPARE(internalWindow->opacity(), 0.75);
0481 }
0482 
0483 void InternalWindowTest::testMove()
0484 {
0485     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0486     HelperWindow win;
0487     win.setOpacity(0.5);
0488     win.setGeometry(0, 0, 100, 100);
0489     win.show();
0490     QTRY_COMPARE(windowAddedSpy.count(), 1);
0491     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0492     QVERIFY(internalWindow);
0493     QCOMPARE(internalWindow->frameGeometry(), QRect(0, 0, 100, 100));
0494 
0495     // normal move should be synced
0496     internalWindow->move(QPoint(5, 10));
0497     QCOMPARE(internalWindow->frameGeometry(), QRect(5, 10, 100, 100));
0498     QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100));
0499     // another move should also be synced
0500     internalWindow->move(QPoint(10, 20));
0501     QCOMPARE(internalWindow->frameGeometry(), QRect(10, 20, 100, 100));
0502     QTRY_COMPARE(win.geometry(), QRect(10, 20, 100, 100));
0503 
0504     // now move with a Geometry update blocker
0505     {
0506         GeometryUpdatesBlocker blocker(internalWindow);
0507         internalWindow->move(QPoint(5, 10));
0508         // not synced!
0509         QCOMPARE(win.geometry(), QRect(10, 20, 100, 100));
0510     }
0511     // after destroying the blocker it should be synced
0512     QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100));
0513 }
0514 
0515 void InternalWindowTest::testSkipCloseAnimation_data()
0516 {
0517     QTest::addColumn<bool>("initial");
0518 
0519     QTest::newRow("set") << true;
0520     QTest::newRow("not set") << false;
0521 }
0522 
0523 void InternalWindowTest::testSkipCloseAnimation()
0524 {
0525     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0526     HelperWindow win;
0527     win.setOpacity(0.5);
0528     win.setGeometry(0, 0, 100, 100);
0529     QFETCH(bool, initial);
0530     win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial);
0531     win.show();
0532     QTRY_COMPARE(windowAddedSpy.count(), 1);
0533     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0534     QVERIFY(internalWindow);
0535     QCOMPARE(internalWindow->skipsCloseAnimation(), initial);
0536     QSignalSpy skipCloseChangedSpy(internalWindow, &Window::skipCloseAnimationChanged);
0537     win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", !initial);
0538     QCOMPARE(skipCloseChangedSpy.count(), 1);
0539     QCOMPARE(internalWindow->skipsCloseAnimation(), !initial);
0540     win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial);
0541     QCOMPARE(skipCloseChangedSpy.count(), 2);
0542     QCOMPARE(internalWindow->skipsCloseAnimation(), initial);
0543 }
0544 
0545 void InternalWindowTest::testModifierClickUnrestrictedMove()
0546 {
0547     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0548     HelperWindow win;
0549     win.setGeometry(0, 0, 100, 100);
0550     win.setFlags(win.flags() & ~Qt::FramelessWindowHint);
0551     win.show();
0552     QTRY_COMPARE(windowAddedSpy.count(), 1);
0553     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0554     QVERIFY(internalWindow);
0555     QVERIFY(internalWindow->isDecorated());
0556 
0557     KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
0558     group.writeEntry("CommandAllKey", "Meta");
0559     group.writeEntry("CommandAll1", "Move");
0560     group.writeEntry("CommandAll2", "Move");
0561     group.writeEntry("CommandAll3", "Move");
0562     group.sync();
0563     workspace()->slotReconfigure();
0564     QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
0565     QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove);
0566     QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove);
0567     QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove);
0568 
0569     // move cursor on window
0570     input()->pointer()->warp(internalWindow->frameGeometry().center());
0571 
0572     // simulate modifier+click
0573     quint32 timestamp = 1;
0574     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0575     QVERIFY(!internalWindow->isInteractiveMove());
0576     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0577     QVERIFY(internalWindow->isInteractiveMove());
0578     // release modifier should not change it
0579     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0580     QVERIFY(internalWindow->isInteractiveMove());
0581     // but releasing the key should end move/resize
0582     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0583     QVERIFY(!internalWindow->isInteractiveMove());
0584 }
0585 
0586 void InternalWindowTest::testModifierScroll()
0587 {
0588     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0589     HelperWindow win;
0590     win.setGeometry(0, 0, 100, 100);
0591     win.setFlags(win.flags() & ~Qt::FramelessWindowHint);
0592     win.show();
0593     QTRY_COMPARE(windowAddedSpy.count(), 1);
0594     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0595     QVERIFY(internalWindow);
0596     QVERIFY(internalWindow->isDecorated());
0597 
0598     KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
0599     group.writeEntry("CommandAllKey", "Meta");
0600     group.writeEntry("CommandAllWheel", "change opacity");
0601     group.sync();
0602     workspace()->slotReconfigure();
0603 
0604     // move cursor on window
0605     input()->pointer()->warp(internalWindow->frameGeometry().center());
0606 
0607     // set the opacity to 0.5
0608     internalWindow->setOpacity(0.5);
0609     QCOMPARE(internalWindow->opacity(), 0.5);
0610     quint32 timestamp = 1;
0611     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0612     Test::pointerAxisVertical(-5, timestamp++);
0613     QCOMPARE(internalWindow->opacity(), 0.6);
0614     Test::pointerAxisVertical(5, timestamp++);
0615     QCOMPARE(internalWindow->opacity(), 0.5);
0616     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0617 }
0618 
0619 void InternalWindowTest::testPopup()
0620 {
0621     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0622     HelperWindow win;
0623     win.setGeometry(0, 0, 100, 100);
0624     win.setFlags(win.flags() | Qt::Popup);
0625     win.show();
0626     QTRY_COMPARE(windowAddedSpy.count(), 1);
0627     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0628     QVERIFY(internalWindow);
0629     QCOMPARE(internalWindow->isPopupWindow(), true);
0630 }
0631 
0632 void InternalWindowTest::testScale()
0633 {
0634     Test::setOutputConfig({
0635         Test::OutputInfo{
0636             .geometry = QRect(0, 0, 1280, 1024),
0637             .scale = 2.0,
0638         },
0639         Test::OutputInfo{
0640             .geometry = QRect(1280, 0, 1280, 1024),
0641             .scale = 2.0,
0642         },
0643     });
0644 
0645     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0646     HelperWindow win;
0647     win.setGeometry(0, 0, 100, 100);
0648     win.setFlags(win.flags() | Qt::Popup);
0649     win.show();
0650     QCOMPARE(win.devicePixelRatio(), 2.0);
0651     QTRY_COMPARE(windowAddedSpy.count(), 1);
0652     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0653     QCOMPARE(internalWindow->bufferScale(), 2);
0654 }
0655 
0656 void InternalWindowTest::testEffectWindow()
0657 {
0658     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0659     HelperWindow win;
0660     win.setGeometry(0, 0, 100, 100);
0661     win.show();
0662     QTRY_COMPARE(windowAddedSpy.count(), 1);
0663     auto internalWindow = windowAddedSpy.first().first().value<InternalWindow *>();
0664     QVERIFY(internalWindow);
0665     QVERIFY(internalWindow->effectWindow());
0666     QCOMPARE(internalWindow->effectWindow()->internalWindow(), &win);
0667 
0668     QCOMPARE(effects->findWindow(&win), internalWindow->effectWindow());
0669     QCOMPARE(effects->findWindow(&win)->internalWindow(), &win);
0670 }
0671 
0672 void InternalWindowTest::testReentrantMoveResize()
0673 {
0674     // This test verifies that calling moveResize() from a slot connected directly
0675     // to the frameGeometryChanged() signal won't cause an infinite recursion.
0676 
0677     // Create an internal window.
0678     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0679     HelperWindow win;
0680     win.setGeometry(0, 0, 100, 100);
0681     win.show();
0682     QTRY_COMPARE(windowAddedSpy.count(), 1);
0683     auto window = windowAddedSpy.first().first().value<InternalWindow *>();
0684     QVERIFY(window);
0685     QCOMPARE(window->pos(), QPoint(0, 0));
0686 
0687     // Let's pretend that there is a script that really wants the window to be at (100, 100).
0688     connect(window, &Window::frameGeometryChanged, this, [window]() {
0689         window->moveResize(QRectF(QPointF(100, 100), window->size()));
0690     });
0691 
0692     // Trigger the lambda above.
0693     window->move(QPoint(40, 50));
0694 
0695     // Eventually, the window will end up at (100, 100).
0696     QCOMPARE(window->pos(), QPoint(100, 100));
0697 }
0698 
0699 void InternalWindowTest::testDismissPopup()
0700 {
0701     // This test verifies that a popup window created by the compositor will be dismissed
0702     // when user clicks another window.
0703 
0704     // Create a toplevel window.
0705     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0706     HelperWindow clientToplevel;
0707     clientToplevel.setGeometry(0, 0, 100, 100);
0708     clientToplevel.show();
0709     QTRY_COMPARE(windowAddedSpy.count(), 1);
0710     auto serverToplevel = windowAddedSpy.last().first().value<InternalWindow *>();
0711     QVERIFY(serverToplevel);
0712 
0713     // Create a popup window.
0714     QRasterWindow clientPopup;
0715     clientPopup.setFlag(Qt::Popup);
0716     clientPopup.setTransientParent(&clientToplevel);
0717     clientPopup.setGeometry(0, 0, 50, 50);
0718     clientPopup.show();
0719     QTRY_COMPARE(windowAddedSpy.count(), 2);
0720     auto serverPopup = windowAddedSpy.last().first().value<InternalWindow *>();
0721     QVERIFY(serverPopup);
0722 
0723     // Create the other window to click
0724     HelperWindow otherClientToplevel;
0725     otherClientToplevel.setGeometry(100, 100, 100, 100);
0726     otherClientToplevel.show();
0727     QTRY_COMPARE(windowAddedSpy.count(), 3);
0728     auto serverOtherToplevel = windowAddedSpy.last().first().value<InternalWindow *>();
0729     QVERIFY(serverOtherToplevel);
0730 
0731     // Click somewhere outside the popup window.
0732     QSignalSpy popupClosedSpy(serverPopup, &InternalWindow::closed);
0733     quint32 timestamp = 0;
0734     Test::pointerMotion(serverOtherToplevel->frameGeometry().center(), timestamp++);
0735     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0736     QTRY_COMPARE(popupClosedSpy.count(), 1);
0737 }
0738 
0739 }
0740 
0741 WAYLANDTEST_MAIN(KWin::InternalWindowTest)
0742 #include "internal_window.moc"