Warning, file /plasma/kwin/autotests/integration/xwayland_input_test.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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