Changeset View
Changeset View
Standalone View
Standalone View
autotests/integration/xwayland_input_test.cpp
Show First 20 Lines • Show All 41 Lines • ▼ Show 20 Line(s) | |||||
42 | 42 | | |||
43 | class XWaylandInputTest : public QObject | 43 | class XWaylandInputTest : public QObject | ||
44 | { | 44 | { | ||
45 | Q_OBJECT | 45 | Q_OBJECT | ||
46 | private Q_SLOTS: | 46 | private Q_SLOTS: | ||
47 | void initTestCase(); | 47 | void initTestCase(); | ||
48 | void init(); | 48 | void init(); | ||
49 | void testPointerEnterLeave(); | 49 | void testPointerEnterLeave(); | ||
50 | void testInputTransformationCsd(); | ||||
50 | }; | 51 | }; | ||
51 | 52 | | |||
52 | void XWaylandInputTest::initTestCase() | 53 | void XWaylandInputTest::initTestCase() | ||
53 | { | 54 | { | ||
54 | qRegisterMetaType<KWin::XdgShellClient *>(); | 55 | qRegisterMetaType<KWin::XdgShellClient *>(); | ||
55 | qRegisterMetaType<KWin::AbstractClient*>(); | 56 | qRegisterMetaType<KWin::AbstractClient*>(); | ||
56 | qRegisterMetaType<KWin::Deleted*>(); | 57 | qRegisterMetaType<KWin::Deleted*>(); | ||
57 | QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); | 58 | QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); | ||
Show All 10 Lines | |||||
68 | setenv("QT_QPA_PLATFORM", "wayland", true); | 69 | setenv("QT_QPA_PLATFORM", "wayland", true); | ||
69 | waylandServer()->initWorkspace(); | 70 | waylandServer()->initWorkspace(); | ||
70 | } | 71 | } | ||
71 | 72 | | |||
72 | void XWaylandInputTest::init() | 73 | void XWaylandInputTest::init() | ||
73 | { | 74 | { | ||
74 | screens()->setCurrent(0); | 75 | screens()->setCurrent(0); | ||
75 | Cursor::setPos(QPoint(640, 512)); | 76 | Cursor::setPos(QPoint(640, 512)); | ||
77 | xcb_warp_pointer(connection(), XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 640, 512); | ||||
78 | xcb_flush(connection()); | ||||
76 | QVERIFY(waylandServer()->clients().isEmpty()); | 79 | QVERIFY(waylandServer()->clients().isEmpty()); | ||
77 | } | 80 | } | ||
78 | 81 | | |||
79 | | ||||
80 | struct XcbConnectionDeleter | 82 | struct XcbConnectionDeleter | ||
81 | { | 83 | { | ||
82 | static inline void cleanup(xcb_connection_t *pointer) | 84 | static inline void cleanup(xcb_connection_t *pointer) | ||
83 | { | 85 | { | ||
84 | xcb_disconnect(pointer); | 86 | xcb_disconnect(pointer); | ||
85 | } | 87 | } | ||
86 | }; | 88 | }; | ||
87 | 89 | | |||
88 | class X11EventReaderHelper : public QObject | 90 | class X11EventReaderHelper : public QObject | ||
89 | { | 91 | { | ||
90 | Q_OBJECT | 92 | Q_OBJECT | ||
91 | public: | 93 | public: | ||
92 | X11EventReaderHelper(xcb_connection_t *c); | 94 | X11EventReaderHelper(xcb_connection_t *c); | ||
93 | 95 | | |||
94 | Q_SIGNALS: | 96 | Q_SIGNALS: | ||
95 | void entered(); | 97 | void entered(const QPoint &localPoint); | ||
96 | void left(); | 98 | void left(const QPoint &localPoint); | ||
99 | void motion(const QPoint &localPoint); | ||||
97 | 100 | | |||
98 | private: | 101 | private: | ||
99 | void processXcbEvents(); | 102 | void processXcbEvents(); | ||
100 | xcb_connection_t *m_connection; | 103 | xcb_connection_t *m_connection; | ||
101 | QSocketNotifier *m_notifier; | 104 | QSocketNotifier *m_notifier; | ||
102 | }; | 105 | }; | ||
103 | 106 | | |||
104 | X11EventReaderHelper::X11EventReaderHelper(xcb_connection_t *c) | 107 | X11EventReaderHelper::X11EventReaderHelper(xcb_connection_t *c) | ||
105 | : QObject() | 108 | : QObject() | ||
106 | , m_connection(c) | 109 | , m_connection(c) | ||
107 | , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this)) | 110 | , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this)) | ||
108 | { | 111 | { | ||
109 | connect(m_notifier, &QSocketNotifier::activated, this, &X11EventReaderHelper::processXcbEvents); | 112 | connect(m_notifier, &QSocketNotifier::activated, this, &X11EventReaderHelper::processXcbEvents); | ||
110 | connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11EventReaderHelper::processXcbEvents); | 113 | connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11EventReaderHelper::processXcbEvents); | ||
111 | connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &X11EventReaderHelper::processXcbEvents); | 114 | connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &X11EventReaderHelper::processXcbEvents); | ||
112 | } | 115 | } | ||
113 | 116 | | |||
114 | void X11EventReaderHelper::processXcbEvents() | 117 | void X11EventReaderHelper::processXcbEvents() | ||
115 | { | 118 | { | ||
116 | while (auto event = xcb_poll_for_event(m_connection)) { | 119 | while (auto event = xcb_poll_for_event(m_connection)) { | ||
117 | const uint8_t eventType = event->response_type & ~0x80; | 120 | const uint8_t eventType = event->response_type & ~0x80; | ||
118 | switch (eventType) { | 121 | switch (eventType) { | ||
119 | case XCB_ENTER_NOTIFY: | 122 | case XCB_ENTER_NOTIFY: { | ||
120 | emit entered(); | 123 | auto enterEvent = reinterpret_cast<xcb_enter_notify_event_t *>(event); | ||
121 | break; | 124 | emit entered(QPoint(enterEvent->event_x, enterEvent->event_y)); | ||
122 | case XCB_LEAVE_NOTIFY: | 125 | break; } | ||
123 | emit left(); | 126 | case XCB_MOTION_NOTIFY: { | ||
124 | break; | 127 | auto motionEvent = reinterpret_cast<xcb_motion_notify_event_t *>(event); | ||
128 | emit motion(QPoint(motionEvent->event_x, motionEvent->event_y)); | ||||
129 | break; } | ||||
130 | case XCB_LEAVE_NOTIFY: { | ||||
131 | auto leaveEvent = reinterpret_cast<xcb_leave_notify_event_t *>(event); | ||||
132 | emit left(QPoint(leaveEvent->event_x, leaveEvent->event_y)); | ||||
133 | break; } | ||||
125 | } | 134 | } | ||
126 | free(event); | 135 | free(event); | ||
127 | } | 136 | } | ||
128 | xcb_flush(m_connection); | 137 | xcb_flush(m_connection); | ||
129 | } | 138 | } | ||
130 | 139 | | |||
131 | void XWaylandInputTest::testPointerEnterLeave() | 140 | void XWaylandInputTest::testPointerEnterLeave() | ||
132 | { | 141 | { | ||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Line(s) | |||||
201 | QSignalSpy windowClosedSpy(client, &X11Client::windowClosed); | 210 | QSignalSpy windowClosedSpy(client, &X11Client::windowClosed); | ||
202 | QVERIFY(windowClosedSpy.isValid()); | 211 | QVERIFY(windowClosedSpy.isValid()); | ||
203 | xcb_unmap_window(c.data(), w); | 212 | xcb_unmap_window(c.data(), w); | ||
204 | xcb_destroy_window(c.data(), w); | 213 | xcb_destroy_window(c.data(), w); | ||
205 | xcb_flush(c.data()); | 214 | xcb_flush(c.data()); | ||
206 | QVERIFY(windowClosedSpy.wait()); | 215 | QVERIFY(windowClosedSpy.wait()); | ||
207 | } | 216 | } | ||
208 | 217 | | |||
218 | void XWaylandInputTest::testInputTransformationCsd() | ||||
219 | { | ||||
220 | // This test verifies whether pointer events that are sent to client-side decorated Xwayland | ||||
221 | // clients carry correct pointer coordinates. | ||||
222 | | ||||
223 | QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); | ||||
224 | QVERIFY(!xcb_connection_has_error(c.data())); | ||||
225 | | ||||
226 | if (xcb_get_setup(c.data())->release_number < 11800000) { | ||||
227 | QSKIP("XWayland 1.18 required"); | ||||
228 | } | ||||
229 | if (!Xcb::Extensions::self()->isShapeAvailable()) { | ||||
230 | QSKIP("SHAPE extension is required"); | ||||
231 | } | ||||
232 | | ||||
233 | X11EventReaderHelper eventReader(c.data()); | ||||
234 | QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); | ||||
235 | QVERIFY(enteredSpy.isValid()); | ||||
236 | QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); | ||||
237 | QVERIFY(leftSpy.isValid()); | ||||
238 | QSignalSpy motionSpy(&eventReader, &X11EventReaderHelper::motion); | ||||
239 | QVERIFY(motionSpy.isValid()); | ||||
240 | | ||||
241 | // Extents of the client-side drop-shadow. | ||||
242 | NETStrut clientFrameExtent; | ||||
243 | clientFrameExtent.left = 10; | ||||
244 | clientFrameExtent.right = 10; | ||||
245 | clientFrameExtent.top = 5; | ||||
246 | clientFrameExtent.bottom = 20; | ||||
247 | | ||||
248 | // Need to set the bounding shape in order to create a window without decoration. | ||||
249 | xcb_rectangle_t boundingRect; | ||||
250 | boundingRect.x = 0; | ||||
251 | boundingRect.y = 0; | ||||
252 | boundingRect.width = 100 + clientFrameExtent.left + clientFrameExtent.right; | ||||
253 | boundingRect.height = 200 + clientFrameExtent.top + clientFrameExtent.bottom; | ||||
254 | | ||||
255 | xcb_window_t window = xcb_generate_id(c.data()); | ||||
256 | const uint32_t values[] = { | ||||
257 | XCB_EVENT_MASK_ENTER_WINDOW | | ||||
258 | XCB_EVENT_MASK_LEAVE_WINDOW | | ||||
259 | XCB_EVENT_MASK_POINTER_MOTION | ||||
260 | }; | ||||
261 | xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, window, rootWindow(), | ||||
262 | boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height, | ||||
263 | 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); | ||||
264 | xcb_size_hints_t hints; | ||||
265 | memset(&hints, 0, sizeof(hints)); | ||||
266 | xcb_icccm_size_hints_set_position(&hints, 1, boundingRect.x, boundingRect.y); | ||||
267 | xcb_icccm_size_hints_set_size(&hints, 1, boundingRect.width, boundingRect.height); | ||||
268 | xcb_icccm_set_wm_normal_hints(c.data(), window, &hints); | ||||
269 | xcb_shape_rectangles(c.data(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, | ||||
270 | XCB_CLIP_ORDERING_UNSORTED, window, 0, 0, 1, &boundingRect); | ||||
271 | NETWinInfo info(c.data(), window, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); | ||||
272 | info.setWindowType(NET::Normal); | ||||
273 | info.setGtkFrameExtents(clientFrameExtent); | ||||
274 | xcb_map_window(c.data(), window); | ||||
275 | xcb_flush(c.data()); | ||||
276 | | ||||
277 | QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); | ||||
278 | QVERIFY(windowCreatedSpy.isValid()); | ||||
279 | QVERIFY(windowCreatedSpy.wait()); | ||||
280 | X11Client *client = windowCreatedSpy.last().first().value<X11Client *>(); | ||||
281 | QVERIFY(client); | ||||
282 | QVERIFY(!client->isDecorated()); | ||||
283 | QVERIFY(client->isClientSideDecorated()); | ||||
284 | QCOMPARE(client->bufferGeometry(), QRect(0, 0, 120, 225)); | ||||
285 | QCOMPARE(client->frameGeometry(), QRect(10, 5, 100, 200)); | ||||
286 | | ||||
287 | QMetaObject::invokeMethod(client, "setReadyForPainting"); | ||||
288 | QVERIFY(client->readyForPainting()); | ||||
289 | QVERIFY(!client->surface()); | ||||
290 | QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); | ||||
291 | QVERIFY(surfaceChangedSpy.isValid()); | ||||
292 | QVERIFY(surfaceChangedSpy.wait()); | ||||
293 | QVERIFY(client->surface()); | ||||
294 | | ||||
295 | // Move pointer into the window, should trigger an enter. | ||||
296 | QVERIFY(!client->frameGeometry().contains(Cursor::pos())); | ||||
297 | QVERIFY(enteredSpy.isEmpty()); | ||||
298 | Cursor::setPos(client->frameGeometry().center()); | ||||
299 | QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); | ||||
300 | QVERIFY(waylandServer()->seat()->focusedPointer()); | ||||
301 | QVERIFY(enteredSpy.wait()); | ||||
302 | QCOMPARE(enteredSpy.last().first(), QPoint(59, 104)); | ||||
303 | | ||||
304 | // Move to the bottom-right frame corner, should trigger a motion. | ||||
305 | Cursor::setPos(client->frameGeometry().bottomRight()); | ||||
306 | QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); | ||||
307 | QVERIFY(waylandServer()->seat()->focusedPointer()); | ||||
308 | QVERIFY(motionSpy.wait()); | ||||
309 | QCOMPARE(motionSpy.last().first(), QPoint(109, 204)); | ||||
310 | | ||||
311 | // Move out of the window, should trigger a leave. | ||||
312 | QVERIFY(leftSpy.isEmpty()); | ||||
313 | Cursor::setPos(client->frameGeometry().bottomRight() + QPoint(100, 100)); | ||||
314 | QVERIFY(leftSpy.wait()); | ||||
315 | QCOMPARE(leftSpy.last().first(), QPoint(109, 204)); | ||||
316 | | ||||
317 | // Destroy the window. | ||||
318 | QSignalSpy windowClosedSpy(client, &X11Client::windowClosed); | ||||
319 | QVERIFY(windowClosedSpy.isValid()); | ||||
320 | xcb_unmap_window(c.data(), window); | ||||
321 | xcb_destroy_window(c.data(), window); | ||||
322 | xcb_flush(c.data()); | ||||
323 | QVERIFY(windowClosedSpy.wait()); | ||||
324 | } | ||||
325 | | ||||
209 | } | 326 | } | ||
210 | 327 | | |||
211 | WAYLANDTEST_MAIN(KWin::XWaylandInputTest) | 328 | WAYLANDTEST_MAIN(KWin::XWaylandInputTest) | ||
212 | #include "xwayland_input_test.moc" | 329 | #include "xwayland_input_test.moc" |