xcb: allow to change XInput device properties at runtime

xinput list-props <device-id>
xinput set-prop <device-id> <atom-id> n n n

Example:

xinput list-props 9
  ..
  Evdev Scrolling Distance (274): 1, 1, 1
  ..
xinput set-prop 9 274 8 1 1

[ChangeLog][Platform Specific Changes][Linux] XInput device property
changes are now detected at runtime (no application restart required).

Change-Id: I4d2455eef70857bc2e35c27011a3808a78fa960f
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
Gatis Paeglis 2017-06-08 18:54:53 +02:00
parent 46001e6f49
commit 900abb116d
2 changed files with 183 additions and 154 deletions

View File

@ -547,6 +547,7 @@ private:
bool m_xi2Enabled = false;
int m_xi2Minor = -1;
void initializeXInput2();
void xi2SetupDevice(void *info, bool removeExisting = true);
void xi2SetupDevices();
struct TouchDeviceData {
QTouchDevice *qtTouchDevice = nullptr;

View File

@ -93,6 +93,178 @@ void QXcbConnection::initializeXInput2()
}
}
void QXcbConnection::xi2SetupDevice(void *info, bool removeExisting)
{
XIDeviceInfo *deviceInfo = reinterpret_cast<XIDeviceInfo *>(info);
if (removeExisting) {
#if QT_CONFIG(tabletevent)
for (int i = 0; i < m_tabletData.count(); ++i) {
if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) {
m_tabletData.remove(i);
break;
}
}
#endif
m_scrollingDevices.remove(deviceInfo->deviceid);
m_touchDevices.remove(deviceInfo->deviceid);
}
qCDebug(lcQpaXInputDevices) << "input device " << deviceInfo->name << "ID" << deviceInfo->deviceid;
#if QT_CONFIG(tabletevent)
TabletData tabletData;
#endif
ScrollingDevice scrollingDevice;
for (int c = 0; c < deviceInfo->num_classes; ++c) {
XIAnyClassInfo *classinfo = deviceInfo->classes[c];
switch (classinfo->type) {
case XIValuatorClass: {
XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo);
const int valuatorAtom = qatom(vci->label);
qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms);
#if QT_CONFIG(tabletevent)
if (valuatorAtom < QXcbAtom::NAtoms) {
TabletData::ValuatorClassInfo info;
info.minVal = vci->min;
info.maxVal = vci->max;
info.number = vci->number;
tabletData.valuatorInfo[valuatorAtom] = info;
}
#endif // QT_CONFIG(tabletevent)
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 *>(classinfo);
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;
}
case XIButtonClass: {
XIButtonClassInfo *bci = reinterpret_cast<XIButtonClassInfo *>(classinfo);
if (bci->num_buttons >= 5) {
Atom label4 = bci->labels[3];
Atom label5 = bci->labels[4];
// Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
// button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) &&
(!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown))
scrollingDevice.legacyOrientations |= Qt::Vertical;
}
if (bci->num_buttons >= 7) {
Atom label6 = bci->labels[5];
Atom label7 = bci->labels[6];
if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight))
scrollingDevice.legacyOrientations |= Qt::Horizontal;
}
qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons);
break;
}
#endif
case XIKeyClass:
qCDebug(lcQpaXInputDevices) << " it's a keyboard";
break;
#ifdef XCB_USE_XINPUT22
case XITouchClass:
// will be handled in populateTouchDevices()
break;
#endif
default:
qCDebug(lcQpaXInputDevices) << " has class" << classinfo->type;
break;
}
}
bool isTablet = false;
#if QT_CONFIG(tabletevent)
// If we have found the valuators which we expect a tablet to have, it might be a tablet.
if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) &&
tabletData.valuatorInfo.contains(QXcbAtom::AbsY) &&
tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure))
isTablet = true;
// But we need to be careful not to take the touch and tablet-button devices as tablets.
QByteArray name = QByteArray(deviceInfo->name).toLower();
QString dbgType = QLatin1String("UNKNOWN");
if (name.contains("eraser")) {
isTablet = true;
tabletData.pointerType = QTabletEvent::Eraser;
dbgType = QLatin1String("eraser");
} else if (name.contains("cursor")) {
isTablet = true;
tabletData.pointerType = QTabletEvent::Cursor;
dbgType = QLatin1String("cursor");
} else if (name.contains("wacom") && name.contains("finger touch")) {
isTablet = false;
} else if ((name.contains("pen") || name.contains("stylus")) && isTablet) {
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("wacom") && isTablet && !name.contains("touch")) {
// combined device (evdev) rather than separate pen/eraser (wacom driver)
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) {
// some "Genius" tablets
isTablet = true;
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("waltop") && name.contains("tablet")) {
// other "Genius" tablets
// WALTOP International Corp. Slim Tablet
isTablet = true;
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("uc-logic") && isTablet) {
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else {
isTablet = false;
}
if (isTablet) {
tabletData.deviceId = deviceInfo->deviceid;
m_tabletData.append(tabletData);
qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType;
}
#endif // QT_CONFIG(tabletevent)
#ifdef XCB_USE_XINPUT21
if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) {
scrollingDevice.deviceId = deviceInfo->deviceid;
// Only use legacy wheel button events when we don't have real scroll valuators.
scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations;
m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice);
qCDebug(lcQpaXInputDevices) << " it's a scrolling device";
}
#endif
if (!isTablet) {
TouchDeviceData *dev = populateTouchDevices(deviceInfo);
if (dev && lcQpaXInputDevices().isDebugEnabled()) {
if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen)
qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d",
dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
dev->qtTouchDevice->maximumTouchPoints());
else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad)
qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f",
dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
dev->qtTouchDevice->maximumTouchPoints(),
dev->size.width(), dev->size.height());
}
}
}
void QXcbConnection::xi2SetupDevices()
{
#if QT_CONFIG(tabletevent)
@ -108,159 +280,7 @@ void QXcbConnection::xi2SetupDevices()
// Only non-master pointing devices are relevant here.
if (devices[i].use != XISlavePointer)
continue;
qCDebug(lcQpaXInputDevices) << "input device " << devices[i].name << "ID" << devices[i].deviceid;
#if QT_CONFIG(tabletevent)
TabletData tabletData;
#endif
ScrollingDevice scrollingDevice;
for (int c = 0; c < devices[i].num_classes; ++c) {
XIAnyClassInfo *classinfo = devices[i].classes[c];
switch (classinfo->type) {
case XIValuatorClass: {
XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo);
const int valuatorAtom = qatom(vci->label);
qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms);
#if QT_CONFIG(tabletevent)
if (valuatorAtom < QXcbAtom::NAtoms) {
TabletData::ValuatorClassInfo info;
info.minVal = vci->min;
info.maxVal = vci->max;
info.number = vci->number;
tabletData.valuatorInfo[valuatorAtom] = info;
}
#endif // QT_CONFIG(tabletevent)
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 *>(classinfo);
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;
}
case XIButtonClass: {
XIButtonClassInfo *bci = reinterpret_cast<XIButtonClassInfo *>(classinfo);
if (bci->num_buttons >= 5) {
Atom label4 = bci->labels[3];
Atom label5 = bci->labels[4];
// Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
// button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) &&
(!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown))
scrollingDevice.legacyOrientations |= Qt::Vertical;
}
if (bci->num_buttons >= 7) {
Atom label6 = bci->labels[5];
Atom label7 = bci->labels[6];
if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight))
scrollingDevice.legacyOrientations |= Qt::Horizontal;
}
qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons);
break;
}
#endif
case XIKeyClass:
qCDebug(lcQpaXInputDevices) << " it's a keyboard";
break;
#ifdef XCB_USE_XINPUT22
case XITouchClass:
// will be handled in populateTouchDevices()
break;
#endif
default:
qCDebug(lcQpaXInputDevices) << " has class" << classinfo->type;
break;
}
}
bool isTablet = false;
#if QT_CONFIG(tabletevent)
// If we have found the valuators which we expect a tablet to have, it might be a tablet.
if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) &&
tabletData.valuatorInfo.contains(QXcbAtom::AbsY) &&
tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure))
isTablet = true;
// But we need to be careful not to take the touch and tablet-button devices as tablets.
QByteArray name = QByteArray(devices[i].name).toLower();
QString dbgType = QLatin1String("UNKNOWN");
if (name.contains("eraser")) {
isTablet = true;
tabletData.pointerType = QTabletEvent::Eraser;
dbgType = QLatin1String("eraser");
} else if (name.contains("cursor")) {
isTablet = true;
tabletData.pointerType = QTabletEvent::Cursor;
dbgType = QLatin1String("cursor");
} else if (name.contains("wacom") && name.contains("finger touch")) {
isTablet = false;
} else if ((name.contains("pen") || name.contains("stylus")) && isTablet) {
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("wacom") && isTablet && !name.contains("touch")) {
// combined device (evdev) rather than separate pen/eraser (wacom driver)
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) {
// some "Genius" tablets
isTablet = true;
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("waltop") && name.contains("tablet")) {
// other "Genius" tablets
// WALTOP International Corp. Slim Tablet
isTablet = true;
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else if (name.contains("uc-logic") && isTablet) {
tabletData.pointerType = QTabletEvent::Pen;
dbgType = QLatin1String("pen");
} else {
isTablet = false;
}
if (isTablet) {
tabletData.deviceId = devices[i].deviceid;
m_tabletData.append(tabletData);
qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType;
}
#endif // QT_CONFIG(tabletevent)
#ifdef XCB_USE_XINPUT21
if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) {
scrollingDevice.deviceId = devices[i].deviceid;
// Only use legacy wheel button events when we don't have real scroll valuators.
scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations;
m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice);
qCDebug(lcQpaXInputDevices) << " it's a scrolling device";
}
#endif
if (!isTablet) {
TouchDeviceData *dev = populateTouchDevices(&devices[i]);
if (dev && lcQpaXInputDevices().isDebugEnabled()) {
if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen)
qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d",
dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
dev->qtTouchDevice->maximumTouchPoints());
else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad)
qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f",
dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
dev->qtTouchDevice->maximumTouchPoints(),
dev->size.width(), dev->size.height());
}
}
xi2SetupDevice(&devices[i], false);
}
XIFreeDeviceInfo(devices);
}
@ -835,8 +855,16 @@ void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
{
xXIDeviceChangedEvent *xiEvent = reinterpret_cast<xXIDeviceChangedEvent *>(event);
switch (xiEvent->reason) {
case XIDeviceChange:
case XIDeviceChange: {
int nrDevices = 0;
Display *dpy = static_cast<Display *>(m_xlib_display);
XIDeviceInfo* deviceInfo = XIQueryDevice(dpy, xiEvent->sourceid, &nrDevices);
if (nrDevices <= 0)
return;
xi2SetupDevice(deviceInfo);
XIFreeDeviceInfo(deviceInfo);
break;
}
case XISlaveSwitch: {
#ifdef XCB_USE_XINPUT21
if (ScrollingDevice *scrollingDevice = scrollingDeviceForId(xiEvent->sourceid))