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"