File indexing completed on 2024-05-12 05:30:33

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 "pointer_input.h"
0013 #include "wayland/seat.h"
0014 #include "wayland_server.h"
0015 #include "window.h"
0016 #include "workspace.h"
0017 
0018 #include <KWayland/Client/compositor.h>
0019 #include <KWayland/Client/connection_thread.h>
0020 #include <KWayland/Client/event_queue.h>
0021 #include <KWayland/Client/pointer.h>
0022 #include <KWayland/Client/registry.h>
0023 #include <KWayland/Client/seat.h>
0024 #include <KWayland/Client/shm_pool.h>
0025 #include <KWayland/Client/surface.h>
0026 
0027 #include <QSignalSpy>
0028 
0029 namespace KWin
0030 {
0031 
0032 static const QString s_socketName = QStringLiteral("wayland_test_kwin_input_stacking_order-0");
0033 
0034 class InputStackingOrderTest : public QObject
0035 {
0036     Q_OBJECT
0037 private Q_SLOTS:
0038     void initTestCase();
0039     void init();
0040     void cleanup();
0041     void testPointerFocusUpdatesOnStackingOrderChange();
0042 
0043 private:
0044     void render(KWayland::Client::Surface *surface);
0045 };
0046 
0047 void InputStackingOrderTest::initTestCase()
0048 {
0049     qRegisterMetaType<KWin::Window *>();
0050     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0051     QVERIFY(waylandServer()->init(s_socketName));
0052     Test::setOutputConfig({
0053         QRect(0, 0, 1280, 1024),
0054         QRect(1280, 0, 1280, 1024),
0055     });
0056 
0057     kwinApp()->start();
0058     QVERIFY(applicationStartedSpy.wait());
0059     const auto outputs = workspace()->outputs();
0060     QCOMPARE(outputs.count(), 2);
0061     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0062     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0063     setenv("QT_QPA_PLATFORM", "wayland", true);
0064 }
0065 
0066 void InputStackingOrderTest::init()
0067 {
0068     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat));
0069     QVERIFY(Test::waitForWaylandPointer());
0070 
0071     workspace()->setActiveOutput(QPoint(640, 512));
0072     input()->pointer()->warp(QPoint(640, 512));
0073 }
0074 
0075 void InputStackingOrderTest::cleanup()
0076 {
0077     Test::destroyWaylandConnection();
0078 }
0079 
0080 void InputStackingOrderTest::render(KWayland::Client::Surface *surface)
0081 {
0082     Test::render(surface, QSize(100, 50), Qt::blue);
0083     Test::flushWaylandConnection();
0084 }
0085 
0086 void InputStackingOrderTest::testPointerFocusUpdatesOnStackingOrderChange()
0087 {
0088     // this test creates two windows which overlap
0089     // the pointer is in the overlapping area which means the top most window has focus
0090     // as soon as the top most window gets lowered the window should lose focus and the
0091     // other window should gain focus without a mouse event in between
0092 
0093     // create pointer and signal spy for enter and leave signals
0094     auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
0095     QVERIFY(pointer);
0096     QVERIFY(pointer->isValid());
0097     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
0098     QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
0099 
0100     // now create the two windows and make them overlap
0101     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0102     std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
0103     QVERIFY(surface1);
0104     Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
0105     QVERIFY(shellSurface1);
0106     render(surface1.get());
0107     QVERIFY(windowAddedSpy.wait());
0108     Window *window1 = workspace()->activeWindow();
0109     QVERIFY(window1);
0110 
0111     std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
0112     QVERIFY(surface2);
0113     Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
0114     QVERIFY(shellSurface2);
0115     render(surface2.get());
0116     QVERIFY(windowAddedSpy.wait());
0117 
0118     Window *window2 = workspace()->activeWindow();
0119     QVERIFY(window2);
0120     QVERIFY(window1 != window2);
0121 
0122     // now make windows overlap
0123     window2->move(window1->pos());
0124     QCOMPARE(window1->frameGeometry(), window2->frameGeometry());
0125 
0126     // enter
0127     Test::pointerMotion(QPointF(25, 25), 1);
0128     QVERIFY(enteredSpy.wait());
0129     QCOMPARE(enteredSpy.count(), 1);
0130     // window 2 should have focus
0131     QCOMPARE(pointer->enteredSurface(), surface2.get());
0132     // also on the server
0133     QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface());
0134 
0135     // raise window 1 above window 2
0136     QVERIFY(leftSpy.isEmpty());
0137     workspace()->raiseWindow(window1);
0138     // should send leave to window2
0139     QVERIFY(leftSpy.wait());
0140     QCOMPARE(leftSpy.count(), 1);
0141     // and an enter to window1
0142     QCOMPARE(enteredSpy.count(), 2);
0143     QCOMPARE(pointer->enteredSurface(), surface1.get());
0144     QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window1->surface());
0145 
0146     // let's destroy window1, that should pass focus to window2 again
0147     QSignalSpy windowClosedSpy(window1, &Window::closed);
0148     surface1.reset();
0149     QVERIFY(windowClosedSpy.wait());
0150     QVERIFY(enteredSpy.wait());
0151     QCOMPARE(enteredSpy.count(), 3);
0152     QCOMPARE(pointer->enteredSurface(), surface2.get());
0153     QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface());
0154 }
0155 
0156 }
0157 
0158 WAYLANDTEST_MAIN(KWin::InputStackingOrderTest)
0159 #include "input_stacking_order.moc"