Lomiri
Loading...
Searching...
No Matches
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
69using QTest::QTouchEventSequence;
70
71namespace {
72MouseTouchAdaptor *g_instance = nullptr;
73
74const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
75const Qt::KeyboardModifiers QUAD_PRESS_MODIFIER = TRI_PRESS_MODIFIER|Qt::MetaModifier;
76
77Qt::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
88Qt::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
101MouseTouchAdaptor::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
117MouseTouchAdaptor::~MouseTouchAdaptor()
118{
119 g_instance = nullptr;
120}
121
122MouseTouchAdaptor* MouseTouchAdaptor::instance()
123{
124 if (!g_instance) {
125 g_instance = new MouseTouchAdaptor;
126 }
127
128 return g_instance;
129}
130
131void 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.
157typedef 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
165bool 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
181static inline qreal fixed1616ToReal(FP1616 val)
182{
183 return qreal(val) / 0x10000;
184}
185
186bool 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
237bool 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
281bool 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
315bool 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
347bool 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
389QWindow *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
408bool MouseTouchAdaptor::enabled() const
409{
410 return m_enabled;
411}
412
413void MouseTouchAdaptor::setEnabled(bool value)
414{
415 if (value != m_enabled) {
416 m_enabled = value;
417 Q_EMIT enabledChanged(value);
418 }
419}