File indexing completed on 2024-05-05 17:35:48

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