Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qtuiohandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2014 Robin Burchell <robin.burchell@viroteck.net>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qtuiohandler_p.h"
6
7#include "qtuiocursor_p.h"
8#include "qtuiotoken_p.h"
9#include "qoscbundle_p.h"
10#include "qoscmessage_p.h"
11
12#include <qpa/qwindowsysteminterface.h>
13
14#include <QPointingDevice>
15#include <QWindow>
16#include <QGuiApplication>
17
18#include <QLoggingCategory>
19#include <QRect>
20#include <qmath.h>
21
23
24Q_LOGGING_CATEGORY(lcTuioHandler, "qt.qpa.tuio.handler")
25Q_LOGGING_CATEGORY(lcTuioSource, "qt.qpa.tuio.source")
26Q_LOGGING_CATEGORY(lcTuioSet, "qt.qpa.tuio.set")
27
28// With TUIO the first application takes exclusive ownership of the "device"
29// we cannot attach more than one application to the same port anyway.
30// Forcing delivery makes it easy to use simulators in the same machine
31// and forget about headaches about unfocused TUIO windows.
32static bool forceDelivery = qEnvironmentVariableIsSet("QT_TUIOTOUCH_DELIVER_WITHOUT_FOCUS");
33
34QTuioHandler::QTuioHandler(const QString &specification)
35{
36 QStringList args = specification.split(':');
37 int portNumber = 3333;
38 int rotationAngle = 0;
39 bool invertx = false;
40 bool inverty = false;
41
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") {
51 invertx = true;
52 } else if (args.at(i) == "inverty") {
53 inverty = true;
54 } else if (args.at(i).startsWith("rotate=")) {
55 QString rotateArg = args.at(i).section('=', 1, 1);
56 int argValue = rotateArg.toInt();
57 switch (argValue) {
58 case 90:
59 case 180:
60 case 270:
61 rotationAngle = argValue;
62 break;
63 default:
64 break;
65 }
66 }
67 }
68
69 if (rotationAngle)
70 m_transform = QTransform::fromTranslate(0.5, 0.5).rotate(rotationAngle).translate(-0.5, -0.5);
71
72 if (invertx)
73 m_transform *= QTransform::fromTranslate(0.5, 0.5).scale(-1.0, 1.0).translate(-0.5, -0.5);
74
75 if (inverty)
76 m_transform *= QTransform::fromTranslate(0.5, 0.5).scale(1.0, -1.0).translate(-0.5, -0.5);
77
78 // not leaked, QPointingDevice cleans up registered devices itself
79 // TODO register each device based on SOURCE, not just an all-purpose generic touchscreen
80 // TODO define seats when multiple connections occur
87 16, 0);
89
90 if (!m_socket.bind(QHostAddress::Any, portNumber)) {
91 qCWarning(lcTuioHandler) << "Failed to bind TUIO socket: " << m_socket.errorString();
92 return;
93 }
94
95 connect(&m_socket, &QUdpSocket::readyRead, this, &QTuioHandler::processPackets);
96}
97
101
102void QTuioHandler::processPackets()
103{
104 while (m_socket.hasPendingDatagrams()) {
105 QByteArray datagram;
106 datagram.resize(m_socket.pendingDatagramSize());
108 quint16 senderPort;
109
110 qint64 size = m_socket.readDatagram(datagram.data(), datagram.size(),
111 &sender, &senderPort);
112
113 if (size == -1)
114 continue;
115
116 if (size != datagram.size())
117 datagram.resize(size);
118
119 // "A typical TUIO bundle will contain an initial ALIVE message,
120 // followed by an arbitrary number of SET messages that can fit into the
121 // actual bundle capacity and a concluding FSEQ message. A minimal TUIO
122 // bundle needs to contain at least the compulsory ALIVE and FSEQ
123 // messages. The FSEQ frame ID is incremented for each delivered bundle,
124 // while redundant bundles can be marked using the frame sequence ID
125 // -1."
126 QList<QOscMessage> messages;
127
128 QOscBundle bundle(datagram);
129 if (bundle.isValid()) {
130 messages = bundle.messages();
131 } else {
132 QOscMessage msg(datagram);
133 if (!msg.isValid()) {
134 qCWarning(lcTuioSet) << "Got invalid datagram.";
135 continue;
136 }
137 messages.push_back(msg);
138 }
139
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");
145 continue;
146 }
147
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);
157 } else {
158 qCWarning(lcTuioHandler) << "Ignoring unknown TUIO message type: " << messageType;
159 continue;
160 }
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");
165 continue;
166 }
167
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);
177 } else {
178 qCWarning(lcTuioHandler) << "Ignoring unknown TUIO message type: " << messageType;
179 continue;
180 }
181 } else {
182 qCWarning(lcTuioHandler) << "Ignoring unknown address pattern " << message.addressPattern();
183 continue;
184 }
185 }
186 }
187}
188
189void QTuioHandler::process2DCurSource(const QOscMessage &message)
190{
191 QList<QVariant> arguments = message.arguments();
192 if (arguments.size() != 2) {
193 qCWarning(lcTuioSource) << "Ignoring malformed TUIO source message: " << arguments.size();
194 return;
195 }
196
197 if (QMetaType::Type(arguments.at(1).userType()) != QMetaType::QByteArray) {
198 qCWarning(lcTuioSource, "Ignoring malformed TUIO source message (bad argument type)");
199 return;
200 }
201
202 qCDebug(lcTuioSource) << "Got TUIO source message from: " << arguments.at(1).toByteArray();
203}
204
205void QTuioHandler::process2DCurAlive(const QOscMessage &message)
206{
207 QList<QVariant> arguments = message.arguments();
208
209 // delta the notified cursors that are active, against the ones we already
210 // know of.
211 //
212 // TBD: right now we're assuming one 2Dcur alive message corresponds to a
213 // new data source from the input. is this correct, or do we need to store
214 // changes and only process the deltas on fseq?
215 QMap<int, QTuioCursor> oldActiveCursors = m_activeCursors;
216 QMap<int, QTuioCursor> newActiveCursors;
217
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 << ')';
221 return;
222 }
223
224 int cursorId = arguments.at(i).toInt();
225 if (!oldActiveCursors.contains(cursorId)) {
226 // newly active
227 QTuioCursor cursor(cursorId);
229 newActiveCursors.insert(cursorId, cursor);
230 } else {
231 // we already know about it, remove it so it isn't marked as released
232 QTuioCursor cursor = oldActiveCursors.value(cursorId);
233 cursor.setState(QEventPoint::State::Stationary); // position change in SET will update if needed
234 newActiveCursors.insert(cursorId, cursor);
235 oldActiveCursors.remove(cursorId);
236 }
237 }
238
239 // anything left is dead now
241
242 // deadCursors should be cleared from the last FSEQ now
243 m_deadCursors.reserve(oldActiveCursors.size());
244
245 // TODO: there could be an issue of resource exhaustion here if FSEQ isn't
246 // sent in a timely fashion. we should probably track message counts and
247 // force-flush if we get too many built up.
248 while (it != oldActiveCursors.constEnd()) {
249 m_deadCursors.append(it.value());
250 ++it;
251 }
252
253 m_activeCursors = newActiveCursors;
254}
255
256void QTuioHandler::process2DCurSet(const QOscMessage &message)
257{
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();
261 return;
262 }
263
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
270 ) {
271 qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with bad types: " << arguments;
272 return;
273 }
274
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();
281
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;
285 return;
286 }
287
288 qCDebug(lcTuioSet) << "Processing SET for " << cursorId << " x: " << x << y << vx << vy << acceleration;
289 QTuioCursor &cur = *it;
290 cur.setX(x);
291 cur.setY(y);
292 cur.setVX(vx);
293 cur.setVY(vy);
294 cur.setAcceleration(acceleration);
295}
296
297QWindowSystemInterface::TouchPoint QTuioHandler::cursorToTouchPoint(const QTuioCursor &tc, QWindow *win)
298{
300 tp.id = tc.id();
301 tp.pressure = 1.0f;
302
303 tp.normalPosition = QPointF(tc.x(), tc.y());
304
305 if (!m_transform.isIdentity())
306 tp.normalPosition = m_transform.map(tp.normalPosition);
307
308 tp.state = tc.state();
309
310 // we map the touch to the size of the window. we do this, because frankly,
311 // trying to figure out which part of the screen to hit in order to press an
312 // element on the UI is pretty tricky when one is not using an overlay-style
313 // TUIO device.
314 //
315 // in the future, it might make sense to make this choice optional,
316 // dependent on the spec.
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());
321 return tp;
322}
323
324
325void QTuioHandler::process2DCurFseq(const QOscMessage &message)
326{
327 Q_UNUSED(message); // TODO: do we need to do anything with the frame id?
328
332
333 if (!win)
334 return;
335
336 QList<QWindowSystemInterface::TouchPoint> tpl;
337 tpl.reserve(m_activeCursors.size() + m_deadCursors.size());
338
339 for (const QTuioCursor &tc : std::as_const(m_activeCursors)) {
340 QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win);
341 tpl.append(tp);
342 }
343
344 for (const QTuioCursor &tc : std::as_const(m_deadCursors)) {
345 QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win);
347 tpl.append(tp);
348 }
350
351 m_deadCursors.clear();
352}
353
354void QTuioHandler::process2DObjSource(const QOscMessage &message)
355{
356 QList<QVariant> arguments = message.arguments();
357 if (arguments.size() != 2) {
358 qCWarning(lcTuioSource ) << "Ignoring malformed TUIO source message: " << arguments.size();
359 return;
360 }
361
362 if (QMetaType::Type(arguments.at(1).userType()) != QMetaType::QByteArray) {
363 qCWarning(lcTuioSource, "Ignoring malformed TUIO source message (bad argument type)");
364 return;
365 }
366
367 qCDebug(lcTuioSource) << "Got TUIO source message from: " << arguments.at(1).toByteArray();
368}
369
370void QTuioHandler::process2DObjAlive(const QOscMessage &message)
371{
372 QList<QVariant> arguments = message.arguments();
373
374 // delta the notified tokens that are active, against the ones we already
375 // know of.
376 //
377 // TBD: right now we're assuming one 2DObj alive message corresponds to a
378 // new data source from the input. is this correct, or do we need to store
379 // changes and only process the deltas on fseq?
380 QMap<int, QTuioToken> oldActiveTokens = m_activeTokens;
381 QMap<int, QTuioToken> newActiveTokens;
382
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 << ')';
386 return;
387 }
388
389 int sessionId = arguments.at(i).toInt();
390 if (!oldActiveTokens.contains(sessionId)) {
391 // newly active
392 QTuioToken token(sessionId);
394 newActiveTokens.insert(sessionId, token);
395 } else {
396 // we already know about it, remove it so it isn't marked as released
397 QTuioToken token = oldActiveTokens.value(sessionId);
398 token.setState(QEventPoint::State::Stationary); // position change in SET will update if needed
399 newActiveTokens.insert(sessionId, token);
400 oldActiveTokens.remove(sessionId);
401 }
402 }
403
404 // anything left is dead now
406
407 // deadTokens should be cleared from the last FSEQ now
408 m_deadTokens.reserve(oldActiveTokens.size());
409
410 // TODO: there could be an issue of resource exhaustion here if FSEQ isn't
411 // sent in a timely fashion. we should probably track message counts and
412 // force-flush if we get too many built up.
413 while (it != oldActiveTokens.constEnd()) {
414 m_deadTokens.append(it.value());
415 ++it;
416 }
417
418 m_activeTokens = newActiveTokens;
419}
420
421void QTuioHandler::process2DObjSet(const QOscMessage &message)
422{
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();
426 return;
427 }
428
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;
440 return;
441 }
442
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();
453
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;
457 return;
458 }
459
460 qCDebug(lcTuioSet) << "Processing SET for token " << classId << id << " @ " << x << y << " angle: " << angle <<
461 "vel" << vx << vy << angularVelocity << "acc" << acceleration << angularAcceleration;
462 QTuioToken &tok = *it;
463 tok.setClassId(classId);
464 tok.setX(x);
465 tok.setY(y);
466 tok.setVX(vx);
467 tok.setVY(vy);
468 tok.setAcceleration(acceleration);
469 tok.setAngle(angle);
470 tok.setAngularVelocity(angularAcceleration);
471 tok.setAngularAcceleration(angularAcceleration);
472}
473
474QWindowSystemInterface::TouchPoint QTuioHandler::tokenToTouchPoint(const QTuioToken &tc, QWindow *win)
475{
477 tp.id = tc.id();
478 tp.uniqueId = tc.classId(); // TODO TUIO 2.0: populate a QVariant, and register the mapping from int to arbitrary UID data
479 tp.pressure = 1.0f;
480
481 tp.normalPosition = QPointF(tc.x(), tc.y());
482
483 if (!m_transform.isIdentity())
484 tp.normalPosition = m_transform.map(tp.normalPosition);
485
486 tp.state = tc.state();
487
488 // We map the token position to the size of the window.
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());
493 tp.rotation = qRadiansToDegrees(tc.angle());
494 return tp;
495}
496
497
498void QTuioHandler::process2DObjFseq(const QOscMessage &message)
499{
500 Q_UNUSED(message); // TODO: do we need to do anything with the frame id?
501
505
506 if (!win)
507 return;
508
509 QList<QWindowSystemInterface::TouchPoint> tpl;
510 tpl.reserve(m_activeTokens.size() + m_deadTokens.size());
511
512 for (const QTuioToken & t : std::as_const(m_activeTokens)) {
513 QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win);
514 tpl.append(tp);
515 }
516
517 for (const QTuioToken & t : std::as_const(m_deadTokens)) {
518 QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win);
520 tp.velocity = QVector2D();
521 tpl.append(tp);
522 }
524
525 m_deadTokens.clear();
526}
527
529
530#include "moc_qtuiohandler_p.cpp"
531
\inmodule QtCore
Definition qbytearray.h:57
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
static QWindowList topLevelWindows()
Returns a list of the top-level windows in the application.
static QWindow * focusWindow()
Returns the QWindow that receives events tied to focus, such as key events.
The QHostAddress class provides an IP address.
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
qsizetype size() const noexcept
Definition qlist.h:397
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void reserve(qsizetype size)
Definition qlist.h:753
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
iterator find(const Key &key)
Definition qmap.h:641
iterator end()
Definition qmap.h:602
size_type size() const
Definition qmap.h:267
Type
\macro Q_DECLARE_OPAQUE_POINTER(PointerType)
Definition qmetatype.h:345
QObject * sender() const
Returns a pointer to the object that sent the signal, if called in a slot activated by a signal; othe...
Definition qobject.cpp:2658
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
constexpr QPoint toPoint() const
Rounds the coordinates of this point to the nearest integer, and returns a QPoint object with the rou...
Definition qpoint.h:404
The QPointingDevice class describes a device from which mouse, touch or tablet events originate.
constexpr void moveCenter(const QPointF &p) noexcept
Moves the rectangle, leaving the center point at the given position.
Definition qrect.h:726
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QPoint map(const QPoint &p) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QTransform fromTranslate(qreal dx, qreal dy)
Creates a matrix which corresponds to a translation of dx along the x axis and dy along the y axis.
bool isIdentity() const
Returns true if the matrix is the identity matrix, otherwise returns false.
Definition qtransform.h:169
virtual ~QTuioHandler()
bool hasPendingDatagrams() const
Returns true if at least one datagram is waiting to be read; otherwise returns false.
qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host=nullptr, quint16 *port=nullptr)
Receives a datagram no larger than maxSize bytes and stores it in data.
qint64 pendingDatagramSize() const
Returns the size of the first pending UDP datagram.
int toInt(bool *ok=nullptr) const
Returns the variant as an int if the variant has userType() \l QMetaType::Int, \l QMetaType::Bool,...
int userType() const
Definition qvariant.h:339
float toFloat(bool *ok=nullptr) const
Returns the variant as a float if the variant has userType() \l QMetaType::Double,...
QByteArray toByteArray() const
Returns the variant as a QByteArray if the variant has userType() \l QMetaType::QByteArray or \l QMet...
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
QSize size
the size of the widget excluding any window frame
Definition qwidget.h:113
QPointF mapToGlobal(const QPointF &) const
Translates the widget coordinate pos to global screen coordinates.
static bool handleTouchEvent(QWindow *window, const QPointingDevice *device, const QList< struct TouchPoint > &points, Qt::KeyboardModifiers mods=Qt::NoModifier)
static void registerInputDevice(const QInputDevice *device)
\inmodule QtGui
Definition qwindow.h:63
QCursor cursor
QSet< QString >::iterator it
QList< QVariant > arguments
Token token
Definition keywords.cpp:444
Combined button and popup list for selecting options.
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr float qRadiansToDegrees(float radians)
Definition qmath.h:281
GLint GLint GLint GLint GLint x
[0]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLfloat angle
GLuint GLsizei const GLchar * message
GLint y
GLsizei GLsizei GLchar * source
const GLfloat * tc
GLdouble GLdouble t
Definition qopenglext.h:243
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
#define Q_UNUSED(x)
static QT_BEGIN_NAMESPACE bool forceDelivery
unsigned short quint16
Definition qtypes.h:48
long long qint64
Definition qtypes.h:60
QWidget * win
Definition settings.cpp:6
QFuture< QSet< QChar > > set
[10]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
QJSValueList args