12#include <qpa/qwindowsysteminterface.h>
14#include <QPointingDevice>
16#include <QGuiApplication>
18#include <QLoggingCategory>
24Q_LOGGING_CATEGORY(lcTuioHandler,
"qt.qpa.tuio.handler")
25Q_LOGGING_CATEGORY(lcTuioSource,
"qt.qpa.tuio.source")
26Q_LOGGING_CATEGORY(lcTuioSet,
"qt.qpa.tuio.set")
32static bool forceDelivery = qEnvironmentVariableIsSet(
"QT_TUIOTOUCH_DELIVER_WITHOUT_FOCUS");
36 QStringList args = specification.split(
':');
37 int portNumber = 3333;
38 int rotationAngle = 0;
42 for (
int i = 0; i < args.size(); ++i) {
43 if (args.at(i).startsWith(
"udp=")) {
44 QString portString = args.at(i).section(
'=', 1, 1);
45 portNumber = portString.toInt();
46 }
else if (args.at(i).startsWith(
"tcp=")) {
47 QString portString = args.at(i).section(
'=', 1, 1);
48 portNumber = portString.toInt();
49 qCWarning(lcTuioHandler) <<
"TCP is not yet supported. Falling back to UDP on " << portNumber;
50 }
else if (args.at(i) ==
"invertx") {
52 }
else if (args.at(i) ==
"inverty") {
54 }
else if (args.at(i).startsWith(
"rotate=")) {
55 QString rotateArg = args.at(i).section(
'=', 1, 1);
56 int argValue = rotateArg.toInt();
61 rotationAngle = argValue;
70 m_transform = QTransform::fromTranslate(0.5, 0.5).rotate(rotationAngle).translate(-0.5, -0.5);
73 m_transform *= QTransform::fromTranslate(0.5, 0.5).scale(-1.0, 1.0).translate(-0.5, -0.5);
76 m_transform *= QTransform::fromTranslate(0.5, 0.5).scale(1.0, -1.0).translate(-0.5, -0.5);
81 m_device =
new QPointingDevice(QLatin1String(
"TUIO"), 1, QInputDevice::DeviceType::TouchScreen,
82 QPointingDevice::PointerType::Finger,
83 QInputDevice::Capability::Position |
84 QInputDevice::Capability::Area |
85 QInputDevice::Capability::Velocity |
86 QInputDevice::Capability::NormalizedPosition,
88 QWindowSystemInterface::registerInputDevice(m_device);
90 if (!m_socket.bind(QHostAddress::Any, portNumber)) {
91 qCWarning(lcTuioHandler) <<
"Failed to bind TUIO socket: " << m_socket.errorString();
95 connect(&m_socket, &QUdpSocket::readyRead,
this, &QTuioHandler::processPackets);
104 while (m_socket.hasPendingDatagrams()) {
106 datagram.resize(m_socket.pendingDatagramSize());
110 qint64 size = m_socket.readDatagram(datagram.data(), datagram.size(),
111 &sender, &senderPort);
116 if (size != datagram.size())
117 datagram.resize(size);
126 QList<QOscMessage> messages;
130 messages = bundle.messages();
132 QOscMessage msg(datagram);
133 if (!msg.isValid()) {
134 qCWarning(lcTuioSet) <<
"Got invalid datagram.";
137 messages.push_back(msg);
140 for (
const QOscMessage &message : std::as_const(messages)) {
141 if (message.addressPattern() ==
"/tuio/2Dcur") {
142 QList<QVariant> arguments = message.arguments();
143 if (arguments.size() == 0) {
144 qCWarning(lcTuioHandler,
"Ignoring TUIO message with no arguments");
148 QByteArray messageType = arguments.at(0).toByteArray();
149 if (messageType ==
"source") {
150 process2DCurSource(message);
151 }
else if (messageType ==
"alive") {
152 process2DCurAlive(message);
153 }
else if (messageType ==
"set") {
154 process2DCurSet(message);
155 }
else if (messageType ==
"fseq") {
156 process2DCurFseq(message);
158 qCWarning(lcTuioHandler) <<
"Ignoring unknown TUIO message type: " << messageType;
161 }
else if (message.addressPattern() ==
"/tuio/2Dobj") {
162 QList<QVariant> arguments = message.arguments();
163 if (arguments.size() == 0) {
164 qCWarning(lcTuioHandler,
"Ignoring TUIO message with no arguments");
168 QByteArray messageType = arguments.at(0).toByteArray();
169 if (messageType ==
"source") {
170 process2DObjSource(message);
171 }
else if (messageType ==
"alive") {
172 process2DObjAlive(message);
173 }
else if (messageType ==
"set") {
174 process2DObjSet(message);
175 }
else if (messageType ==
"fseq") {
176 process2DObjFseq(message);
178 qCWarning(lcTuioHandler) <<
"Ignoring unknown TUIO message type: " << messageType;
182 qCWarning(lcTuioHandler) <<
"Ignoring unknown address pattern " << message.addressPattern();
189void QTuioHandler::process2DCurSource(
const QOscMessage &message)
191 QList<QVariant> arguments = message.arguments();
192 if (arguments.size() != 2) {
193 qCWarning(lcTuioSource) <<
"Ignoring malformed TUIO source message: " << arguments.size();
197 if (QMetaType::Type(arguments.at(1).userType()) != QMetaType::QByteArray) {
198 qCWarning(lcTuioSource,
"Ignoring malformed TUIO source message (bad argument type)");
202 qCDebug(lcTuioSource) <<
"Got TUIO source message from: " << arguments.at(1).toByteArray();
205void QTuioHandler::process2DCurAlive(
const QOscMessage &message)
207 QList<QVariant> arguments = message.arguments();
215 QMap<
int, QTuioCursor> oldActiveCursors = m_activeCursors;
216 QMap<
int, QTuioCursor> newActiveCursors;
218 for (
int i = 1; i < arguments.size(); ++i) {
219 if (QMetaType::Type(arguments.at(i).userType()) != QMetaType::Int) {
220 qCWarning(lcTuioHandler) <<
"Ignoring malformed TUIO alive message (bad argument on position" << i << arguments <<
')';
224 int cursorId = arguments.at(i).toInt();
225 if (!oldActiveCursors.contains(cursorId)) {
228 cursor.setState(QEventPoint::State::Pressed);
229 newActiveCursors.insert(cursorId, cursor);
232 QTuioCursor cursor = oldActiveCursors.value(cursorId);
233 cursor.setState(QEventPoint::State::Stationary);
234 newActiveCursors.insert(cursorId, cursor);
235 oldActiveCursors.remove(cursorId);
240 QMap<
int, QTuioCursor>::ConstIterator it = oldActiveCursors.constBegin();
243 m_deadCursors.reserve(oldActiveCursors.size());
248 while (it != oldActiveCursors.constEnd()) {
249 m_deadCursors.append(it.value());
253 m_activeCursors = newActiveCursors;
256void QTuioHandler::process2DCurSet(
const QOscMessage &message)
258 QList<QVariant> arguments = message.arguments();
259 if (arguments.size() < 7) {
260 qCWarning(lcTuioSet) <<
"Ignoring malformed TUIO set message with too few arguments: " << arguments.size();
264 if (QMetaType::Type(arguments.at(1).userType()) != QMetaType::Int ||
265 QMetaType::Type(arguments.at(2).userType()) != QMetaType::Float ||
266 QMetaType::Type(arguments.at(3).userType()) != QMetaType::Float ||
267 QMetaType::Type(arguments.at(4).userType()) != QMetaType::Float ||
268 QMetaType::Type(arguments.at(5).userType()) != QMetaType::Float ||
269 QMetaType::Type(arguments.at(6).userType()) != QMetaType::Float
271 qCWarning(lcTuioSet) <<
"Ignoring malformed TUIO set message with bad types: " << arguments;
275 int cursorId = arguments.at(1).toInt();
276 float x = arguments.at(2).toFloat();
277 float y = arguments.at(3).toFloat();
278 float vx = arguments.at(4).toFloat();
279 float vy = arguments.at(5).toFloat();
280 float acceleration = arguments.at(6).toFloat();
282 QMap<
int, QTuioCursor>::Iterator it = m_activeCursors.find(cursorId);
283 if (it == m_activeCursors.end()) {
284 qCWarning(lcTuioSet) <<
"Ignoring malformed TUIO set for nonexistent cursor " << cursorId;
288 qCDebug(lcTuioSet) <<
"Processing SET for " << cursorId <<
" x: " << x << y << vx << vy << acceleration;
299 QWindowSystemInterface::TouchPoint tp;
303 tp.normalPosition = QPointF(tc
.x(), tc
.y());
305 if (!m_transform.isIdentity())
306 tp.normalPosition = m_transform.map(tp.normalPosition);
308 tp.state = tc.state();
317 QPointF relPos = QPointF(win->size().width() * tp.normalPosition.x(), win->size().height() * tp.normalPosition.y());
318 QPointF delta = relPos - relPos.toPoint();
319 tp.area.moveCenter(win->mapToGlobal(relPos.toPoint()) + delta);
320 tp.velocity = QVector2D(win->size().width() * tc
.vx(), win->size().height() * tc
.vy());
325void QTuioHandler::process2DCurFseq(
const QOscMessage &message)
329 QWindow *win = QGuiApplication::focusWindow();
330 if (!win && QGuiApplication::topLevelWindows().size() > 0 && forceDelivery)
331 win = QGuiApplication::topLevelWindows().at(0);
336 QList<QWindowSystemInterface::TouchPoint> tpl;
337 tpl.reserve(m_activeCursors.size() + m_deadCursors.size());
339 for (
const QTuioCursor &tc : std::as_const(m_activeCursors)) {
340 QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win);
344 for (
const QTuioCursor &tc : std::as_const(m_deadCursors)) {
345 QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win);
346 tp.state = QEventPoint::State::Released;
349 QWindowSystemInterface::handleTouchEvent(win, m_device, tpl);
351 m_deadCursors.clear();
354void QTuioHandler::process2DObjSource(
const QOscMessage &message)
356 QList<QVariant> arguments = message.arguments();
357 if (arguments.size() != 2) {
358 qCWarning(lcTuioSource ) <<
"Ignoring malformed TUIO source message: " << arguments.size();
362 if (QMetaType::Type(arguments.at(1).userType()) != QMetaType::QByteArray) {
363 qCWarning(lcTuioSource,
"Ignoring malformed TUIO source message (bad argument type)");
367 qCDebug(lcTuioSource) <<
"Got TUIO source message from: " << arguments.at(1).toByteArray();
370void QTuioHandler::process2DObjAlive(
const QOscMessage &message)
372 QList<QVariant> arguments = message.arguments();
380 QMap<
int, QTuioToken> oldActiveTokens = m_activeTokens;
381 QMap<
int, QTuioToken> newActiveTokens;
383 for (
int i = 1; i < arguments.size(); ++i) {
384 if (QMetaType::Type(arguments.at(i).userType()) != QMetaType::Int) {
385 qCWarning(lcTuioHandler) <<
"Ignoring malformed TUIO alive message (bad argument on position" << i << arguments <<
')';
389 int sessionId = arguments.at(i).toInt();
390 if (!oldActiveTokens.contains(sessionId)) {
393 token.setState(QEventPoint::State::Pressed);
394 newActiveTokens.insert(sessionId, token);
397 QTuioToken token = oldActiveTokens.value(sessionId);
398 token.setState(QEventPoint::State::Stationary);
399 newActiveTokens.insert(sessionId, token);
400 oldActiveTokens.remove(sessionId);
405 QMap<
int, QTuioToken>::ConstIterator it = oldActiveTokens.constBegin();
408 m_deadTokens.reserve(oldActiveTokens.size());
413 while (it != oldActiveTokens.constEnd()) {
414 m_deadTokens.append(it.value());
418 m_activeTokens = newActiveTokens;
421void QTuioHandler::process2DObjSet(
const QOscMessage &message)
423 QList<QVariant> arguments = message.arguments();
424 if (arguments.size() < 7) {
425 qCWarning(lcTuioSet) <<
"Ignoring malformed TUIO set message with too few arguments: " << arguments.size();
429 if (QMetaType::Type(arguments.at(1).userType()) != QMetaType::Int ||
430 QMetaType::Type(arguments.at(2).userType()) != QMetaType::Int ||
431 QMetaType::Type(arguments.at(3).userType()) != QMetaType::Float ||
432 QMetaType::Type(arguments.at(4).userType()) != QMetaType::Float ||
433 QMetaType::Type(arguments.at(5).userType()) != QMetaType::Float ||
434 QMetaType::Type(arguments.at(6).userType()) != QMetaType::Float ||
435 QMetaType::Type(arguments.at(7).userType()) != QMetaType::Float ||
436 QMetaType::Type(arguments.at(8).userType()) != QMetaType::Float ||
437 QMetaType::Type(arguments.at(9).userType()) != QMetaType::Float ||
438 QMetaType::Type(arguments.at(10).userType()) != QMetaType::Float) {
439 qCWarning(lcTuioSet) <<
"Ignoring malformed TUIO set message with bad types: " << arguments;
443 int id = arguments.at(1).toInt();
444 int classId = arguments.at(2).toInt();
445 float x = arguments.at(3).toFloat();
446 float y = arguments.at(4).toFloat();
447 float angle = arguments.at(5).toFloat();
448 float vx = arguments.at(6).toFloat();
449 float vy = arguments.at(7).toFloat();
450 float angularVelocity = arguments.at(8).toFloat();
451 float acceleration = arguments.at(9).toFloat();
452 float angularAcceleration = arguments.at(10).toFloat();
454 QMap<
int, QTuioToken>::Iterator it = m_activeTokens.find(id);
455 if (it == m_activeTokens.end()) {
456 qCWarning(lcTuioSet) <<
"Ignoring malformed TUIO set for nonexistent token " << classId;
460 qCDebug(lcTuioSet) <<
"Processing SET for token " << classId << id <<
" @ " << x << y <<
" angle: " << angle <<
461 "vel" << vx << vy << angularVelocity <<
"acc" << acceleration << angularAcceleration;
476 QWindowSystemInterface::TouchPoint tp;
481 tp.normalPosition = QPointF(tc
.x(), tc
.y());
483 if (!m_transform.isIdentity())
484 tp.normalPosition = m_transform.map(tp.normalPosition);
486 tp.state = tc.state();
489 QPointF relPos = QPointF(win->size().width() * tp.normalPosition.x(), win->size().height() * tp.normalPosition.y());
490 QPointF delta = relPos - relPos.toPoint();
491 tp.area.moveCenter(win->mapToGlobal(relPos.toPoint()) + delta);
492 tp.velocity = QVector2D(win->size().width() * tc
.vx(), win->size().height() * tc
.vy());
498void QTuioHandler::process2DObjFseq(
const QOscMessage &message)
502 QWindow *win = QGuiApplication::focusWindow();
503 if (!win && QGuiApplication::topLevelWindows().size() > 0 && forceDelivery)
504 win = QGuiApplication::topLevelWindows().at(0);
509 QList<QWindowSystemInterface::TouchPoint> tpl;
510 tpl.reserve(m_activeTokens.size() + m_deadTokens.size());
512 for (
const QTuioToken & t : std::as_const(m_activeTokens)) {
513 QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win);
517 for (
const QTuioToken & t : std::as_const(m_deadTokens)) {
518 QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win);
519 tp.state = QEventPoint::State::Released;
520 tp.velocity = QVector2D();
523 QWindowSystemInterface::handleTouchEvent(win, m_device, tpl);
525 m_deadTokens.clear();
530#include "moc_qtuiohandler_p.cpp"
void setAcceleration(float acceleration)
void setAngularAcceleration(float angularAcceleration)
void setAcceleration(float acceleration)
void setAngle(float angle)
void setClassId(int classId)
void setAngularVelocity(float angularVelocity)
constexpr float qRadiansToDegrees(float radians)