File indexing completed on 2025-03-23 13:48: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 "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"