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;
};