diff --git a/autotests/integration/xwayland_input_test.cpp b/autotests/integration/xwayland_input_test.cpp --- a/autotests/integration/xwayland_input_test.cpp +++ b/autotests/integration/xwayland_input_test.cpp @@ -46,7 +46,8 @@ private Q_SLOTS: void initTestCase(); void init(); - void testPointerEnterLeave(); + void testPointerEnterLeaveSsd(); + void testPointerEventLeaveCsd(); }; void XWaylandInputTest::initTestCase() @@ -73,10 +74,11 @@ { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); + xcb_warp_pointer(connection(), XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 640, 512); + xcb_flush(connection()); QVERIFY(waylandServer()->clients().isEmpty()); } - struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) @@ -92,8 +94,8 @@ X11EventReaderHelper(xcb_connection_t *c); Q_SIGNALS: - void entered(); - void left(); + void entered(const QPoint &localPoint); + void left(const QPoint &localPoint); private: void processXcbEvents(); @@ -116,21 +118,23 @@ while (auto event = xcb_poll_for_event(m_connection)) { const uint8_t eventType = event->response_type & ~0x80; switch (eventType) { - case XCB_ENTER_NOTIFY: - emit entered(); - break; - case XCB_LEAVE_NOTIFY: - emit left(); - break; + case XCB_ENTER_NOTIFY: { + auto enterEvent = reinterpret_cast(event); + emit entered(QPoint(enterEvent->event_x, enterEvent->event_y)); + break; } + case XCB_LEAVE_NOTIFY: { + auto leaveEvent = reinterpret_cast(event); + emit left(QPoint(leaveEvent->event_x, leaveEvent->event_y)); + break; } } free(event); } xcb_flush(m_connection); } -void XWaylandInputTest::testPointerEnterLeave() +void XWaylandInputTest::testPointerEnterLeaveSsd() { - // this test simulates a pointer enter and pointer leave on an X11 window + // this test simulates a pointer enter and pointer leave on a server-side decorated X11 window // create the test window QScopedPointer c(xcb_connect(nullptr, nullptr)); @@ -192,10 +196,12 @@ QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); QVERIFY(waylandServer()->seat()->focusedPointer()); QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.last().first(), QPoint(49, 81)); // move out of window Cursor::setPos(client->frameGeometry().bottomRight() + QPoint(10, 10)); QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.last().first(), QPoint(49, 81)); // destroy window again QSignalSpy windowClosedSpy(client, &X11Client::windowClosed); @@ -206,6 +212,103 @@ QVERIFY(windowClosedSpy.wait()); } +void XWaylandInputTest::testPointerEventLeaveCsd() +{ + // this test simulates a pointer enter and pointer leave on a client-side decorated X11 window + + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + + if (xcb_get_setup(c.data())->release_number < 11800000) { + QSKIP("XWayland 1.18 required"); + } + if (!Xcb::Extensions::self()->isShapeAvailable()) { + QSKIP("SHAPE extension is required"); + } + + X11EventReaderHelper eventReader(c.data()); + QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); + QVERIFY(enteredSpy.isValid()); + QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); + QVERIFY(leftSpy.isValid()); + + // Extents of the client-side drop-shadow. + NETStrut clientFrameExtent; + clientFrameExtent.left = 10; + clientFrameExtent.right = 10; + clientFrameExtent.top = 5; + clientFrameExtent.bottom = 20; + + // Need to set the bounding shape in order to create a window without decoration. + xcb_rectangle_t boundingRect; + boundingRect.x = 0; + boundingRect.y = 0; + boundingRect.width = 100 + clientFrameExtent.left + clientFrameExtent.right; + boundingRect.height = 200 + clientFrameExtent.top + clientFrameExtent.bottom; + + xcb_window_t window = xcb_generate_id(c.data()); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW + }; + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, window, rootWindow(), + boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, boundingRect.x, boundingRect.y); + xcb_icccm_size_hints_set_size(&hints, 1, boundingRect.width, boundingRect.height); + xcb_icccm_set_wm_normal_hints(c.data(), window, &hints); + xcb_shape_rectangles(c.data(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, + XCB_CLIP_ORDERING_UNSORTED, window, 0, 0, 1, &boundingRect); + NETWinInfo info(c.data(), window, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + info.setGtkFrameExtents(clientFrameExtent); + xcb_map_window(c.data(), window); + xcb_flush(c.data()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + X11Client *client = windowCreatedSpy.last().first().value(); + QVERIFY(client); + QVERIFY(!client->isDecorated()); + QVERIFY(client->isClientSideDecorated()); + QCOMPARE(client->bufferGeometry(), QRect(0, 0, 120, 225)); + QCOMPARE(client->frameGeometry(), QRect(10, 5, 100, 200)); + + QMetaObject::invokeMethod(client, "setReadyForPainting"); + QVERIFY(client->readyForPainting()); + QVERIFY(!client->surface()); + QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); + QVERIFY(surfaceChangedSpy.isValid()); + QVERIFY(surfaceChangedSpy.wait()); + QVERIFY(client->surface()); + + // Move pointer into the window, should trigger an enter. + QVERIFY(!client->frameGeometry().contains(Cursor::pos())); + QVERIFY(enteredSpy.isEmpty()); + Cursor::setPos(client->frameGeometry().center()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); + QVERIFY(waylandServer()->seat()->focusedPointer()); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.last().first(), QPoint(59, 104)); + + // Move out of the window, should trigger a leave. + QVERIFY(leftSpy.isEmpty()); + Cursor::setPos(client->frameGeometry().bottomRight() + QPoint(100, 100)); + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.last().first(), QPoint(59, 104)); + + // Destroy the window. + QSignalSpy windowClosedSpy(client, &X11Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + xcb_unmap_window(c.data(), window); + xcb_destroy_window(c.data(), window); + xcb_flush(c.data()); + QVERIFY(windowClosedSpy.wait()); +} + } WAYLANDTEST_MAIN(KWin::XWaylandInputTest) diff --git a/x11client.h b/x11client.h --- a/x11client.h +++ b/x11client.h @@ -89,6 +89,7 @@ xcb_window_t inputId() const { return m_decoInputExtent; } xcb_window_t frameId() const override; + QRect inputGeometry() const override; QRect bufferGeometry() const override; QMargins bufferMargins() const override; @@ -98,6 +99,8 @@ QSize clientSizeToFrameSize(const QSize &size) const override; QRect frameRectToBufferRect(const QRect &rect) const; + QMatrix4x4 inputTransformation() const override; + bool isTransient() const override; bool groupTransient() const override; bool wasOriginallyGroupTransient() const; diff --git a/x11client.cpp b/x11client.cpp --- a/x11client.cpp +++ b/x11client.cpp @@ -2634,6 +2634,15 @@ return m_frame; } +QRect X11Client::inputGeometry() const +{ + // Notice that the buffer geometry corresponds to the geometry of the frame window. + if (isDecorated()) { + return m_bufferGeometry + decoration()->resizeOnlyBorders(); + } + return m_bufferGeometry; +} + QRect X11Client::bufferGeometry() const { return m_bufferGeometry; @@ -2716,6 +2725,13 @@ return frameRectToClientRect(rect); } +QMatrix4x4 X11Client::inputTransformation() const +{ + QMatrix4x4 matrix; + matrix.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y()); + return matrix; +} + Xcb::Property X11Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1);