diff --git a/plugins/platforms/x11/windowed/CMakeLists.txt b/plugins/platforms/x11/windowed/CMakeLists.txt --- a/plugins/platforms/x11/windowed/CMakeLists.txt +++ b/plugins/platforms/x11/windowed/CMakeLists.txt @@ -8,6 +8,9 @@ include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/opengl) add_library(KWinWaylandX11Backend MODULE ${X11BACKEND_SOURCES}) target_link_libraries(KWinWaylandX11Backend eglx11common kwin kwinxrenderutils X11::XCB SceneQPainterBackend SceneOpenGLBackend) +if(X11_Xinput_FOUND) + target_link_libraries(KWinWaylandX11Backend ${X11_Xinput_LIB}) +endif() install( TARGETS diff --git a/plugins/platforms/x11/windowed/x11windowed_backend.h b/plugins/platforms/x11/windowed/x11windowed_backend.h --- a/plugins/platforms/x11/windowed/x11windowed_backend.h +++ b/plugins/platforms/x11/windowed/x11windowed_backend.h @@ -87,6 +87,8 @@ void handleExpose(xcb_expose_event_t *event); void updateSize(xcb_configure_notify_event_t *event); void createCursor(const QImage &img, const QPoint &hotspot); + void initXInputForWindow(xcb_window_t window); + void initXInput(); xcb_connection_t *m_connection = nullptr; xcb_screen_t *m_screen = nullptr; @@ -106,6 +108,11 @@ xcb_cursor_t m_cursor = XCB_CURSOR_NONE; Display *m_display = nullptr; bool m_keyboardGrabbed = false; + + bool m_hasXInput = false; + int m_xiOpcode = 0; + int m_majorVersion = 0; + int m_minorVersion = 0; }; } diff --git a/plugins/platforms/x11/windowed/x11windowed_backend.cpp b/plugins/platforms/x11/windowed/x11windowed_backend.cpp --- a/plugins/platforms/x11/windowed/x11windowed_backend.cpp +++ b/plugins/platforms/x11/windowed/x11windowed_backend.cpp @@ -39,6 +39,11 @@ #include // xcb #include +// X11 +#if HAVE_X11_XINPUT +#include +#include +#endif // system #include #include @@ -92,6 +97,7 @@ m_screen = it.data; } } + initXInput(); XRenderUtils::init(m_connection, m_screen->root); createWindow(); connect(kwinApp(), &Application::workspaceCreated, this, &X11WindowedBackend::startEventReading); @@ -103,12 +109,43 @@ setReady(true); waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasKeyboard(true); + if (m_hasXInput) { + waylandServer()->seat()->setHasTouch(true); + } emit screensQueried(); } else { emit initFailed(); } } +void X11WindowedBackend::initXInput() +{ +#if HAVE_X11_XINPUT + int xi_opcode, event, error; + // init XInput extension + if (!XQueryExtension(m_display, "XInputExtension", &xi_opcode, &event, &error)) { + qCDebug(KWIN_X11WINDOWED) << "XInputExtension not present"; + return; + } + + // verify that the XInput extension is at at least version 2.0 + int major = 2, minor = 2; + int result = XIQueryVersion(m_display, &major, &minor); + if (result != Success) { + qCDebug(KWIN_X11WINDOWED) << "Failed to init XInput 2.2, trying 2.0"; + minor = 0; + if (XIQueryVersion(m_display, &major, &minor) != Success) { + qCDebug(KWIN_X11WINDOWED) << "Failed to init XInput"; + return; + } + } + m_xiOpcode = xi_opcode; + m_majorVersion = major; + m_minorVersion = minor; + m_hasXInput = m_majorVersion >=2 && m_minorVersion >= 2; +#endif +} + void X11WindowedBackend::createWindow() { Xcb::Atom protocolsAtom(QByteArrayLiteral("WM_PROTOCOLS"), false, m_connection); @@ -139,6 +176,9 @@ 0, 0, o.size.width(), o.size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); + // select xinput 2 events + initXInputForWindow(o.window); + o.winInfo = new NETWinInfo(m_connection, o.window, m_screen->root, NET::WMWindowType, NET::Properties2()); o.winInfo->setWindowType(NET::Normal); o.winInfo->setPid(QCoreApplication::applicationPid()); @@ -171,6 +211,27 @@ xcb_flush(m_connection); } +void X11WindowedBackend::initXInputForWindow(xcb_window_t window) +{ + if (!m_hasXInput) { + return; + } +#if HAVE_X11_XINPUT + XIEventMask evmasks[1]; + unsigned char mask1[XIMaskLen(XI_LASTEVENT)]; + + memset(mask1, 0, sizeof(mask1)); + XISetMask(mask1, XI_TouchBegin); + XISetMask(mask1, XI_TouchUpdate); + XISetMask(mask1, XI_TouchOwnership); + XISetMask(mask1, XI_TouchEnd); + evmasks[0].deviceid = XIAllMasterDevices; + evmasks[0].mask_len = sizeof(mask1); + evmasks[0].mask = mask1; + XISelectEvents(m_display, window, evmasks, 1); +#endif +} + void X11WindowedBackend::startEventReading() { QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this); @@ -186,6 +247,39 @@ connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); } +#if HAVE_X11_XINPUT +class GeEventMemMover +{ +public: + GeEventMemMover(xcb_generic_event_t *event) + : m_event(reinterpret_cast(event)) + { + // xcb event structs contain stuff that wasn't on the wire, the full_sequence field + // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes. + // Move this data back to have the same layout in memory as it was on the wire + // and allow casting, overwriting the full_sequence field. + memmove((char*) m_event + 32, (char*) m_event + 36, m_event->length * 4); + } + ~GeEventMemMover() + { + // move memory layout back, so that Qt can do the same without breaking + memmove((char*) m_event + 36, (char *) m_event + 32, m_event->length * 4); + } + + xcb_ge_generic_event_t *operator->() const { + return m_event; + } + +private: + xcb_ge_generic_event_t *m_event; +}; + +static inline qreal fixed1616ToReal(FP1616 val) +{ + return (val) * 1.0 / (1 << 16); +} +#endif + void X11WindowedBackend::handleEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; @@ -249,6 +343,46 @@ xcb_refresh_keyboard_mapping(m_keySymbols, reinterpret_cast(e)); } break; +#if HAVE_X11_XINPUT + case XCB_GE_GENERIC: { + GeEventMemMover ge(e); + auto te = reinterpret_cast(e); + auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [te] (const Output &o) { return o.window == te->event; }); + if (it == m_windows.constEnd()) { + break; + } + QPointF position{ + fixed1616ToReal(te->root_x) - (*it).xPosition.x() + (*it).internalPosition.x(), + fixed1616ToReal(te->root_y) - (*it).xPosition.y() + (*it).internalPosition.y() + }; + position /= it->scale; + + switch (ge->event_type) { + + case XI_TouchBegin: { + touchDown(te->detail, position, te->time); + touchFrame(); + break; + } + case XI_TouchUpdate: { + touchMotion(te->detail, position, te->time); + touchFrame(); + break; + } + case XI_TouchEnd: { + touchUp(te->detail, te->time); + touchFrame(); + break; + } + case XI_TouchOwnership: { + auto te = reinterpret_cast(e); + XIAllowTouchEvents(m_display, te->deviceid, te->sourceid, te->touchid, XIAcceptTouch); + break; + } + } + break; + } +#endif default: break; }