1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qlibinputtouch_p.h"
41 #include "qtouchoutputmapping_p.h"
42 #include <libinput.h>
43 #include <QtGui/QGuiApplication>
44 #include <QtGui/QScreen>
45 #include <QtGui/private/qhighdpiscaling_p.h>
46 
47 QT_BEGIN_NAMESPACE
48 
Q_DECLARE_LOGGING_CATEGORY(qLcLibInput)49 Q_DECLARE_LOGGING_CATEGORY(qLcLibInput)
50 
51 QWindowSystemInterface::TouchPoint *QLibInputTouch::DeviceState::point(int32_t slot)
52 {
53     const int id = qMax(0, slot);
54 
55     for (int i = 0; i < m_points.count(); ++i)
56         if (m_points.at(i).id == id)
57             return &m_points[i];
58 
59     return nullptr;
60 }
61 
deviceState(libinput_event_touch * e)62 QLibInputTouch::DeviceState *QLibInputTouch::deviceState(libinput_event_touch *e)
63 {
64     libinput_device *dev = libinput_event_get_device(libinput_event_touch_get_base_event(e));
65     return &m_devState[dev];
66 }
67 
getPos(libinput_event_touch * e)68 QPointF QLibInputTouch::getPos(libinput_event_touch *e)
69 {
70     DeviceState *state = deviceState(e);
71     QScreen *screen = QGuiApplication::primaryScreen();
72     if (!state->m_screenName.isEmpty()) {
73         if (!m_screen) {
74             const QList<QScreen *> screens = QGuiApplication::screens();
75             for (QScreen *s : screens) {
76                 if (s->name() == state->m_screenName) {
77                     m_screen = s;
78                     break;
79                 }
80             }
81         }
82         if (m_screen)
83             screen = m_screen;
84     }
85     const QRect geom = QHighDpi::toNativePixels(screen->geometry(), screen);
86     const double x = libinput_event_touch_get_x_transformed(e, geom.width());
87     const double y = libinput_event_touch_get_y_transformed(e, geom.height());
88     return geom.topLeft() + QPointF(x, y);
89 }
90 
registerDevice(libinput_device * dev)91 void QLibInputTouch::registerDevice(libinput_device *dev)
92 {
93     struct udev_device *udev_device;
94     udev_device = libinput_device_get_udev_device(dev);
95     QString devNode = QString::fromUtf8(udev_device_get_devnode(udev_device));
96     QString devName = QString::fromUtf8(libinput_device_get_name(dev));
97 
98     qCDebug(qLcLibInput, "libinput: registerDevice %s - %s",
99             qPrintable(devNode), qPrintable(devName));
100 
101     QTouchOutputMapping mapping;
102     if (mapping.load()) {
103         m_devState[dev].m_screenName = mapping.screenNameForDeviceNode(devNode);
104         if (!m_devState[dev].m_screenName.isEmpty())
105             qCDebug(qLcLibInput, "libinput: Mapping device %s to screen %s",
106                     qPrintable(devNode), qPrintable(m_devState[dev].m_screenName));
107     }
108 
109     QTouchDevice *&td = m_devState[dev].m_touchDevice;
110     td = new QTouchDevice;
111     td->setName(devName);
112     td->setType(QTouchDevice::TouchScreen);
113     td->setCapabilities(QTouchDevice::Position | QTouchDevice::Area);
114     QWindowSystemInterface::registerTouchDevice(td);
115 }
116 
unregisterDevice(libinput_device * dev)117 void QLibInputTouch::unregisterDevice(libinput_device *dev)
118 {
119     Q_UNUSED(dev);
120     // There is no way to remove a QTouchDevice.
121 }
122 
processTouchDown(libinput_event_touch * e)123 void QLibInputTouch::processTouchDown(libinput_event_touch *e)
124 {
125     int slot = libinput_event_touch_get_slot(e);
126     DeviceState *state = deviceState(e);
127     QWindowSystemInterface::TouchPoint *tp = state->point(slot);
128     if (tp) {
129         qWarning("Incorrect touch state");
130     } else {
131         QWindowSystemInterface::TouchPoint newTp;
132         newTp.id = qMax(0, slot);
133         newTp.state = Qt::TouchPointPressed;
134         newTp.area = QRect(0, 0, 8, 8);
135         newTp.area.moveCenter(getPos(e));
136         state->m_points.append(newTp);
137     }
138 }
139 
processTouchMotion(libinput_event_touch * e)140 void QLibInputTouch::processTouchMotion(libinput_event_touch *e)
141 {
142     int slot = libinput_event_touch_get_slot(e);
143     DeviceState *state = deviceState(e);
144     QWindowSystemInterface::TouchPoint *tp = state->point(slot);
145     if (tp) {
146         Qt::TouchPointState tmpState = Qt::TouchPointMoved;
147         const QPointF p = getPos(e);
148         if (tp->area.center() == p)
149             tmpState = Qt::TouchPointStationary;
150         else
151             tp->area.moveCenter(p);
152         // 'down' may be followed by 'motion' within the same "frame".
153         // Handle this by compressing and keeping the Pressed state until the 'frame'.
154         if (tp->state != Qt::TouchPointPressed && tp->state != Qt::TouchPointReleased)
155             tp->state = tmpState;
156     } else {
157         qWarning("Inconsistent touch state (got 'motion' without 'down')");
158     }
159 }
160 
processTouchUp(libinput_event_touch * e)161 void QLibInputTouch::processTouchUp(libinput_event_touch *e)
162 {
163     int slot = libinput_event_touch_get_slot(e);
164     DeviceState *state = deviceState(e);
165     QWindowSystemInterface::TouchPoint *tp = state->point(slot);
166     if (tp) {
167         tp->state = Qt::TouchPointReleased;
168         // There may not be a Frame event after the last Up. Work this around.
169         Qt::TouchPointStates s;
170         for (int i = 0; i < state->m_points.count(); ++i)
171             s |= state->m_points.at(i).state;
172         if (s == Qt::TouchPointReleased)
173             processTouchFrame(e);
174     } else {
175         qWarning("Inconsistent touch state (got 'up' without 'down')");
176     }
177 }
178 
processTouchCancel(libinput_event_touch * e)179 void QLibInputTouch::processTouchCancel(libinput_event_touch *e)
180 {
181     DeviceState *state = deviceState(e);
182     if (state->m_touchDevice)
183         QWindowSystemInterface::handleTouchCancelEvent(nullptr, state->m_touchDevice, QGuiApplication::keyboardModifiers());
184     else
185         qWarning("TouchCancel without registered device");
186 }
187 
processTouchFrame(libinput_event_touch * e)188 void QLibInputTouch::processTouchFrame(libinput_event_touch *e)
189 {
190     DeviceState *state = deviceState(e);
191     if (!state->m_touchDevice) {
192         qWarning("TouchFrame without registered device");
193         return;
194     }
195     if (state->m_points.isEmpty())
196         return;
197 
198     QWindowSystemInterface::handleTouchEvent(nullptr, state->m_touchDevice, state->m_points,
199                                              QGuiApplication::keyboardModifiers());
200 
201     for (int i = 0; i < state->m_points.count(); ++i) {
202         QWindowSystemInterface::TouchPoint &tp(state->m_points[i]);
203         if (tp.state == Qt::TouchPointReleased)
204             state->m_points.removeAt(i--);
205         else if (tp.state == Qt::TouchPointPressed)
206             tp.state = Qt::TouchPointStationary;
207     }
208 }
209 
210 QT_END_NAMESPACE
211