Use XInput2 scrolling events to enable smoother scrolling

With this patch all wheel events will be generated by XInput2 where
available. This enables higher precision and smoother scrolling
especially from input devices such as touchpads on laptops.

[ChangeLog][Platform Specific Changes][X11 / XCB]Support XInput2 smooth scrolling events

Change-Id: I3b069ed92ad5c53e08af64baaece32de82e9b5c4
Reviewed-by: Shawn Rutledge <shawn.rutledge@digia.com>
This commit is contained in:
Allan Sandfeld Jensen 2014-01-02 10:32:52 +01:00 committed by The Qt Project
parent 8ca19dde55
commit 097b0a5316
4 changed files with 164 additions and 28 deletions

View File

@ -1441,6 +1441,10 @@ static const char * xcb_atomnames = {
"Abs Distance\0"
"Wacom Serial IDs\0"
"INTEGER\0"
"Rel Horiz Wheel\0"
"Rel Vert Wheel\0"
"Rel Horiz Scroll\0"
"Rel Vert Scroll\0"
#if XCB_USE_MAEMO_WINDOW_PROPERTIES
"_MEEGOTOUCH_ORIENTATION_ANGLE\0"
#endif
@ -1722,21 +1726,23 @@ bool QXcbConnection::hasEgl() const
#endif // defined(XCB_USE_EGL)
#if defined(XCB_USE_XINPUT2) || defined(XCB_USE_XINPUT2_MAEMO)
// Borrowed from libXi.
int QXcbConnection::xi2CountBits(unsigned char *ptr, int len)
static int xi2ValuatorOffset(unsigned char *maskPtr, int maskLen, int number)
{
int bits = 0;
int i;
unsigned char x;
for (i = 0; i < len; i++) {
x = ptr[i];
while (x > 0) {
bits += (x & 0x1);
x >>= 1;
int offset = 0;
for (int i = 0; i < maskLen; i++) {
if (number < 8) {
if ((maskPtr[i] & (1 << number)) == 0)
return -1;
}
for (int j = 0; j < 8; j++) {
if (j == number)
return offset;
if (maskPtr[i] & (1 << j))
offset++;
}
number -= 8;
}
return bits;
return -1;
}
bool QXcbConnection::xi2GetValuatorValueIfSet(void *event, int valuatorNum, double *value)
@ -1745,13 +1751,13 @@ bool QXcbConnection::xi2GetValuatorValueIfSet(void *event, int valuatorNum, doub
unsigned char *buttonsMaskAddr = (unsigned char*)&xideviceevent[1];
unsigned char *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4;
FP3232 *valuatorsValuesAddr = (FP3232*)(valuatorsMaskAddr + xideviceevent->valuators_len * 4);
int numValuatorValues = xi2CountBits(valuatorsMaskAddr, xideviceevent->valuators_len * 4);
// This relies on all bit being set until a certain number i.e. it doesn't support only bit 0 and 5 being set in the mask.
// Just like the original code, works for now.
if (valuatorNum >= numValuatorValues)
int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum);
if (valuatorOffset < 0)
return false;
*value = valuatorsValuesAddr[valuatorNum].integral;
*value += ((double)valuatorsValuesAddr[valuatorNum].frac / (1 << 16) / (1 << 16));
*value = valuatorsValuesAddr[valuatorOffset].integral;
*value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16));
return true;
}

View File

@ -69,9 +69,12 @@
struct XInput2MaemoData;
#elif XCB_USE_XINPUT2
#include <X11/extensions/XI2.h>
#ifdef XIScrollClass
#define XCB_USE_XINPUT21 // XI 2.1 adds smooth scrolling support
#ifdef XI_TouchBeginMask
#define XCB_USE_XINPUT22 // XI 2.2 adds multi-point touch support
#endif
#endif
struct XInput2DeviceData;
#endif
struct xcb_randr_get_output_info_reply_t;
@ -271,6 +274,10 @@ namespace QXcbAtom {
AbsDistance,
WacomSerialIDs,
INTEGER,
RelHorizWheel,
RelVertWheel,
RelHorizScroll,
RelVertScroll,
#if XCB_USE_MAEMO_WINDOW_PROPERTIES
MeegoTouchOrientationAngle,
@ -499,10 +506,19 @@ private:
void xi2ReportTabletEvent(const TabletData &tabletData, void *event);
QVector<TabletData> m_tabletData;
#endif
struct ScrollingDevice {
ScrollingDevice() : deviceId(0), verticalIndex(0), horizontalIndex(0), orientations(0) { }
int deviceId;
int verticalIndex, horizontalIndex;
double verticalIncrement, horizontalIncrement;
Qt::Orientations orientations;
QPointF lastScrollPosition;
};
void xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice);
QHash<int, ScrollingDevice> m_scrollingDevices;
#endif // XCB_USE_XINPUT2
#if defined(XCB_USE_XINPUT2) || defined(XCB_USE_XINPUT2_MAEMO)
static int xi2CountBits(unsigned char *ptr, int len);
static bool xi2GetValuatorValueIfSet(void *event, int valuatorNum, double *value);
static bool xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *event, int opCode);
#endif

