21#include <QElapsedTimer>
27#include "TPClientQt.h"
30Q_LOGGING_CATEGORY(lcTPC,
"TPClientQt", QtDebugMsg);
32Q_LOGGING_CATEGORY(lcTPC,
"TPClientQt", QtWarningMsg);
36struct TPClientQt::Private
40 socket(new QTcpSocket(q)),
44 inline void onSockStateChanged(QAbstractSocket::SocketState s)
46 qCDebug(lcTPC) <<
"Socket state changed:" << s;
48 case QAbstractSocket::ConnectedState:
49 socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
50#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) || !defined(Q_OS_WIN)
52 socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
59 if (connTimeout > 0) {
60 QThread *waitThread = QThread::create([=] { waitForPaired(); });
61 QObject::connect(waitThread, &QThread::finished, q, [=]() { waitThread->wait(); waitThread->deleteLater(); }, Qt::QueuedConnection);
66 case QAbstractSocket::UnconnectedState:
68 qCInfo(lcTPC) <<
"Closed TP Connection.";
75 inline void onSocketError(QAbstractSocket::SocketError e)
77 if (e == QAbstractSocket::TemporaryError || e == QAbstractSocket::UnknownSocketError)
79 lastError = socket->errorString();
81 qCWarning(lcTPC) <<
"Permanent socket error:" << e << lastError;
91 while (!
tpInfo.
tpVersionCode && socket->state() == QAbstractSocket::ConnectedState && !sw.hasExpired(connTimeout))
92 QThread::yieldCurrentThread();
95 qCCritical(lcTPC) <<
"Could not pair with TP! Disconnecting.";
96 Q_EMIT q->error(QAbstractSocket::SocketTimeoutError);
97 QMetaObject::invokeMethod(q,
"disconnect", Qt::QueuedConnection);
101 void onTpMessage(
MessageType type,
const QJsonObject &msg)
105 tpInfo.
status = msg.value(QLatin1String(
"status")).toString();
111 qCInfo(lcTPC).nospace().noquote()
117 lastError =
"Touch Portal responded with unknown 'status' of: " +
tpInfo.
status;
118 qCCritical(lcTPC) << lastError;
119 Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError);
124 const QJsonObject settings = arrayToObj(msg.value(QLatin1String(
"settings")));
125 Q_EMIT q->connected(
tpInfo, settings);
138 Q_EMIT q->message(type, msg);
141 QJsonObject arrayToObj(
const QJsonValue &arry)
const
144 const QJsonArray a = arry.toArray();
145 for (
const QJsonValue &v : a) {
147 const QJsonObject &vObj = v.toObject();
148 QJsonObject::const_iterator next = vObj.begin(), last = vObj.end();
149 for (; next != last; ++next)
150 ret.insert(next.key(), next.value());
157 QTcpSocket *
const socket;
160 QString tpHost = QStringLiteral(
"127.0.0.1");
161 uint16_t tpPort = 12136;
162 int connTimeout = 10000;
167#define d_const const_cast<const Private *>(d)
177 qRegisterMetaType<TPClientQt::MessageType>();
178 qRegisterMetaType<TPClientQt::TPInfo>();
179 qRegisterMetaType<QAbstractSocket::SocketState>();
180 qRegisterMetaType<QAbstractSocket::SocketError>();
182 QObject::connect(d->socket, &QTcpSocket::readyRead,
this, &TPClientQt::onReadyRead);
184 QObject::connect(d->socket, &QTcpSocket::stateChanged,
this, [
this](QAbstractSocket::SocketState s) { d->onSockStateChanged(s); });
185#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
186 QObject::connect(d->socket, qOverload<QAbstractSocket::SocketError>(&QAbstractSocket::error),
this, [
this](QAbstractSocket::SocketError e) { d->onSocketError(e); });
188 QObject::connect(d->socket, &QAbstractSocket::errorOccurred,
this, [
this](QAbstractSocket::SocketError e) { d->onSocketError(e); });
192TPClientQt::~TPClientQt()
198bool TPClientQt::isConnected()
const {
return d_const->socket->state() == QAbstractSocket::ConnectedState && d_const->tpInfo.paired; }
213 qCCritical(lcTPC()) <<
"Plugin ID is required!";
216 if (d_const->socket->state() != QAbstractSocket::UnconnectedState) {
217 qCCritical(lcTPC()) <<
"Cannot change Plugin ID while connected.";
226 if (!nameOrAddress.isEmpty())
227 d->tpHost = nameOrAddress;
234 d->connTimeout = timeoutMs;
239 if (d_const->socket->state() != QAbstractSocket::UnconnectedState) {
240 qCWarning(lcTPC()) <<
"Cannot connect while socket already connected or pending operation.";
244 if (d->pluginId.isEmpty()) {
245 d->lastError =
"Plugin ID is required!";
246 qCCritical(lcTPC()) << d->lastError;
247 Q_EMIT
error(QAbstractSocket::OperationError);
251#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && defined(Q_OS_WIN)
253 d->socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
257 d->socket->connectToHost(d->tpHost, d->tpPort);
262 d_const->socket->flush();
263 d_const->socket->disconnectFromHost();
268 if (!d_const->socket || !d_const->socket->isWritable())
270 const int len = data.length();
271 qint64 bw = 0, sbw = 0;
273 sbw = d_const->socket->write(data);
276 while (bw != len && sbw > -1);
278 qCCritical(lcTPC()) <<
"Socket write error: " << d->socket->errorString();
282 d_const->socket->write(
"\n", 1);
287void TPClientQt::onReadyRead()
290 while (d->socket->canReadLine()) {
291 const QByteArray &bytes = d->socket->readLine();
294 const QJsonDocument &js = QJsonDocument::fromJson(bytes, &jpe);
295 if (!js.isObject()) {
296 if (jpe.error == QJsonParseError::NoError)
297 qCWarning(lcTPC) <<
"Got empty or invalid JSON data, with no parsing error.";
299 qCWarning(lcTPC) <<
"Got invalid JSON data:" << jpe.errorString() <<
"; @" << jpe.offset;
300 qCDebug(lcTPC) << bytes;
303 const QJsonObject &msg = js.object();
305 const QJsonValue &jMsgType = msg.value(QLatin1String(
"type"));
306 if (!jMsgType.isString()) {
307 qCWarning(lcTPC) <<
"TP message data missing the 'type' property.";
308 qCDebug(lcTPC) << msg;
313 MessageType iMsgType = (
MessageType)QMetaEnum::fromType<TPClientQt::MessageType>().keyToValue(qPrintable(jMsgType.toString()), &ok);
316 qCWarning(lcTPC) <<
"Unknown TP message 'type' property:" << jMsgType.toString();
318 d->onTpMessage(iMsgType, msg);
The TPClientQt class is a simple TCP/IP network client for usage in Touch Portal plugins which wish t...
QString tpVersionString
Touch Portal version number as text.
uint16_t hostPort() const
Returns the currently set Touch Portal host port. This is either the default or one explicitly set wi...
uint32_t tpVersionCode
Numeric Touch Portal version.
void connect()
Initiate a connection to Touch Portal. The plugin ID (set in constructor or with setPluginId() must b...
const TPClientQt::TPInfo & tpInfo() const
Returns information about the currently connected Touch Portal instance. This data is saved from the ...
void disconnect() const
Initiates disconnection from Touch Portal. This flushes and gracefully closes any open network socket...
QString errorString() const
Returns the current TCP/IP network error, if any, as a human-readable string.
TPClientQt(const char *pluginId, QObject *parent=nullptr)
The constructor creates the instance but does not attempt any connections. The pluginId will be used ...
uint16_t sdkVersion
Supported SDK version.
bool paired
true if actively connected to TP; expects that 'status' == 'paired' in initial 'info' message.
QAbstractSocket::SocketError socketError() const
Returns the current TCP/IP network socket error, if any.
bool setPluginId(const char *pluginId)
Alternate way to set or change the plugin ID. This cannot be changed when connected to TP....
void disconnected()
Emitted upon disconnection from Touch Portal, either from an explicit call to close() or if the conne...
void error(QAbstractSocket::SocketError error)
Emitted in case of error upon initial connection or unexpected termination. This would typically be w...
bool isConnected() const
Returns true if connected to Touch Portal, false otherwise.
void setHostProperties(const QString &nameOrAddress=QStringLiteral("127.0.0.1"), uint16_t port=12136)
Set the Touch Portal host name/address and port number for connection. nameOrAddress can be a IPv4 do...
uint32_t pluginVersion
Numeric plugin version read from entry.tp file.
QAbstractSocket::SocketState socketState() const
Returns the current state of the TCP/IP network socket used to communicate with Touch Portal.
QString pluginId() const
Returns the plugin ID set in constructor or with setPluginId().
MessageType
This enumeration is used in the message() signal to indicate message type. The names match the Touch ...
@ settings
Emitted for 'info' and 'settings' message type; value/settings array is flattened to QJsonObject of {...
@ Unknown
An unknown event, perhaps from a newer version of TP which isn't supported yet.
@ info
The initial connection event, sent after pairing with TP.
QString status
The 'status' property from initial 'info' message (typically "paired"); This does not get changed aft...
void write(const QByteArray &data) const
Low-level API: Write UTF-8 bytes directly to Touch Portal. data should contain one TP message in the ...
int connectionTimeout() const
Returns the currently set connection timeout value, in milliseconds. This is either the default or on...
QString hostName() const
Returns the currently set Touch Portal host name/address string. This is either the default or one ex...
void setConnectionTimeout(int timeoutMs=10000)
Sets the timeout value for the initial pairing 'info' message to be received from Touch Portal,...
Structure to hold information about current Touch Portal session. Populated from the initial 'info' m...