Provide input geometry and input transformation matrix for Xwayland clients
Summary: We need to provide the input geometry and the input transformation matrix for Xwayland clients in order to make sure that input events are correctly mapped from the global screen coordinates to the screen-local coordinates. BUG: 417444 FIXED-IN: 5.18.1 Reviewers: #kwin Subscribers: kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D27375
This commit is contained in:
parent
3dbfa6a325
commit
1181af2cd1
3 changed files with 134 additions and 12 deletions
|
@ -46,7 +46,8 @@ class XWaylandInputTest : public QObject
|
|||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void init();
|
||||
void testPointerEnterLeave();
|
||||
void testPointerEnterLeaveSsd();
|
||||
void testPointerEventLeaveCsd();
|
||||
};
|
||||
|
||||
void XWaylandInputTest::initTestCase()
|
||||
|
@ -73,10 +74,11 @@ void XWaylandInputTest::init()
|
|||
{
|
||||
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 @@ public:
|
|||
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 @@ void X11EventReaderHelper::processXcbEvents()
|
|||
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<xcb_enter_notify_event_t *>(event);
|
||||
emit entered(QPoint(enterEvent->event_x, enterEvent->event_y));
|
||||
break; }
|
||||
case XCB_LEAVE_NOTIFY: {
|
||||
auto leaveEvent = reinterpret_cast<xcb_leave_notify_event_t *>(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<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
||||
|
@ -192,10 +196,12 @@ void XWaylandInputTest::testPointerEnterLeave()
|
|||
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 @@ void XWaylandInputTest::testPointerEnterLeave()
|
|||
QVERIFY(windowClosedSpy.wait());
|
||||
}
|
||||
|
||||
void XWaylandInputTest::testPointerEventLeaveCsd()
|
||||
{
|
||||
// this test simulates a pointer enter and pointer leave on a client-side decorated X11 window
|
||||
|
||||
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> 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<X11Client *>();
|
||||
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)
|
||||
|
|
|
@ -2634,6 +2634,15 @@ xcb_window_t X11Client::frameId() const
|
|||
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 @@ QRect X11Client::frameRectToBufferRect(const QRect &rect) const
|
|||
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);
|
||||
|
|
|
@ -89,6 +89,7 @@ public:
|
|||
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 @@ public:
|
|||
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;
|
||||
|
|
Loading…
Reference in a new issue