Lomiri
MouseTouchAdaptor.cpp
1 /*
2  * Copyright (C) 2013, 2015 Canonical Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com>
17  */
18 
19 
20 /* Some parts of the code were copied from the XCB platform plugin of the Qt Toolkit,
21  * which is under the following license:
22  */
23 
24 /****************************************************************************
25 **
26 ** Copyright (C) 2015 The Qt Company Ltd.
27 ** Contact: http://www.qt.io/licensing/
28 **
29 ** This file is part of the .
30 **
31 ** $QT_BEGIN_LICENSE:LGPL21$
32 ** Commercial License Usage
33 ** Licensees holding valid commercial Qt licenses may use this file in
34 ** accordance with the commercial license agreement provided with the
35 ** Software or, alternatively, in accordance with the terms contained in
36 ** a written agreement between you and The Qt Company. For licensing terms
37 ** and conditions see http://www.qt.io/terms-conditions. For further
38 ** information use the contact form at http://www.qt.io/contact-us.
39 **
40 ** GNU Lesser General Public License Usage
41 ** Alternatively, this file may be used under the terms of the GNU Lesser
42 ** General Public License version 2.1 or version 3 as published by the Free
43 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
44 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
45 ** following information to ensure the GNU Lesser General Public License
46 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
47 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
48 **
49 ** As a special exception, The Qt Company gives you certain additional
50 ** rights. These rights are described in The Qt Company LGPL Exception
51 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
52 **
53 ** $QT_END_LICENSE$
54 **
55 ****************************************************************************/
56 
57 #include "MouseTouchAdaptor.h"
58 
59 #include <qpa/qplatformnativeinterface.h>
60 #include <qpa/qwindowsysteminterface.h>
61 
62 #include <QCoreApplication>
63 #include <QMouseEvent>
64 #include <QTest>
65 
66 #include <X11/extensions/XInput2.h>
67 #include <X11/extensions/XI2proto.h>
68 
69 using QTest::QTouchEventSequence;
70 
71 namespace {
72 MouseTouchAdaptor *g_instance = nullptr;
73 
74 const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
75 const Qt::KeyboardModifiers QUAD_PRESS_MODIFIER = TRI_PRESS_MODIFIER|Qt::MetaModifier;
76 
77 Qt::MouseButton translateMouseButton(xcb_button_t detail)
78 {
79  switch (detail) {
80  case 1: return Qt::LeftButton;
81  case 2: return Qt::MidButton;
82  case 3: return Qt::RightButton;
83  // Button values 4-7 are Wheel events
84  default: return Qt::NoButton;
85  }
86 }
87 
88 Qt::KeyboardModifiers translateMofidier(uint32_t mod)
89 {
90  Qt::KeyboardModifiers qtMod = Qt::NoModifier;
91 
92  if (mod & 0x01) qtMod |= Qt::ShiftModifier;
93  if (mod & 0x04) qtMod |= Qt::ControlModifier;
94  if (mod & 0x08) qtMod |= Qt::AltModifier;
95  if (mod & 0x40) qtMod |= Qt::MetaModifier;
96 
97  return qtMod;
98 }
99 } // end of anonymous namespace
100 
101 MouseTouchAdaptor::MouseTouchAdaptor()
102  : QObject(nullptr)
103  , m_leftButtonIsPressed(false)
104  , m_triPressModifier(false)
105  , m_quadPressModifier(false)
106  , m_enabled(true)
107 {
108  QCoreApplication::instance()->installNativeEventFilter(this);
109 
110  m_touchDevice = new QTouchDevice;
111  m_touchDevice->setType(QTouchDevice::TouchScreen);
112  QWindowSystemInterface::registerTouchDevice(m_touchDevice);
113 
114  fetchXInput2Info();
115 }
116 
117 MouseTouchAdaptor::~MouseTouchAdaptor()
118 {
119  g_instance = nullptr;
120 }
121 
122 MouseTouchAdaptor* MouseTouchAdaptor::instance()
123 {
124  if (!g_instance) {
125  g_instance = new MouseTouchAdaptor;
126  }
127 
128  return g_instance;
129 }
130 
131 void MouseTouchAdaptor::fetchXInput2Info()
132 {
133  QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
134  Display *xDisplay = static_cast<Display*>(nativeInterface->nativeResourceForIntegration("Display"));
135  if (xDisplay && XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) {
136  int xiMajor = 2;
137  m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End
138  if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
139  m_xi2Minor = 1; // for smooth scrolling 2.1 is enough
140  if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
141  m_xi2Minor = 0; // for tablet support 2.0 is enough
142  m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
143  } else
144  m_xi2Enabled = true;
145  } else {
146  m_xi2Enabled = true;
147  }
148  }
149 }
150 
151 // Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed:
152 // - "pad0" became "extension"
153 // - "pad1" and "pad" became "pad0"
154 // New and old version of this struct share the following fields:
155 // NOTE: API might change again in the next release of xcb in which case this comment will
156 // need to be updated to reflect the reality.
157 typedef struct qt_xcb_ge_event_t {
158  uint8_t response_type;
159  uint8_t extension;
160  uint16_t sequence;
161  uint32_t length;
162  uint16_t event_type;
163 } qt_xcb_ge_event_t;
164 
165 bool xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *ev, int opCode)
166 {
167  qt_xcb_ge_event_t *event = (qt_xcb_ge_event_t *)ev;
168  // xGenericEvent has "extension" on the second byte, the same is true for xcb_ge_event_t starting from
169  // the xcb version 1.9.3, prior to that it was called "pad0".
170  if (event->extension == opCode) {
171  // xcb event structs contain stuff that wasn't on the wire, the full_sequence field
172  // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes.
173  // Move this data back to have the same layout in memory as it was on the wire
174  // and allow casting, overwriting the full_sequence field.
175  memmove((char*) event + 32, (char*) event + 36, event->length * 4);
176  return true;
177  }
178  return false;
179 }
180 
181 static inline qreal fixed1616ToReal(FP1616 val)
182 {
183  return qreal(val) / 0x10000;
184 }
185 
186 bool MouseTouchAdaptor::xi2HandleEvent(xcb_ge_event_t *event)
187 {
188  if (!xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) {
189  return false;
190  }
191 
192  xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
193  xXIDeviceEvent *xiDeviceEvent = 0;
194 
195  switch (xiEvent->evtype) {
196  case XI_ButtonPress:
197  case XI_ButtonRelease:
198  case XI_Motion:
199  xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
200  break;
201  default:
202  break;
203  }
204 
205  if (!xiDeviceEvent) {
206  return false;
207  }
208 
209  switch (xiDeviceEvent->evtype) {
210  case XI_ButtonPress:
211  return handleButtonPress(
212  static_cast<WId>(xiDeviceEvent->event),
213  xiDeviceEvent->detail,
214  xiDeviceEvent->mods.base_mods,
215  fixed1616ToReal(xiDeviceEvent->event_x),
216  fixed1616ToReal(xiDeviceEvent->event_y));
217  case XI_ButtonRelease:
218  return handleButtonRelease(
219  static_cast<WId>(xiDeviceEvent->event),
220  xiDeviceEvent->detail,
221  xiDeviceEvent->mods.base_mods,
222  fixed1616ToReal(xiDeviceEvent->event_x),
223  fixed1616ToReal(xiDeviceEvent->event_y));
224  case XI_Motion:
225  return handleMotionNotify(
226  static_cast<WId>(xiDeviceEvent->event),
227  xiDeviceEvent->mods.base_mods,
228  fixed1616ToReal(xiDeviceEvent->event_x),
229  fixed1616ToReal(xiDeviceEvent->event_y));
230  return true;
231  default:
232  return false;
233  }
234 }
235 
236 
237 bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
238  void * message, long * /*result*/)
239 {
240  static int eventCount = 0;
241  eventCount++;
242  if (!m_enabled) {
243  return false;
244  }
245 
246  if (eventType != "xcb_generic_event_t") {
247  // wrong backend.
248  qWarning("MouseTouchAdaptor: XCB backend not in use. Adaptor inoperative!");
249  return false;
250  }
251 
252  xcb_generic_event_t *xcbEvent = static_cast<xcb_generic_event_t *>(message);
253 
254  switch (xcbEvent->response_type & ~0x80) {
255  case XCB_BUTTON_PRESS: {
256  auto pressEvent = reinterpret_cast<xcb_button_press_event_t *>(xcbEvent);
257  return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail, 0,
258  pressEvent->event_x, pressEvent->event_y);
259  }
260  case XCB_BUTTON_RELEASE: {
261  auto releaseEvent = reinterpret_cast<xcb_button_release_event_t *>(xcbEvent);
262  return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail, 0,
263  releaseEvent->event_x, releaseEvent->event_y);
264  }
265  case XCB_MOTION_NOTIFY: {
266  auto motionEvent = reinterpret_cast<xcb_motion_notify_event_t *>(xcbEvent);
267  return handleMotionNotify(static_cast<WId>(motionEvent->event), 0,
268  motionEvent->event_x, motionEvent->event_y);
269  }
270  case XCB_GE_GENERIC:
271  if (m_xi2Enabled) {
272  return xi2HandleEvent(reinterpret_cast<xcb_ge_event_t *>(xcbEvent));
273  } else {
274  return false;
275  }
276  default:
277  return false;
278  };
279 }
280 
281 bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y)
282 {
283  Qt::MouseButton button = translateMouseButton(detail);
284  Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
285 
286  // Just eat the event if it wasn't a left mouse press
287  if (button != Qt::LeftButton)
288  return true;
289 
290  QWindow *targetWindow = findQWindowWithXWindowID(windowId);
291 
292  QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
293 
294  QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
295  false /* autoCommit */);
296  touchEvent.press(0 /* touchId */, windowPos);
297  if (qtMod == TRI_PRESS_MODIFIER) {
298  touchEvent.press(1, windowPos);
299  touchEvent.press(2, windowPos);
300  m_triPressModifier = true;
301  }
302  if (qtMod == QUAD_PRESS_MODIFIER) {
303  touchEvent.press(1, windowPos);
304  touchEvent.press(2, windowPos);
305  touchEvent.press(3, windowPos);
306  m_quadPressModifier = true;
307  }
308 
309  touchEvent.commit(false /* processEvents */);
310 
311  m_leftButtonIsPressed = true;
312  return true;
313 }
314 
315 bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, uint32_t, int x, int y)
316 {
317  Qt::MouseButton button = translateMouseButton(detail);
318 
319  // Don't eat the event if it wasn't a left mouse press
320  if (button != Qt::LeftButton)
321  return false;
322 
323  QWindow *targetWindow = findQWindowWithXWindowID(windowId);
324 
325  QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
326 
327  QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
328  false /* autoCommit */);
329  touchEvent.release(0 /* touchId */, windowPos);
330  if (m_triPressModifier) {
331  touchEvent.release(1, windowPos);
332  touchEvent.release(2, windowPos);
333  }
334  if (m_quadPressModifier) {
335  touchEvent.release(1, windowPos);
336  touchEvent.release(2, windowPos);
337  touchEvent.release(3, windowPos);
338  }
339  touchEvent.commit(false /* processEvents */);
340 
341  m_leftButtonIsPressed = false;
342  m_triPressModifier = false;
343  m_quadPressModifier = false;
344  return true;
345 }
346 
347 bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y)
348 {
349  if (!m_leftButtonIsPressed) {
350  return true;
351  }
352  Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
353 
354  QWindow *targetWindow = findQWindowWithXWindowID(windowId);
355 
356  QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
357 
358  QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
359  false /* autoCommit */);
360  touchEvent.move(0 /* touchId */, windowPos);
361  if (m_triPressModifier) {
362  if (qtMod == TRI_PRESS_MODIFIER) {
363  touchEvent.move(1, windowPos);
364  touchEvent.move(2, windowPos);
365  } else {
366  // released modifiers
367  touchEvent.release(1, windowPos);
368  touchEvent.release(2, windowPos);
369  m_triPressModifier = false;
370  }
371  }
372  if (m_quadPressModifier) {
373  if (qtMod == QUAD_PRESS_MODIFIER) {
374  touchEvent.move(1, windowPos);
375  touchEvent.move(2, windowPos);
376  touchEvent.move(3, windowPos);
377  } else {
378  touchEvent.release(1, windowPos);
379  touchEvent.release(2, windowPos);
380  touchEvent.release(3, windowPos);
381  m_quadPressModifier = false;
382  }
383  }
384  touchEvent.commit(false /* processEvents */);
385 
386  return true;
387 }
388 
389 QWindow *MouseTouchAdaptor::findQWindowWithXWindowID(WId windowId)
390 {
391  QWindowList windowList = QGuiApplication::topLevelWindows();
392  QWindow *foundWindow = nullptr;
393 
394  int i = 0;
395  while (!foundWindow && i < windowList.count()) {
396  QWindow *window = windowList[i];
397  if (window->winId() == windowId) {
398  foundWindow = window;
399  } else {
400  ++i;
401  }
402  }
403 
404  Q_ASSERT(foundWindow);
405  return foundWindow;
406 }
407 
408 bool MouseTouchAdaptor::enabled() const
409 {
410  return m_enabled;
411 }
412 
413 void MouseTouchAdaptor::setEnabled(bool value)
414 {
415  if (value != m_enabled) {
416  m_enabled = value;
417  Q_EMIT enabledChanged(value);
418  }
419 }