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