xcb: handle XI2 input button and motion events from slave devices

On Ubuntu 24.04, when the SuperKey is pressed, Qt only receives XI2
input events sent from the slave pointer devices on a mouse click or
a mouse move, but does not handle those XI2 input events.

Fixing this by allowing Qt to handle such an XI2 event and generate
a QMouseEvent for it if it is not handled previously.

Upon receipt of an aforementioned event, check if the event
is a duplicate XI2 event. A duplicate XI2 event means that
the XI2 event was sent from another device previously.

- If it is not a duplicate event, the XI2 event is passed to
  QXcbWindow to be handled. Eventually, a QMouseEvent is
  generated and sent from the GUI for the XI2 event.
- If it is a duplicate event, ignore it.

Note, part of commit:3bc0f1724ae49c2fd7e6d7bcb650350d20d12246
is re-done with the fix.

Fixes: QTBUG-110841
Pick-to: 6.7 6.5 6.2 5.15
Change-Id: I93beddef7261c37ddbe8bfdb6ae08c95f8f155c5
Reviewed-by: Liang Qi <liang.qi@qt.io>
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit b71be292780b858f2c55ce92601452e2ea946de2)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Dongmei Wang 2024-05-29 19:09:26 -07:00 committed by Qt Cherry-pick Bot
parent 432b7187e1
commit 65b6de2a20

View File

@ -682,23 +682,96 @@ static inline qreal fixed1616ToReal(xcb_input_fp1616_t val)
return qreal(val) / 0x10000;
}
//implementation is ported from https://codereview.qt-project.org/c/qt/qtbase/+/231552/12/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp#558
namespace {
/*! \internal
Qt listens for XIAllDevices to avoid losing mouse events. This function
ensures that we don't process the same event twice: from a slave device and
then again from a master device.
In a normal use case (e.g. mouse press and release inside a window), we will
drop events from master devices as duplicates. Other advantage of processing
events from slave devices is that they don't share button state. All buttons
on a master device share the state.
Examples of special cases:
\list
\li During system move/resize, window manager (_NET_WM_MOVERESIZE) grabs the
master pointer, in this case we process the matching release from the slave
device. A master device event is not sent by the server, hence no duplicate
event to drop. If we listened for XIAllMasterDevices instead, we would never
see a release event in this case.
\li If we dismiss a context menu by clicking somewhere outside a Qt application,
we will process the mouse press from the master pointer as that is the
device we are grabbing. We are not grabbing slave devices (grabbing on the
slave device is buggy according to 19d289ab1b5bde3e136765e5432b5c7d004df3a4).
And since the event occurs outside our window, the slave device event is
not sent to us by the server, hence no duplicate event to drop.
\endlist
*/
bool isDuplicateEvent(xcb_ge_event_t *event)
{
Q_ASSERT(event);
struct qXIEvent {
bool isValid = false;
uint16_t sourceid;
uint8_t evtype;
uint32_t detail;
int32_t root_x;
int32_t root_y;
};
static qXIEvent lastSeenEvent;
bool isDuplicate = false;
auto *xiDeviceEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
if (lastSeenEvent.isValid) {
isDuplicate = lastSeenEvent.sourceid == xiDeviceEvent->sourceid &&
lastSeenEvent.evtype == xiDeviceEvent->event_type &&
lastSeenEvent.detail == xiDeviceEvent->detail &&
lastSeenEvent.root_x == xiDeviceEvent->root_x &&
lastSeenEvent.root_y == xiDeviceEvent->root_y;
} else {
lastSeenEvent.isValid = true;
}
lastSeenEvent.sourceid = xiDeviceEvent->sourceid;
lastSeenEvent.evtype = xiDeviceEvent->event_type;
lastSeenEvent.detail = xiDeviceEvent->detail;
lastSeenEvent.root_x = xiDeviceEvent->root_x;
lastSeenEvent.root_y = xiDeviceEvent->root_y;
if (isDuplicate) {
qCDebug(lcQpaXInputEvents, "Duplicate XI2 event %d", event->event_type);
// This sanity check ensures that special cases like QTBUG-59277 keep working.
lastSeenEvent.isValid = false; // An event can be a duplicate only once.
}
return isDuplicate;
}
} // namespace
void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
{
auto *xiEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
setTime(xiEvent->time);
if (m_xiSlavePointerIds.contains(xiEvent->deviceid) && xiEvent->event_type != XCB_INPUT_PROPERTY) {
if (m_xiSlavePointerIds.contains(xiEvent->deviceid)) {
if (!(xiEvent->event_type == XCB_INPUT_BUTTON_PRESS
|| xiEvent->event_type == XCB_INPUT_BUTTON_RELEASE
|| xiEvent->event_type == XCB_INPUT_MOTION)) {
if (!m_duringSystemMoveResize)
return;
if (xiEvent->event == XCB_NONE)
return;
if (xiEvent->event_type == XCB_INPUT_BUTTON_RELEASE
&& xiEvent->detail == XCB_BUTTON_INDEX_1 ) {
if (xiEvent->event_type == XCB_INPUT_TOUCH_END)
abortSystemMoveResize(xiEvent->event);
} else if (xiEvent->event_type == XCB_INPUT_TOUCH_END) {
abortSystemMoveResize(xiEvent->event);
return;
} else {
return;
}
}
@ -710,11 +783,27 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
switch (xiEvent->event_type) {
case XCB_INPUT_BUTTON_PRESS:
case XCB_INPUT_BUTTON_RELEASE:
case XCB_INPUT_MOTION:
case XCB_INPUT_MOTION: {
if (isDuplicateEvent(event))
return;
if (m_xiSlavePointerIds.contains(xiEvent->deviceid)) {
if (m_duringSystemMoveResize) {
if (xiEvent->event_type == XCB_INPUT_BUTTON_RELEASE
&& xiEvent->detail == XCB_BUTTON_INDEX_1 ) {
abortSystemMoveResize(xiEvent->event);
} else {
return;
}
}
}
xiDeviceEvent = xiEvent;
eventListener = windowEventListenerFromId(xiDeviceEvent->event);
sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master
break;
}
case XCB_INPUT_TOUCH_BEGIN:
case XCB_INPUT_TOUCH_UPDATE:
case XCB_INPUT_TOUCH_END:
{
case XCB_INPUT_TOUCH_END: {
xiDeviceEvent = xiEvent;
eventListener = windowEventListenerFromId(xiDeviceEvent->event);
sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master