Unity 8
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 
76 Qt::MouseButton translateMouseButton(xcb_button_t detail)
77 {
78  switch (detail) {
79  case 1: return Qt::LeftButton;
80  case 2: return Qt::MidButton;
81  case 3: return Qt::RightButton;
82  // Button values 4-7 are Wheel events
83  default: return Qt::NoButton;
84  }
85 }
86 
87 Qt::KeyboardModifiers translateMofidier(uint32_t mod)
88 {
89  Qt::KeyboardModifiers qtMod = Qt::NoModifier;
90 
91  if (mod & 0x01) qtMod |= Qt::ShiftModifier;
92  if (mod & 0x04) qtMod |= Qt::ControlModifier;
93  if (mod & 0x08) qtMod |= Qt::AltModifier;
94  if (mod & 0x80) qtMod |= Qt::MetaModifier;
95 
96  return qtMod;
97 }
98 } // end of anonymous namespace
99 
100 MouseTouchAdaptor::MouseTouchAdaptor()
101  : QObject(nullptr)
102  , m_leftButtonIsPressed(false)
103  , m_triPressModifier(false)
104  , m_enabled(true)
105 {
106  QCoreApplication::instance()->installNativeEventFilter(this);
107 
108  m_touchDevice = new QTouchDevice;
109  m_touchDevice->setType(QTouchDevice::TouchScreen);
110  QWindowSystemInterface::registerTouchDevice(m_touchDevice);
111 
112  fetchXInput2Info();
113 }
114 
115 MouseTouchAdaptor::~MouseTouchAdaptor()
116 {
117  g_instance = nullptr;
118 }
119 
120 MouseTouchAdaptor* MouseTouchAdaptor::instance()
121 {
122  if (!g_instance) {
123  g_instance = new MouseTouchAdaptor;
124  }
125 
126  return g_instance;
127 }
128 
129 void MouseTouchAdaptor::fetchXInput2Info()
130 {
131  QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
132  Display *xDisplay = static_cast<Display*>(nativeInterface->nativeResourceForIntegration("Display"));
133  if (xDisplay && XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) {
134  int xiMajor = 2;
135  m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End
136  if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
137  m_xi2Minor = 1; // for smooth scrolling 2.1 is enough
138  if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
139  m_xi2Minor = 0; // for tablet support 2.0 is enough
140  m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
141  } else
142  m_xi2Enabled = true;
143  } else {
144  m_xi2Enabled = true;
145  }
146  }
147 }
148 
149 // Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed:
150 // - "pad0" became "extension"
151 // - "pad1" and "pad" became "pad0"
152 // New and old version of this struct share the following fields:
153 // NOTE: API might change again in the next release of xcb in which case this comment will
154 // need to be updated to reflect the reality.
155 typedef struct qt_xcb_ge_event_t {
156  uint8_t response_type;
157  uint8_t extension;
158  uint16_t sequence;
159  uint32_t length;
160  uint16_t event_type;
161 } qt_xcb_ge_event_t;
162 
163 bool xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *ev, int opCode)
164 {
165  qt_xcb_ge_event_t *event = (qt_xcb_ge_event_t *)ev;
166  // xGenericEvent has "extension" on the second byte, the same is true for xcb_ge_event_t starting from
167  // the xcb version 1.9.3, prior to that it was called "pad0".
168  if (event->extension == opCode) {
169  // xcb event structs contain stuff that wasn't on the wire, the full_sequence field
170  // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes.
171  // Move this data back to have the same layout in memory as it was on the wire
172  // and allow casting, overwriting the full_sequence field.
173  memmove((char*) event + 32, (char*) event + 36, event->length * 4);
174  return true;
175  }
176  return false;
177 }
178 
179 static inline qreal fixed1616ToReal(FP1616 val)
180 {
181  return qreal(val) / 0x10000;
182 }
183 
184 bool MouseTouchAdaptor::xi2HandleEvent(xcb_ge_event_t *event)
185 {
186  if (!xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) {
187  return false;
188  }
189 
190  xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
191  xXIDeviceEvent *xiDeviceEvent = 0;
192 
193  switch (xiEvent->evtype) {
194  case XI_ButtonPress:
195  case XI_ButtonRelease:
196  case XI_Motion:
197  xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
198  break;
199  default:
200  break;
201  }
202 
203  if (!xiDeviceEvent) {
204  return false;
205  }
206 
207  switch (xiDeviceEvent->evtype) {
208  case XI_ButtonPress:
209  return handleButtonPress(
210  static_cast<WId>(xiDeviceEvent->event),
211  xiDeviceEvent->detail,
212  xiDeviceEvent->mods.base_mods,
213  fixed1616ToReal(xiDeviceEvent->event_x),
214  fixed1616ToReal(xiDeviceEvent->event_y));
215  case XI_ButtonRelease:
216  return handleButtonRelease(
217  static_cast<WId>(xiDeviceEvent->event),
218  xiDeviceEvent->detail,
219  xiDeviceEvent->mods.base_mods,
220  fixed1616ToReal(xiDeviceEvent->event_x),
221  fixed1616ToReal(xiDeviceEvent->event_y));
222  case XI_Motion:
223  return handleMotionNotify(
224  static_cast<WId>(xiDeviceEvent->event),
225  xiDeviceEvent->mods.base_mods,
226  fixed1616ToReal(xiDeviceEvent->event_x),
227  fixed1616ToReal(xiDeviceEvent->event_y));
228  return true;
229  default:
230  return false;
231  }
232 }
233 
234 
235 bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
236  void * message, long * /*result*/)
237 {
238  static int eventCount = 0;
239  eventCount++;
240  if (!m_enabled) {
241  return false;
242  }
243 
244  if (eventType != "xcb_generic_event_t") {
245  // wrong backend.
246  qWarning("MouseTouchAdaptor: XCB backend not in use. Adaptor inoperative!");
247  return false;
248  }
249 
250  xcb_generic_event_t *xcbEvent = static_cast<xcb_generic_event_t *>(message);
251 
252  switch (xcbEvent->response_type & ~0x80) {
253  case XCB_BUTTON_PRESS: {
254  auto pressEvent = reinterpret_cast<xcb_button_press_event_t *>(xcbEvent);
255  return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail, 0,
256  pressEvent->event_x, pressEvent->event_y);
257  }
258  case XCB_BUTTON_RELEASE: {
259  auto releaseEvent = reinterpret_cast<xcb_button_release_event_t *>(xcbEvent);
260  return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail, 0,
261  releaseEvent->event_x, releaseEvent->event_y);
262  }
263  case XCB_MOTION_NOTIFY: {
264  auto motionEvent = reinterpret_cast<xcb_motion_notify_event_t *>(xcbEvent);
265  return handleMotionNotify(static_cast<WId>(motionEvent->event), 0,
266  motionEvent->event_x, motionEvent->event_y);
267  }
268  case XCB_GE_GENERIC:
269  if (m_xi2Enabled) {
270  return xi2HandleEvent(reinterpret_cast<xcb_ge_event_t *>(xcbEvent));
271  } else {
272  return false;
273  }
274  default:
275  return false;
276  };
277 }
278 
279 bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y)
280 {
281  Qt::MouseButton button = translateMouseButton(detail);
282  Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
283 
284  // Just eat the event if it wasn't a left mouse press
285  if (button != Qt::LeftButton)
286  return true;
287 
288  QWindow *targetWindow = findQWindowWithXWindowID(windowId);
289 
290  QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
291 
292  QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
293  false /* autoCommit */);
294  touchEvent.press(0 /* touchId */, windowPos);
295  if (qtMod == TRI_PRESS_MODIFIER) {
296  touchEvent.press(1, windowPos);
297  touchEvent.press(2, windowPos);
298  m_triPressModifier = true;
299  }
300  touchEvent.commit(false /* processEvents */);
301 
302  m_leftButtonIsPressed = true;
303  return true;
304 }
305 
306 bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, uint32_t, int x, int y)
307 {
308  Qt::MouseButton button = translateMouseButton(detail);
309 
310  // Don't eat the event if it wasn't a left mouse press
311  if (button != Qt::LeftButton)
312  return false;
313 
314  QWindow *targetWindow = findQWindowWithXWindowID(windowId);
315 
316  QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
317 
318  QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
319  false /* autoCommit */);
320  touchEvent.release(0 /* touchId */, windowPos);
321  if (m_triPressModifier) {
322  touchEvent.release(1, windowPos);
323  touchEvent.release(2, windowPos);
324  }
325  touchEvent.commit(false /* processEvents */);
326 
327  m_leftButtonIsPressed = false;
328  m_triPressModifier = false;
329  return true;
330 }
331 
332 bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y)
333 {
334  if (!m_leftButtonIsPressed) {
335  return true;
336  }
337  Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
338 
339  QWindow *targetWindow = findQWindowWithXWindowID(windowId);
340 
341  QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
342 
343  QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
344  false /* autoCommit */);
345  touchEvent.move(0 /* touchId */, windowPos);
346  if (m_triPressModifier) {
347  if (qtMod == TRI_PRESS_MODIFIER) {
348  touchEvent.move(1, windowPos);
349  touchEvent.move(2, windowPos);
350  } else {
351  // released modifiers
352  touchEvent.release(1, windowPos);
353  touchEvent.release(2, windowPos);
354  m_triPressModifier = false;
355  }
356  }
357  touchEvent.commit(false /* processEvents */);
358 
359  return true;
360 }
361 
362 QWindow *MouseTouchAdaptor::findQWindowWithXWindowID(WId windowId)
363 {
364  QWindowList windowList = QGuiApplication::topLevelWindows();
365  QWindow *foundWindow = nullptr;
366 
367  int i = 0;
368  while (!foundWindow && i < windowList.count()) {
369  QWindow *window = windowList[i];
370  if (window->winId() == windowId) {
371  foundWindow = window;
372  } else {
373  ++i;
374  }
375  }
376 
377  Q_ASSERT(foundWindow);
378  return foundWindow;
379 }
380 
381 bool MouseTouchAdaptor::enabled() const
382 {
383  return m_enabled;
384 }
385 
386 void MouseTouchAdaptor::setEnabled(bool value)
387 {
388  if (value != m_enabled) {
389  m_enabled = value;
390  Q_EMIT enabledChanged(value);
391  }
392 }