1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2014 Robin Burchell <robin.burchell@viroteck.net> |
4 | ** Copyright (C) 2016 The Qt Company Ltd. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the QtCore module of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:LGPL$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU Lesser General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
22 | ** packaging of this file. Please review the following information to |
23 | ** ensure the GNU Lesser General Public License version 3 requirements |
24 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
25 | ** |
26 | ** GNU General Public License Usage |
27 | ** Alternatively, this file may be used under the terms of the GNU |
28 | ** General Public License version 2.0 or (at your option) the GNU General |
29 | ** Public license version 3 or any later version approved by the KDE Free |
30 | ** Qt Foundation. The licenses are as published by the Free Software |
31 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
32 | ** included in the packaging of this file. Please review the following |
33 | ** information to ensure the GNU General Public License requirements will |
34 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
35 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
36 | ** |
37 | ** $QT_END_LICENSE$ |
38 | ** |
39 | ****************************************************************************/ |
40 | |
41 | #include "qtuiohandler_p.h" |
42 | |
43 | #include "qtuiocursor_p.h" |
44 | #include "qtuiotoken_p.h" |
45 | #include "qoscbundle_p.h" |
46 | #include "qoscmessage_p.h" |
47 | |
48 | #include <qpa/qwindowsysteminterface.h> |
49 | |
50 | #include <QTouchDevice> |
51 | #include <QWindow> |
52 | #include <QGuiApplication> |
53 | |
54 | #include <QLoggingCategory> |
55 | #include <QRect> |
56 | #include <qmath.h> |
57 | |
58 | QT_BEGIN_NAMESPACE |
59 | |
60 | Q_LOGGING_CATEGORY(lcTuioHandler, "qt.qpa.tuio.handler" ) |
61 | Q_LOGGING_CATEGORY(lcTuioSource, "qt.qpa.tuio.source" ) |
62 | Q_LOGGING_CATEGORY(lcTuioSet, "qt.qpa.tuio.set" ) |
63 | |
64 | // With TUIO the first application takes exclusive ownership of the "device" |
65 | // we cannot attach more than one application to the same port anyway. |
66 | // Forcing delivery makes it easy to use simulators in the same machine |
67 | // and forget about headaches about unfocused TUIO windows. |
68 | static bool forceDelivery = qEnvironmentVariableIsSet(varName: "QT_TUIOTOUCH_DELIVER_WITHOUT_FOCUS" ); |
69 | |
70 | QTuioHandler::QTuioHandler(const QString &specification) |
71 | : m_device(new QTouchDevice) // not leaked, QTouchDevice cleans up registered devices itself |
72 | { |
73 | QStringList args = specification.split(sep: ':'); |
74 | int portNumber = 3333; |
75 | int rotationAngle = 0; |
76 | bool invertx = false; |
77 | bool inverty = false; |
78 | |
79 | for (int i = 0; i < args.count(); ++i) { |
80 | if (args.at(i).startsWith(s: "udp=" )) { |
81 | QString portString = args.at(i).section(asep: '=', astart: 1, aend: 1); |
82 | portNumber = portString.toInt(); |
83 | } else if (args.at(i).startsWith(s: "tcp=" )) { |
84 | QString portString = args.at(i).section(asep: '=', astart: 1, aend: 1); |
85 | portNumber = portString.toInt(); |
86 | qCWarning(lcTuioHandler) << "TCP is not yet supported. Falling back to UDP on " << portNumber; |
87 | } else if (args.at(i) == "invertx" ) { |
88 | invertx = true; |
89 | } else if (args.at(i) == "inverty" ) { |
90 | inverty = true; |
91 | } else if (args.at(i).startsWith(s: "rotate=" )) { |
92 | QString rotateArg = args.at(i).section(asep: '=', astart: 1, aend: 1); |
93 | int argValue = rotateArg.toInt(); |
94 | switch (argValue) { |
95 | case 90: |
96 | case 180: |
97 | case 270: |
98 | rotationAngle = argValue; |
99 | default: |
100 | break; |
101 | } |
102 | } |
103 | } |
104 | |
105 | if (rotationAngle) |
106 | m_transform = QTransform::fromTranslate(dx: 0.5, dy: 0.5).rotate(a: rotationAngle).translate(dx: -0.5, dy: -0.5); |
107 | |
108 | if (invertx) |
109 | m_transform *= QTransform::fromTranslate(dx: 0.5, dy: 0.5).scale(sx: -1.0, sy: 1.0).translate(dx: -0.5, dy: -0.5); |
110 | |
111 | if (inverty) |
112 | m_transform *= QTransform::fromTranslate(dx: 0.5, dy: 0.5).scale(sx: 1.0, sy: -1.0).translate(dx: -0.5, dy: -0.5); |
113 | |
114 | m_device->setName("TUIO" ); // TODO: multiple based on SOURCE? |
115 | m_device->setType(QTouchDevice::TouchScreen); |
116 | m_device->setCapabilities(QTouchDevice::Position | |
117 | QTouchDevice::Area | |
118 | QTouchDevice::Velocity | |
119 | QTouchDevice::NormalizedPosition); |
120 | QWindowSystemInterface::registerTouchDevice(device: m_device); |
121 | |
122 | if (!m_socket.bind(address: QHostAddress::Any, port: portNumber)) { |
123 | qCWarning(lcTuioHandler) << "Failed to bind TUIO socket: " << m_socket.errorString(); |
124 | return; |
125 | } |
126 | |
127 | connect(sender: &m_socket, signal: &QUdpSocket::readyRead, receiver: this, slot: &QTuioHandler::processPackets); |
128 | } |
129 | |
130 | QTuioHandler::~QTuioHandler() |
131 | { |
132 | } |
133 | |
134 | void QTuioHandler::processPackets() |
135 | { |
136 | while (m_socket.hasPendingDatagrams()) { |
137 | QByteArray datagram; |
138 | datagram.resize(size: m_socket.pendingDatagramSize()); |
139 | QHostAddress sender; |
140 | quint16 senderPort; |
141 | |
142 | qint64 size = m_socket.readDatagram(data: datagram.data(), maxlen: datagram.size(), |
143 | host: &sender, port: &senderPort); |
144 | |
145 | if (size == -1) |
146 | continue; |
147 | |
148 | if (size != datagram.size()) |
149 | datagram.resize(size); |
150 | |
151 | // "A typical TUIO bundle will contain an initial ALIVE message, |
152 | // followed by an arbitrary number of SET messages that can fit into the |
153 | // actual bundle capacity and a concluding FSEQ message. A minimal TUIO |
154 | // bundle needs to contain at least the compulsory ALIVE and FSEQ |
155 | // messages. The FSEQ frame ID is incremented for each delivered bundle, |
156 | // while redundant bundles can be marked using the frame sequence ID |
157 | // -1." |
158 | QVector<QOscMessage> messages; |
159 | |
160 | QOscBundle bundle(datagram); |
161 | if (bundle.isValid()) { |
162 | messages = bundle.messages(); |
163 | } else { |
164 | QOscMessage msg(datagram); |
165 | if (!msg.isValid()) { |
166 | qCWarning(lcTuioSet) << "Got invalid datagram." ; |
167 | continue; |
168 | } |
169 | messages.push_back(t: msg); |
170 | } |
171 | |
172 | for (const QOscMessage &message : qAsConst(t&: messages)) { |
173 | if (message.addressPattern() == "/tuio/2Dcur" ) { |
174 | QList<QVariant> arguments = message.arguments(); |
175 | if (arguments.count() == 0) { |
176 | qCWarning(lcTuioHandler, "Ignoring TUIO message with no arguments" ); |
177 | continue; |
178 | } |
179 | |
180 | QByteArray messageType = arguments.at(i: 0).toByteArray(); |
181 | if (messageType == "source" ) { |
182 | process2DCurSource(message); |
183 | } else if (messageType == "alive" ) { |
184 | process2DCurAlive(message); |
185 | } else if (messageType == "set" ) { |
186 | process2DCurSet(message); |
187 | } else if (messageType == "fseq" ) { |
188 | process2DCurFseq(message); |
189 | } else { |
190 | qCWarning(lcTuioHandler) << "Ignoring unknown TUIO message type: " << messageType; |
191 | continue; |
192 | } |
193 | } else if (message.addressPattern() == "/tuio/2Dobj" ) { |
194 | QList<QVariant> arguments = message.arguments(); |
195 | if (arguments.count() == 0) { |
196 | qCWarning(lcTuioHandler, "Ignoring TUIO message with no arguments" ); |
197 | continue; |
198 | } |
199 | |
200 | QByteArray messageType = arguments.at(i: 0).toByteArray(); |
201 | if (messageType == "source" ) { |
202 | process2DObjSource(message); |
203 | } else if (messageType == "alive" ) { |
204 | process2DObjAlive(message); |
205 | } else if (messageType == "set" ) { |
206 | process2DObjSet(message); |
207 | } else if (messageType == "fseq" ) { |
208 | process2DObjFseq(message); |
209 | } else { |
210 | qCWarning(lcTuioHandler) << "Ignoring unknown TUIO message type: " << messageType; |
211 | continue; |
212 | } |
213 | } else { |
214 | qCWarning(lcTuioHandler) << "Ignoring unknown address pattern " << message.addressPattern(); |
215 | continue; |
216 | } |
217 | } |
218 | } |
219 | } |
220 | |
221 | void QTuioHandler::process2DCurSource(const QOscMessage &message) |
222 | { |
223 | QList<QVariant> arguments = message.arguments(); |
224 | if (arguments.count() != 2) { |
225 | qCWarning(lcTuioSource) << "Ignoring malformed TUIO source message: " << arguments.count(); |
226 | return; |
227 | } |
228 | |
229 | if (QMetaType::Type(arguments.at(i: 1).userType()) != QMetaType::QByteArray) { |
230 | qCWarning(lcTuioSource, "Ignoring malformed TUIO source message (bad argument type)" ); |
231 | return; |
232 | } |
233 | |
234 | qCDebug(lcTuioSource) << "Got TUIO source message from: " << arguments.at(i: 1).toByteArray(); |
235 | } |
236 | |
237 | void QTuioHandler::process2DCurAlive(const QOscMessage &message) |
238 | { |
239 | QList<QVariant> arguments = message.arguments(); |
240 | |
241 | // delta the notified cursors that are active, against the ones we already |
242 | // know of. |
243 | // |
244 | // TBD: right now we're assuming one 2Dcur alive message corresponds to a |
245 | // new data source from the input. is this correct, or do we need to store |
246 | // changes and only process the deltas on fseq? |
247 | QMap<int, QTuioCursor> oldActiveCursors = m_activeCursors; |
248 | QMap<int, QTuioCursor> newActiveCursors; |
249 | |
250 | for (int i = 1; i < arguments.count(); ++i) { |
251 | if (QMetaType::Type(arguments.at(i).userType()) != QMetaType::Int) { |
252 | qCWarning(lcTuioHandler) << "Ignoring malformed TUIO alive message (bad argument on position" << i << arguments << ')'; |
253 | return; |
254 | } |
255 | |
256 | int cursorId = arguments.at(i).toInt(); |
257 | if (!oldActiveCursors.contains(akey: cursorId)) { |
258 | // newly active |
259 | QTuioCursor cursor(cursorId); |
260 | cursor.setState(Qt::TouchPointPressed); |
261 | newActiveCursors.insert(akey: cursorId, avalue: cursor); |
262 | } else { |
263 | // we already know about it, remove it so it isn't marked as released |
264 | QTuioCursor cursor = oldActiveCursors.value(akey: cursorId); |
265 | cursor.setState(Qt::TouchPointStationary); // position change in SET will update if needed |
266 | newActiveCursors.insert(akey: cursorId, avalue: cursor); |
267 | oldActiveCursors.remove(akey: cursorId); |
268 | } |
269 | } |
270 | |
271 | // anything left is dead now |
272 | QMap<int, QTuioCursor>::ConstIterator it = oldActiveCursors.constBegin(); |
273 | |
274 | // deadCursors should be cleared from the last FSEQ now |
275 | m_deadCursors.reserve(asize: oldActiveCursors.size()); |
276 | |
277 | // TODO: there could be an issue of resource exhaustion here if FSEQ isn't |
278 | // sent in a timely fashion. we should probably track message counts and |
279 | // force-flush if we get too many built up. |
280 | while (it != oldActiveCursors.constEnd()) { |
281 | m_deadCursors.append(t: it.value()); |
282 | ++it; |
283 | } |
284 | |
285 | m_activeCursors = newActiveCursors; |
286 | } |
287 | |
288 | void QTuioHandler::process2DCurSet(const QOscMessage &message) |
289 | { |
290 | QList<QVariant> arguments = message.arguments(); |
291 | if (arguments.count() < 7) { |
292 | qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with too few arguments: " << arguments.count(); |
293 | return; |
294 | } |
295 | |
296 | if (QMetaType::Type(arguments.at(i: 1).userType()) != QMetaType::Int || |
297 | QMetaType::Type(arguments.at(i: 2).userType()) != QMetaType::Float || |
298 | QMetaType::Type(arguments.at(i: 3).userType()) != QMetaType::Float || |
299 | QMetaType::Type(arguments.at(i: 4).userType()) != QMetaType::Float || |
300 | QMetaType::Type(arguments.at(i: 5).userType()) != QMetaType::Float || |
301 | QMetaType::Type(arguments.at(i: 6).userType()) != QMetaType::Float |
302 | ) { |
303 | qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with bad types: " << arguments; |
304 | return; |
305 | } |
306 | |
307 | int cursorId = arguments.at(i: 1).toInt(); |
308 | float x = arguments.at(i: 2).toFloat(); |
309 | float y = arguments.at(i: 3).toFloat(); |
310 | float vx = arguments.at(i: 4).toFloat(); |
311 | float vy = arguments.at(i: 5).toFloat(); |
312 | float acceleration = arguments.at(i: 6).toFloat(); |
313 | |
314 | QMap<int, QTuioCursor>::Iterator it = m_activeCursors.find(akey: cursorId); |
315 | if (it == m_activeCursors.end()) { |
316 | qCWarning(lcTuioSet) << "Ignoring malformed TUIO set for nonexistent cursor " << cursorId; |
317 | return; |
318 | } |
319 | |
320 | qCDebug(lcTuioSet) << "Processing SET for " << cursorId << " x: " << x << y << vx << vy << acceleration; |
321 | QTuioCursor &cur = *it; |
322 | cur.setX(x); |
323 | cur.setY(y); |
324 | cur.setVX(vx); |
325 | cur.setVY(vy); |
326 | cur.setAcceleration(acceleration); |
327 | } |
328 | |
329 | QWindowSystemInterface::TouchPoint QTuioHandler::cursorToTouchPoint(const QTuioCursor &tc, QWindow *win) |
330 | { |
331 | QWindowSystemInterface::TouchPoint tp; |
332 | tp.id = tc.id(); |
333 | tp.pressure = 1.0f; |
334 | |
335 | tp.normalPosition = QPointF(tc.x(), tc.y()); |
336 | |
337 | if (!m_transform.isIdentity()) |
338 | tp.normalPosition = m_transform.map(p: tp.normalPosition); |
339 | |
340 | tp.state = tc.state(); |
341 | |
342 | // we map the touch to the size of the window. we do this, because frankly, |
343 | // trying to figure out which part of the screen to hit in order to press an |
344 | // element on the UI is pretty tricky when one is not using an overlay-style |
345 | // TUIO device. |
346 | // |
347 | // in the future, it might make sense to make this choice optional, |
348 | // dependent on the spec. |
349 | QPointF relPos = QPointF(win->size().width() * tp.normalPosition.x(), win->size().height() * tp.normalPosition.y()); |
350 | QPointF delta = relPos - relPos.toPoint(); |
351 | tp.area.moveCenter(p: win->mapToGlobal(pos: relPos.toPoint()) + delta); |
352 | tp.velocity = QVector2D(win->size().width() * tc.vx(), win->size().height() * tc.vy()); |
353 | return tp; |
354 | } |
355 | |
356 | |
357 | void QTuioHandler::process2DCurFseq(const QOscMessage &message) |
358 | { |
359 | Q_UNUSED(message); // TODO: do we need to do anything with the frame id? |
360 | |
361 | QWindow *win = QGuiApplication::focusWindow(); |
362 | if (!win && QGuiApplication::topLevelWindows().length() > 0 && forceDelivery) |
363 | win = QGuiApplication::topLevelWindows().at(i: 0); |
364 | |
365 | if (!win) |
366 | return; |
367 | |
368 | QList<QWindowSystemInterface::TouchPoint> tpl; |
369 | tpl.reserve(alloc: m_activeCursors.size() + m_deadCursors.size()); |
370 | |
371 | for (const QTuioCursor &tc : qAsConst(t&: m_activeCursors)) { |
372 | QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win); |
373 | tpl.append(t: tp); |
374 | } |
375 | |
376 | for (const QTuioCursor &tc : qAsConst(t&: m_deadCursors)) { |
377 | QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win); |
378 | tp.state = Qt::TouchPointReleased; |
379 | tpl.append(t: tp); |
380 | } |
381 | QWindowSystemInterface::handleTouchEvent(window: win, device: m_device, points: tpl); |
382 | |
383 | m_deadCursors.clear(); |
384 | } |
385 | |
386 | void QTuioHandler::process2DObjSource(const QOscMessage &message) |
387 | { |
388 | QList<QVariant> arguments = message.arguments(); |
389 | if (arguments.count() != 2) { |
390 | qCWarning(lcTuioSource, ) << "Ignoring malformed TUIO source message: " << arguments.count(); |
391 | return; |
392 | } |
393 | |
394 | if (QMetaType::Type(arguments.at(i: 1).userType()) != QMetaType::QByteArray) { |
395 | qCWarning(lcTuioSource, "Ignoring malformed TUIO source message (bad argument type)" ); |
396 | return; |
397 | } |
398 | |
399 | qCDebug(lcTuioSource) << "Got TUIO source message from: " << arguments.at(i: 1).toByteArray(); |
400 | } |
401 | |
402 | void QTuioHandler::process2DObjAlive(const QOscMessage &message) |
403 | { |
404 | QList<QVariant> arguments = message.arguments(); |
405 | |
406 | // delta the notified tokens that are active, against the ones we already |
407 | // know of. |
408 | // |
409 | // TBD: right now we're assuming one 2DObj alive message corresponds to a |
410 | // new data source from the input. is this correct, or do we need to store |
411 | // changes and only process the deltas on fseq? |
412 | QMap<int, QTuioToken> oldActiveTokens = m_activeTokens; |
413 | QMap<int, QTuioToken> newActiveTokens; |
414 | |
415 | for (int i = 1; i < arguments.count(); ++i) { |
416 | if (QMetaType::Type(arguments.at(i).userType()) != QMetaType::Int) { |
417 | qCWarning(lcTuioHandler) << "Ignoring malformed TUIO alive message (bad argument on position" << i << arguments << ')'; |
418 | return; |
419 | } |
420 | |
421 | int sessionId = arguments.at(i).toInt(); |
422 | if (!oldActiveTokens.contains(akey: sessionId)) { |
423 | // newly active |
424 | QTuioToken token(sessionId); |
425 | token.setState(Qt::TouchPointPressed); |
426 | newActiveTokens.insert(akey: sessionId, avalue: token); |
427 | } else { |
428 | // we already know about it, remove it so it isn't marked as released |
429 | QTuioToken token = oldActiveTokens.value(akey: sessionId); |
430 | token.setState(Qt::TouchPointStationary); // position change in SET will update if needed |
431 | newActiveTokens.insert(akey: sessionId, avalue: token); |
432 | oldActiveTokens.remove(akey: sessionId); |
433 | } |
434 | } |
435 | |
436 | // anything left is dead now |
437 | QMap<int, QTuioToken>::ConstIterator it = oldActiveTokens.constBegin(); |
438 | |
439 | // deadTokens should be cleared from the last FSEQ now |
440 | m_deadTokens.reserve(asize: oldActiveTokens.size()); |
441 | |
442 | // TODO: there could be an issue of resource exhaustion here if FSEQ isn't |
443 | // sent in a timely fashion. we should probably track message counts and |
444 | // force-flush if we get too many built up. |
445 | while (it != oldActiveTokens.constEnd()) { |
446 | m_deadTokens.append(t: it.value()); |
447 | ++it; |
448 | } |
449 | |
450 | m_activeTokens = newActiveTokens; |
451 | } |
452 | |
453 | void QTuioHandler::process2DObjSet(const QOscMessage &message) |
454 | { |
455 | QList<QVariant> arguments = message.arguments(); |
456 | if (arguments.count() < 7) { |
457 | qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with too few arguments: " << arguments.count(); |
458 | return; |
459 | } |
460 | |
461 | if (QMetaType::Type(arguments.at(i: 1).userType()) != QMetaType::Int || |
462 | QMetaType::Type(arguments.at(i: 2).userType()) != QMetaType::Int || |
463 | QMetaType::Type(arguments.at(i: 3).userType()) != QMetaType::Float || |
464 | QMetaType::Type(arguments.at(i: 4).userType()) != QMetaType::Float || |
465 | QMetaType::Type(arguments.at(i: 5).userType()) != QMetaType::Float || |
466 | QMetaType::Type(arguments.at(i: 6).userType()) != QMetaType::Float || |
467 | QMetaType::Type(arguments.at(i: 7).userType()) != QMetaType::Float || |
468 | QMetaType::Type(arguments.at(i: 8).userType()) != QMetaType::Float || |
469 | QMetaType::Type(arguments.at(i: 9).userType()) != QMetaType::Float || |
470 | QMetaType::Type(arguments.at(i: 10).userType()) != QMetaType::Float) { |
471 | qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with bad types: " << arguments; |
472 | return; |
473 | } |
474 | |
475 | int id = arguments.at(i: 1).toInt(); |
476 | int classId = arguments.at(i: 2).toInt(); |
477 | float x = arguments.at(i: 3).toFloat(); |
478 | float y = arguments.at(i: 4).toFloat(); |
479 | float angle = arguments.at(i: 5).toFloat(); |
480 | float vx = arguments.at(i: 6).toFloat(); |
481 | float vy = arguments.at(i: 7).toFloat(); |
482 | float angularVelocity = arguments.at(i: 8).toFloat(); |
483 | float acceleration = arguments.at(i: 9).toFloat(); |
484 | float angularAcceleration = arguments.at(i: 10).toFloat(); |
485 | |
486 | QMap<int, QTuioToken>::Iterator it = m_activeTokens.find(akey: id); |
487 | if (it == m_activeTokens.end()) { |
488 | qCWarning(lcTuioSet) << "Ignoring malformed TUIO set for nonexistent token " << classId; |
489 | return; |
490 | } |
491 | |
492 | qCDebug(lcTuioSet) << "Processing SET for token " << classId << id << " @ " << x << y << " angle: " << angle << |
493 | "vel" << vx << vy << angularVelocity << "acc" << acceleration << angularAcceleration; |
494 | QTuioToken &tok = *it; |
495 | tok.setClassId(classId); |
496 | tok.setX(x); |
497 | tok.setY(y); |
498 | tok.setVX(vx); |
499 | tok.setVY(vy); |
500 | tok.setAcceleration(acceleration); |
501 | tok.setAngle(angle); |
502 | tok.setAngularVelocity(angularAcceleration); |
503 | tok.setAngularAcceleration(angularAcceleration); |
504 | } |
505 | |
506 | QWindowSystemInterface::TouchPoint QTuioHandler::tokenToTouchPoint(const QTuioToken &tc, QWindow *win) |
507 | { |
508 | QWindowSystemInterface::TouchPoint tp; |
509 | tp.id = tc.id(); |
510 | tp.uniqueId = tc.classId(); // TODO TUIO 2.0: populate a QVariant, and register the mapping from int to arbitrary UID data |
511 | tp.flags = QTouchEvent::TouchPoint::Token; |
512 | tp.pressure = 1.0f; |
513 | |
514 | tp.normalPosition = QPointF(tc.x(), tc.y()); |
515 | |
516 | if (!m_transform.isIdentity()) |
517 | tp.normalPosition = m_transform.map(p: tp.normalPosition); |
518 | |
519 | tp.state = tc.state(); |
520 | |
521 | // We map the token position to the size of the window. |
522 | QPointF relPos = QPointF(win->size().width() * tp.normalPosition.x(), win->size().height() * tp.normalPosition.y()); |
523 | QPointF delta = relPos - relPos.toPoint(); |
524 | tp.area.moveCenter(p: win->mapToGlobal(pos: relPos.toPoint()) + delta); |
525 | tp.velocity = QVector2D(win->size().width() * tc.vx(), win->size().height() * tc.vy()); |
526 | tp.rotation = qRadiansToDegrees(radians: tc.angle()); |
527 | return tp; |
528 | } |
529 | |
530 | |
531 | void QTuioHandler::process2DObjFseq(const QOscMessage &message) |
532 | { |
533 | Q_UNUSED(message); // TODO: do we need to do anything with the frame id? |
534 | |
535 | QWindow *win = QGuiApplication::focusWindow(); |
536 | if (!win && QGuiApplication::topLevelWindows().length() > 0 && forceDelivery) |
537 | win = QGuiApplication::topLevelWindows().at(i: 0); |
538 | |
539 | if (!win) |
540 | return; |
541 | |
542 | QList<QWindowSystemInterface::TouchPoint> tpl; |
543 | tpl.reserve(alloc: m_activeTokens.size() + m_deadTokens.size()); |
544 | |
545 | for (const QTuioToken & t : qAsConst(t&: m_activeTokens)) { |
546 | QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(tc: t, win); |
547 | tpl.append(t: tp); |
548 | } |
549 | |
550 | for (const QTuioToken & t : qAsConst(t&: m_deadTokens)) { |
551 | QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(tc: t, win); |
552 | tp.state = Qt::TouchPointReleased; |
553 | tp.velocity = QVector2D(); |
554 | tpl.append(t: tp); |
555 | } |
556 | QWindowSystemInterface::handleTouchEvent(window: win, device: m_device, points: tpl); |
557 | |
558 | m_deadTokens.clear(); |
559 | } |
560 | |
561 | QT_END_NAMESPACE |
562 | |
563 | |