File indexing completed on 2024-11-10 04:56:12

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 "workspace.h"
0016 #include "x11window.h"
0017 
0018 #include <QAbstractEventDispatcher>
0019 #include <QSocketNotifier>
0020 
0021 #include <netwm.h>
0022 #include <xcb/xcb_icccm.h>
0023 
0024 namespace KWin
0025 {
0026 
0027 static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_input-0");
0028 
0029 class XWaylandInputTest : public QObject
0030 {
0031     Q_OBJECT
0032 private Q_SLOTS:
0033     void initTestCase();
0034     void init();
0035     void testPointerEnterLeaveSsd();
0036     void testPointerEventLeaveCsd();
0037 };
0038 
0039 void XWaylandInputTest::initTestCase()
0040 {
0041     qRegisterMetaType<KWin::Window *>();
0042     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0043     QVERIFY(waylandServer()->init(s_socketName));
0044     Test::setOutputConfig({
0045         QRect(0, 0, 1280, 1024),
0046         QRect(1280, 0, 1280, 1024),
0047     });
0048 
0049     kwinApp()->start();
0050     QVERIFY(applicationStartedSpy.wait());
0051     const auto outputs = workspace()->outputs();
0052     QCOMPARE(outputs.count(), 2);
0053     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0054     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0055     setenv("QT_QPA_PLATFORM", "wayland", true);
0056 }
0057 
0058 void XWaylandInputTest::init()
0059 {
0060     workspace()->setActiveOutput(QPoint(640, 512));
0061     input()->pointer()->warp(QPoint(640, 512));
0062 
0063     QVERIFY(waylandServer()->windows().isEmpty());
0064 }
0065 
0066 class X11EventReaderHelper : public QObject
0067 {
0068     Q_OBJECT
0069 public:
0070     X11EventReaderHelper(xcb_connection_t *c);
0071 
0072 Q_SIGNALS:
0073     void entered(const QPoint &localPoint);
0074     void left(const QPoint &localPoint);
0075 
0076 private:
0077     void processXcbEvents();
0078     xcb_connection_t *m_connection;
0079     QSocketNotifier *m_notifier;
0080 };
0081 
0082 X11EventReaderHelper::X11EventReaderHelper(xcb_connection_t *c)
0083     : QObject()
0084     , m_connection(c)
0085     , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this))
0086 {
0087     connect(m_notifier, &QSocketNotifier::activated, this, &X11EventReaderHelper::processXcbEvents);
0088     connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11EventReaderHelper::processXcbEvents);
0089     connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &X11EventReaderHelper::processXcbEvents);
0090 }
0091 
0092 void X11EventReaderHelper::processXcbEvents()
0093 {
0094     while (auto event = xcb_poll_for_event(m_connection)) {
0095         const uint8_t eventType = event->response_type & ~0x80;
0096         switch (eventType) {
0097         case XCB_ENTER_NOTIFY: {
0098             auto enterEvent = reinterpret_cast<xcb_enter_notify_event_t *>(event);
0099             Q_EMIT entered(QPoint(enterEvent->event_x, enterEvent->event_y));
0100             break;
0101         }
0102         case XCB_LEAVE_NOTIFY: {
0103             auto leaveEvent = reinterpret_cast<xcb_leave_notify_event_t *>(event);
0104             Q_EMIT left(QPoint(leaveEvent->event_x, leaveEvent->event_y));
0105             break;
0106         }
0107         }
0108         free(event);
0109     }
0110     xcb_flush(m_connection);
0111 }
0112 
0113 void XWaylandInputTest::testPointerEnterLeaveSsd()
0114 {
0115     // this test simulates a pointer enter and pointer leave on a server-side decorated X11 window
0116 
0117     // create the test window
0118     Test::XcbConnectionPtr c = Test::createX11Connection();
0119     QVERIFY(!xcb_connection_has_error(c.get()));
0120     if (xcb_get_setup(c.get())->release_number < 11800000) {
0121         QSKIP("XWayland 1.18 required");
0122     }
0123 
0124     xcb_warp_pointer(connection(), XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 640, 512);
0125     xcb_flush(connection());
0126 
0127     X11EventReaderHelper eventReader(c.get());
0128     QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered);
0129     QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left);
0130 
0131     xcb_window_t windowId = xcb_generate_id(c.get());
0132     const QRect windowGeometry = QRect(0, 0, 100, 200);
0133     const uint32_t values[] = {
0134         XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW};
0135     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0136                       windowGeometry.x(),
0137                       windowGeometry.y(),
0138                       windowGeometry.width(),
0139                       windowGeometry.height(),
0140                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values);
0141     xcb_size_hints_t hints;
0142     memset(&hints, 0, sizeof(hints));
0143     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0144     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0145     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0146     NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties);
0147     info.setWindowType(NET::Normal);
0148     xcb_map_window(c.get(), windowId);
0149     xcb_flush(c.get());
0150 
0151     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0152     QVERIFY(windowCreatedSpy.wait());
0153     X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
0154     QVERIFY(window);
0155     QVERIFY(window->isDecorated());
0156     QVERIFY(!window->hasStrut());
0157     QVERIFY(!window->readyForPainting());
0158 
0159     QMetaObject::invokeMethod(window, "setReadyForPainting");
0160     QVERIFY(window->readyForPainting());
0161     QVERIFY(Test::waitForWaylandSurface(window));
0162 
0163     // move pointer into the window, should trigger an enter
0164     QVERIFY(!exclusiveContains(window->frameGeometry(), Cursors::self()->mouse()->pos()));
0165     QVERIFY(enteredSpy.isEmpty());
0166     input()->pointer()->warp(window->frameGeometry().center().toPoint());
0167     QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface());
0168     QVERIFY(enteredSpy.wait());
0169     QCOMPARE(enteredSpy.last().first().toPoint(), (window->frameGeometry().center() - QPointF(window->frameMargins().left(), window->frameMargins().top())).toPoint());
0170 
0171     // move out of window
0172     input()->pointer()->warp(window->frameGeometry().bottomRight() + QPointF(10, 10));
0173     QVERIFY(leftSpy.wait());
0174     QCOMPARE(leftSpy.last().first().toPoint(), (window->frameGeometry().center() - QPointF(window->frameMargins().left(), window->frameMargins().top())).toPoint());
0175 
0176     // destroy window again
0177     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0178     xcb_unmap_window(c.get(), windowId);
0179     xcb_destroy_window(c.get(), windowId);
0180     xcb_flush(c.get());
0181     QVERIFY(windowClosedSpy.wait());
0182 }
0183 
0184 void XWaylandInputTest::testPointerEventLeaveCsd()
0185 {
0186     // this test simulates a pointer enter and pointer leave on a client-side decorated X11 window
0187 
0188     Test::XcbConnectionPtr c = Test::createX11Connection();
0189     QVERIFY(!xcb_connection_has_error(c.get()));
0190 
0191     xcb_warp_pointer(connection(), XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 640, 512);
0192     xcb_flush(connection());
0193 
0194     if (xcb_get_setup(c.get())->release_number < 11800000) {
0195         QSKIP("XWayland 1.18 required");
0196     }
0197     if (!Xcb::Extensions::self()->isShapeAvailable()) {
0198         QSKIP("SHAPE extension is required");
0199     }
0200 
0201     X11EventReaderHelper eventReader(c.get());
0202     QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered);
0203     QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left);
0204 
0205     // Extents of the client-side drop-shadow.
0206     NETStrut clientFrameExtent;
0207     clientFrameExtent.left = 10;
0208     clientFrameExtent.right = 10;
0209     clientFrameExtent.top = 5;
0210     clientFrameExtent.bottom = 20;
0211 
0212     // Need to set the bounding shape in order to create a window without decoration.
0213     xcb_rectangle_t boundingRect;
0214     boundingRect.x = 0;
0215     boundingRect.y = 0;
0216     boundingRect.width = 100 + clientFrameExtent.left + clientFrameExtent.right;
0217     boundingRect.height = 200 + clientFrameExtent.top + clientFrameExtent.bottom;
0218 
0219     xcb_window_t windowId = xcb_generate_id(c.get());
0220     const uint32_t values[] = {
0221         XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW};
0222     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0223                       boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height,
0224                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values);
0225     xcb_size_hints_t hints;
0226     memset(&hints, 0, sizeof(hints));
0227     xcb_icccm_size_hints_set_position(&hints, 1, boundingRect.x, boundingRect.y);
0228     xcb_icccm_size_hints_set_size(&hints, 1, boundingRect.width, boundingRect.height);
0229     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0230     xcb_shape_rectangles(c.get(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING,
0231                          XCB_CLIP_ORDERING_UNSORTED, windowId, 0, 0, 1, &boundingRect);
0232     NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties);
0233     info.setWindowType(NET::Normal);
0234     info.setGtkFrameExtents(clientFrameExtent);
0235     xcb_map_window(c.get(), windowId);
0236     xcb_flush(c.get());
0237 
0238     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0239     QVERIFY(windowCreatedSpy.wait());
0240     X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
0241     QVERIFY(window);
0242     QVERIFY(!window->isDecorated());
0243     QVERIFY(window->isClientSideDecorated());
0244     QCOMPARE(window->bufferGeometry(), QRectF(0, 0, 120, 225));
0245     QCOMPARE(window->frameGeometry(), QRectF(10, 5, 100, 200));
0246 
0247     QMetaObject::invokeMethod(window, "setReadyForPainting");
0248     QVERIFY(window->readyForPainting());
0249     QVERIFY(Test::waitForWaylandSurface(window));
0250 
0251     // Move pointer into the window, should trigger an enter.
0252     QVERIFY(!exclusiveContains(window->frameGeometry(), Cursors::self()->mouse()->pos()));
0253     QVERIFY(enteredSpy.isEmpty());
0254     input()->pointer()->warp(window->frameGeometry().center().toPoint());
0255     QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface());
0256     QVERIFY(enteredSpy.wait());
0257     QCOMPARE(enteredSpy.last().first().toPoint(), QPoint(60, 105));
0258 
0259     // Move out of the window, should trigger a leave.
0260     QVERIFY(leftSpy.isEmpty());
0261     input()->pointer()->warp(window->frameGeometry().bottomRight() + QPoint(100, 100));
0262     QVERIFY(leftSpy.wait());
0263     QCOMPARE(leftSpy.last().first().toPoint(), QPoint(60, 105));
0264 
0265     // Destroy the window.
0266     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0267     xcb_unmap_window(c.get(), windowId);
0268     xcb_destroy_window(c.get(), windowId);
0269     xcb_flush(c.get());
0270     QVERIFY(windowClosedSpy.wait());
0271 }
0272 
0273 }
0274 
0275 WAYLANDTEST_MAIN(KWin::XWaylandInputTest)
0276 #include "xwayland_input_test.moc"