diff --git a/CMakeLists.txt b/CMakeLists.txt index 6800a1ddf2..bdac6c578e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,6 +189,8 @@ set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE REQUIRED ) +add_feature_info("XInput" X11_Xinput_FOUND "Required for poll-free mouse cursor updates") +set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) # All the required XCB components find_package(XCB 1.10 @@ -480,6 +482,7 @@ set(kwin_XLIB_LIBS ${X11_X11_LIB} ${X11_ICE_LIB} ${X11_SM_LIB} + ${X11_Xinput_LIB} ) set(kwin_XCB_LIBS diff --git a/config-kwin.h.cmake b/config-kwin.h.cmake index f055a159f2..0996173d89 100644 --- a/config-kwin.h.cmake +++ b/config-kwin.h.cmake @@ -10,6 +10,7 @@ #define KWIN_RULES_DIALOG_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kwin_rules_dialog" #cmakedefine01 HAVE_INPUT #cmakedefine01 HAVE_X11_XCB +#cmakedefine01 HAVE_X11_XINPUT #cmakedefine01 HAVE_DRM #cmakedefine01 HAVE_GBM #cmakedefine01 HAVE_LIBHYBRIS diff --git a/cursor.cpp b/cursor.cpp index db68ce7fef..de5c5a2606 100644 --- a/cursor.cpp +++ b/cursor.cpp @@ -24,18 +24,28 @@ along with this program. If not, see . #include "input.h" #include "main.h" #include "utils.h" +#include "x11eventfilter.h" #include "xcbutils.h" // KDE #include #include #include // Qt +#include #include #include #include // xcb #include #include +// X11 +#include +#if HAVE_X11_XINPUT +#include +#else +#define XI_RawMotion 0 +#endif +#include namespace KWin { @@ -248,13 +258,36 @@ void Cursor::notifyCursorChanged(uint32_t serial) emit cursorChanged(serial); } +class XInputEventFilter : public X11EventFilter +{ +public: + XInputEventFilter(X11Cursor *parent, int xi_opcode) + : X11EventFilter(XCB_GE_GENERIC, xi_opcode, XI_RawMotion) + , m_x11Cursor(parent) + {} + virtual ~XInputEventFilter() = default; + + bool event(xcb_generic_event_t *event) override { + Q_UNUSED(event) + m_x11Cursor->schedulePoll(); + return false; + } + +private: + X11Cursor *m_x11Cursor; +}; + X11Cursor::X11Cursor(QObject *parent) : Cursor(parent) , m_timeStamp(XCB_TIME_CURRENT_TIME) , m_buttonMask(0) , m_resetTimeStampTimer(new QTimer(this)) , m_mousePollingTimer(new QTimer(this)) + , m_hasXInput(false) + , m_xiOpcode(0) + , m_needsPoll(false) { + initXInput(); m_resetTimeStampTimer->setSingleShot(true); connect(m_resetTimeStampTimer, SIGNAL(timeout()), SLOT(resetTimeStamp())); // TODO: How often do we really need to poll? @@ -268,6 +301,39 @@ X11Cursor::~X11Cursor() { } +void X11Cursor::initXInput() +{ +#ifndef KCMRULES +#if HAVE_X11_XINPUT + if (qEnvironmentVariableIsSet("KWIN_NO_XI2")) { + return; + } + Display *dpy = display(); + int xi_opcode, event, error; + // init XInput extension + if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) { + return; + } + + // verify that the XInput extension is at at least version 2.0 + int major = 2, minor = 0; + int result = XIQueryVersion(dpy, &major, &minor); + if (result == BadImplementation) { + // Xinput 2.2 returns BadImplementation if checked against 2.0 + major = 2; + minor = 2; + if (XIQueryVersion(dpy, &major, &minor) != Success) { + return; + } + } else if (result != Success) { + return; + } + m_hasXInput = true; + m_xiOpcode = xi_opcode; +#endif +#endif +} + void X11Cursor::doSetPos() { const QPoint &pos = currentPos(); @@ -298,14 +364,64 @@ void X11Cursor::resetTimeStamp() m_timeStamp = XCB_TIME_CURRENT_TIME; } +void X11Cursor::aboutToBlock() +{ + if (m_needsPoll) { + mousePolled(); + m_needsPoll = false; + } +} + void X11Cursor::doStartMousePolling() { - m_mousePollingTimer->start(); + if (m_hasXInput) { +#ifndef KCMRULES +#if HAVE_X11_XINPUT + m_xiEventFilter.reset(new XInputEventFilter(this, m_xiOpcode)); + + // this assumes KWin is the only one setting events on the root window + // given Qt's source code this seems to be true. If it breaks, we need to change + XIEventMask evmasks[1]; + unsigned char mask1[XIMaskLen(XI_LASTEVENT)]; + + memset(mask1, 0, sizeof(mask1)); + + XISetMask(mask1, XI_RawMotion); + + evmasks[0].deviceid = XIAllMasterDevices; + evmasks[0].mask_len = sizeof(mask1); + evmasks[0].mask = mask1; + XISelectEvents(display(), rootWindow(), evmasks, 1); + connect(qApp->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11Cursor::aboutToBlock); +#endif +#endif + } else { + m_mousePollingTimer->start(); + } } void X11Cursor::doStopMousePolling() { - m_mousePollingTimer->stop(); + if (m_hasXInput) { +#ifndef KCMRULES +#if HAVE_X11_XINPUT + m_xiEventFilter.reset(); + + XIEventMask evmasks[1]; + unsigned char mask1[(XI_LASTEVENT + 7)/8]; + + memset(mask1, 0, sizeof(mask1)); + + evmasks[0].deviceid = XIAllMasterDevices; + evmasks[0].mask_len = sizeof(mask1); + evmasks[0].mask = mask1; + XISelectEvents(display(), rootWindow(), evmasks, 1); + disconnect(qApp->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11Cursor::aboutToBlock); +#endif +#endif + } else { + m_mousePollingTimer->stop(); + } } void X11Cursor::doStartCursorTracking() diff --git a/cursor.h b/cursor.h index d37a0aabcb..e0a3091e02 100644 --- a/cursor.h +++ b/cursor.h @@ -33,6 +33,8 @@ class QTimer; namespace KWin { +class XInputEventFilter; + /** * @short Replacement for QCursor. * @@ -221,6 +223,11 @@ class X11Cursor : public Cursor Q_OBJECT public: virtual ~X11Cursor(); + + void schedulePoll() { + m_needsPoll = true; + } + protected: virtual xcb_cursor_t getX11Cursor(Qt::CursorShape shape); xcb_cursor_t getX11Cursor(const QByteArray &name) override; @@ -239,14 +246,20 @@ private Q_SLOTS: */ void resetTimeStamp(); void mousePolled(); + void aboutToBlock(); private: X11Cursor(QObject *parent); + void initXInput(); xcb_cursor_t createCursor(const QByteArray &name); QHash m_cursors; xcb_timestamp_t m_timeStamp; uint16_t m_buttonMask; QTimer *m_resetTimeStampTimer; QTimer *m_mousePollingTimer; + bool m_hasXInput; + int m_xiOpcode; + bool m_needsPoll; + QScopedPointer m_xiEventFilter; friend class Cursor; };