View File

@ -40,6 +40,7 @@
****************************************************************************/
#include "qxcbconnection.h"
#include "qxcbkeyboard.h"
#include "qxcbscreen.h"
#include "qxcbwindow.h"
#include "qtouchdevice.h"
@ -75,16 +76,20 @@ void QXcbConnection::initializeXInput2()
#ifndef QT_NO_TABLETEVENT
m_tabletData.clear();
#endif
m_scrollingDevices.clear();
Display *xDisplay = static_cast<Display *>(m_xlib_display);
if (XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) {
int xiMajor = 2;
m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End
if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
m_xi2Minor = 0; // for tablet support 2.0 is enough
m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
} else {
m_xi2Minor = 1; // for smooth scrolling 2.1 is enough
if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
m_xi2Minor = 0; // for tablet support 2.0 is enough
m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
} else
m_xi2Enabled = true;
} else
m_xi2Enabled = true;
}
if (m_xi2Enabled) {
if (Q_UNLIKELY(debug_xinput_devices))
#ifdef XCB_USE_XINPUT22
@ -103,6 +108,7 @@ void QXcbConnection::initializeXInput2()
#ifndef QT_NO_TABLETEVENT
TabletData tabletData;
#endif
ScrollingDevice scrollingDevice;
for (int c = 0; c < devices[i].num_classes; ++c) {
switch (devices[i].classes[c]->type) {
case XIValuatorClass: {
@ -119,7 +125,29 @@ void QXcbConnection::initializeXInput2()
tabletData.valuatorInfo[valuatorAtom] = info;
}
#endif // QT_NO_TABLETEVENT
} break;
if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
scrollingDevice.lastScrollPosition.setX(vci->value);
else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
scrollingDevice.lastScrollPosition.setY(vci->value);
break;
}
#ifdef XCB_USE_XINPUT21
case XIScrollClass: {
XIScrollClassInfo *sci = reinterpret_cast<XIScrollClassInfo *>(devices[i].classes[c]);
scrollingDevice.deviceId = devices[i].deviceid;
if (sci->scroll_type == XIScrollTypeVertical) {
scrollingDevice.orientations |= Qt::Vertical;
scrollingDevice.verticalIndex = sci->number;
scrollingDevice.verticalIncrement = sci->increment;
}
else if (sci->scroll_type == XIScrollTypeHorizontal) {
scrollingDevice.orientations |= Qt::Horizontal;
scrollingDevice.horizontalIndex = sci->number;
scrollingDevice.horizontalIncrement = sci->increment;
}
break;
}
#endif
default:
break;
}
@ -140,6 +168,15 @@ void QXcbConnection::initializeXInput2()
qDebug() << " it's a tablet with pointer type" << tabletData.pointerType;
}
#endif // QT_NO_TABLETEVENT
#ifdef XCB_USE_XINPUT21
if (scrollingDevice.orientations) {
m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice);
if (Q_UNLIKELY(debug_xinput_devices))
qDebug() << " it's a scrolling device";
}
#endif
if (!isTablet) {
XInput2DeviceData *dev = deviceForId(devices[i].deviceid);
if (Q_UNLIKELY(debug_xinput_devices)) {
@ -213,6 +250,22 @@ void QXcbConnection::xi2Select(xcb_window_t window)
XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count());
}
#endif // QT_NO_TABLETEVENT
#ifdef XCB_USE_XINPUT21
// Enable each scroll device
if (!m_scrollingDevices.isEmpty()) {
QVector<XIEventMask> xiEventMask(m_scrollingDevices.size());
bitMask = XI_MotionMask;
int i=0;
Q_FOREACH (const ScrollingDevice& scrollingDevice, m_scrollingDevices) {
xiEventMask[i].deviceid = scrollingDevice.deviceId;
xiEventMask[i].mask_len = sizeof(bitMask);
xiEventMask[i].mask = xiBitMask;
i++;
}
XISelectEvents(xDisplay, window, xiEventMask.data(), m_scrollingDevices.size());
}
#endif
}
XInput2DeviceData *QXcbConnection::deviceForId(int id)
@ -243,7 +296,8 @@ XInput2DeviceData *QXcbConnection::deviceForId(int id)
type = QTouchDevice::TouchScreen;
break;
}
} break;
break;
}
#endif // XCB_USE_XINPUT22
case XIValuatorClass: {
XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo);
@ -260,7 +314,8 @@ XInput2DeviceData *QXcbConnection::deviceForId(int id)
hasRelativeCoords = true;
dev->size.setHeight((vci->max - vci->min) * 1000.0 / vci->resolution);
}
} break;
break;
}
}
}
if (type < 0 && caps && hasRelativeCoords) {
@ -287,12 +342,14 @@ XInput2DeviceData *QXcbConnection::deviceForId(int id)
return dev;
}
#ifdef XCB_USE_XINPUT22
#if defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT)
static qreal fixed1616ToReal(FP1616 val)
{
return (qreal(val >> 16)) + (val & 0xFF) / (qreal)0xFF;
}
#endif // defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT)
#if defined(XCB_USE_XINPUT21)
static qreal valuatorNormalized(double value, XIValuatorClassInfo *vci)
{
if (value > vci->max)
@ -301,7 +358,7 @@ static qreal valuatorNormalized(double value, XIValuatorClassInfo *vci)
value = vci->min;
return (value - vci->min) / (vci->max - vci->min);
}
#endif // XCB_USE_XINPUT22
#endif // XCB_USE_XINPUT21
void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
{
@ -317,6 +374,12 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
}
#endif // QT_NO_TABLETEVENT
#ifdef XCB_USE_XINPUT21
QHash<int, ScrollingDevice>::iterator device = m_scrollingDevices.find(xiEvent->deviceid);
if (device != m_scrollingDevices.end())
xi2HandleScrollEvent(xiEvent, device.value());
#endif // XCB_USE_XINPUT21
#ifdef XCB_USE_XINPUT22
if (xiEvent->evtype == XI_TouchBegin || xiEvent->evtype == XI_TouchUpdate || xiEvent->evtype == XI_TouchEnd) {
xXIDeviceEvent* xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
@ -461,6 +524,55 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
}
}
void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice)
{
#ifdef XCB_USE_XINPUT21
xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
if (xiEvent->evtype == XI_Motion) {
xXIDeviceEvent* xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
QPoint rawDelta;
QPoint angleDelta;
double value;
if (scrollingDevice.orientations & Qt::Vertical) {
if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.verticalIndex, &value)) {
double delta = scrollingDevice.lastScrollPosition.y() - value;
scrollingDevice.lastScrollPosition.setY(value);
angleDelta.setY((delta / scrollingDevice.verticalIncrement) * 120);
// We do not set "pixel" delta if it is only measured in ticks.
if (scrollingDevice.verticalIncrement > 1)
rawDelta.setY(delta);
}
}
if (scrollingDevice.orientations & Qt::Horizontal) {
if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.horizontalIndex, &value)) {
double delta = scrollingDevice.lastScrollPosition.x() - value;
scrollingDevice.lastScrollPosition.setX(value);
angleDelta.setX((delta / scrollingDevice.horizontalIncrement) * 120);
// We do not set "pixel" delta if it is only measured in ticks.
if (scrollingDevice.horizontalIncrement > 1)
rawDelta.setX(delta);
}
}
if (!angleDelta.isNull()) {
QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective_mods);
if (modifiers & Qt::AltModifier) {
std::swap(angleDelta.rx(), angleDelta.ry());
std::swap(rawDelta.rx(), rawDelta.ry());
}
QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiEvent->time, local, global, rawDelta, angleDelta, modifiers);
}
}
}
#else
Q_UNUSED(event);
Q_UNUSED(scrollingDevice);
#endif // XCB_USE_XINPUT21
}
#ifndef QT_NO_TABLETEVENT
bool QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData)
{

View File

@ -1689,6 +1689,7 @@ void QXcbWindow::handleButtonPressEvent(const xcb_button_press_event_t *event)
Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(event->state);
if (isWheel) {
#ifndef XCB_USE_XINPUT21
// Logic borrowed from qapplication_x11.cpp
int delta = 120 * ((event->detail == 4 || event->detail == 6) ? 1 : -1);
bool hor = (((event->detail == 4 || event->detail == 5)
@ -1697,6 +1698,7 @@ void QXcbWindow::handleButtonPressEvent(const xcb_button_press_event_t *event)
QWindowSystemInterface::handleWheelEvent(window(), event->time,
local, global, delta, hor ? Qt::Horizontal : Qt::Vertical, modifiers);
#endif
return;
}