Showing the changes to interface/src/scripting/OSCScriptingInterface.cpp (1 of 2 files). Show all
Unified · Split
| @@ -12,18 +12,22 @@ · 11 unmodified lines | ||
| 12 | 12 | #include <QLoggingCategory> |
| 13 | 13 | #include <QRegularExpression> |
| 14 | 14 | #include <QtEndian> |
| 15 | +#include <QNetworkDatagram> | |
| 15 | 16 | |
| 16 | 17 | #include "ScriptEngine.h" |
| 17 | 18 | #include "OSCScriptingInterface.h" |
| 18 | 19 | |
| 19 | 20 | |
| 20 | −Q_LOGGING_CATEGORY(osc, "osc") | |
| 21 | +Q_LOGGING_CATEGORY(osc_cat, "osc") | |
| 21 | 22 | |
| 22 | 23 | enum OSCTag: char { |
| 23 | 24 | Int = 'i', |
| 24 | 25 | Float = 'f', |
| 25 | 26 | String = 's', |
| 26 | 27 | Blob = 'b', |
| 28 | + False = 'F', | |
| 29 | + True = 'T', | |
| 30 | + Null = 'N', | |
| 27 | 31 | }; |
| 28 | 32 | |
| 29 | 33 | static const QRegularExpression invalidCharacters = QRegularExpression("([ #*,?\\[\\]{}])"); |
| @@ -32,6 +36,9 @@ static const QMap<QChar, QVariant::Type> typeNameMap = { · 2 unmodified lines | ||
| 32 | 36 | { OSCTag::Float, QVariant::Double }, |
| 33 | 37 | { OSCTag::String, QVariant::String }, |
| 34 | 38 | { OSCTag::Blob, QVariant::ByteArray }, |
| 39 | + { OSCTag::False, QVariant::Bool }, | |
| 40 | + { OSCTag::True, QVariant::Bool }, | |
| 41 | + { OSCTag::Null, QVariant::Invalid }, | |
| 35 | 42 | }; |
| 36 | 43 | |
| 37 | 44 | |
| @@ -45,34 +52,146 @@ OSCScriptingInterface::OSCScriptingInterface(QObject* parent) : · 7 unmodified lines | ||
| 45 | 52 | { |
| 46 | 53 | _socket->bind(QHostAddress(_receiveHost.get()), _receivePort.get()); |
| 47 | 54 | |
| 48 | − connect(_socket.get(), &QUdpSocket::readyRead, this, &OSCScriptingInterface::receivePacket); | |
| 55 | + connect(_socket.get(), &QUdpSocket::readyRead, this, &OSCScriptingInterface::readPacket); | |
| 49 | 56 | |
| 50 | − qCInfo(osc) << "Listening on" << _socket->localAddress(); | |
| 57 | + qCInfo(osc_cat) << "Listening on" << _socket->localAddress(); | |
| 51 | 58 | } |
| 52 | 59 | |
| 53 | 60 | OSCScriptingInterface::~OSCScriptingInterface() { |
| 54 | 61 | _socket->close(); |
| 55 | − qCInfo(osc) << "Closed listen socket"; | |
| 62 | + qCInfo(osc_cat) << "Closed listen socket"; | |
| 56 | 63 | } |
| 57 | 64 | |
| 58 | −void OSCScriptingInterface::receivePacket() { | |
| 59 | − // TODO | |
| 60 | − emit messageReceived("/test", "", QVariantList()); | |
| 65 | +void OSCScriptingInterface::readPacket() { | |
| 66 | + auto datagram = _socket->receiveDatagram(); | |
| 67 | + | |
| 68 | + if (!datagram.isValid()) { return; } | |
| 69 | + | |
| 70 | + auto next4 = [](int i) { return i + (4 - i % 4); }; | |
| 71 | + | |
| 72 | + auto data = datagram.data(); | |
| 73 | + int cursor = 0; | |
| 74 | + | |
| 75 | + QString addr; | |
| 76 | + QByteArray argTypes; | |
| 77 | + QVariantList args; | |
| 78 | + | |
| 79 | + // address | |
| 80 | + for (int i = 0; (i + cursor) < data.length(); i++) { | |
| 81 | + if (data[i + cursor] == '\x00') { | |
| 82 | + addr = QString::fromUtf8(data.constData(), i); | |
| 83 | + cursor = next4(cursor + i); | |
| 84 | + break; | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | + if (addr.isEmpty()) { return; } | |
| 89 | + | |
| 90 | + // argument types | |
| 91 | + for (int i = 0; (i + cursor) < data.length(); i++) { | |
| 92 | + if (data[i + cursor] == '\x00') { | |
| 93 | + argTypes = QByteArray(data.constData() + cursor, i); | |
| 94 | + cursor = next4(cursor + i); | |
| 95 | + break; | |
| 96 | + } | |
| 97 | + } | |
| 98 | + | |
| 99 | + // +1 to exclude the comma | |
| 100 | + for (int arg = 1; arg < argTypes.length(); arg++) { | |
| 101 | + switch (argTypes[arg]) { | |
| 102 | + case OSCTag::Int: { | |
| 103 | + const char* ptr = data.constData() + cursor; | |
| 104 | + qint32 tmp = qFromBigEndian<qint32>(ptr); | |
| 105 | + args.append(QVariantMap { | |
| 106 | + {"type", QString(OSCTag::Int)}, | |
| 107 | + {"value", tmp}, | |
| 108 | + }); | |
| 109 | + cursor += 4; | |
| 110 | + } break; | |
| 111 | + | |
| 112 | + case OSCTag::Float: { | |
| 113 | + const char* ptr = data.constData() + cursor; | |
| 114 | + qint32 tmp = qFromBigEndian<qint32>(ptr); | |
| 115 | + args.append(QVariantMap { | |
| 116 | + {"type", QString(OSCTag::Float)}, | |
| 117 | + {"value", *reinterpret_cast<float*>(&tmp)}, | |
| 118 | + }); | |
| 119 | + cursor += 4; | |
| 120 | + } break; | |
| 121 | + | |
| 122 | + case OSCTag::String: { | |
| 123 | + QString tmp; | |
| 124 | + | |
| 125 | + for (int i = 0; i + cursor < data.length(); i++) { | |
| 126 | + if (data[i + cursor] == '\x00') { | |
| 127 | + tmp = QString::fromUtf8(data.constData() + cursor, i); | |
| 128 | + cursor = next4(cursor + i); | |
| 129 | + break; | |
| 130 | + } | |
| 131 | + } | |
| 132 | + | |
| 133 | + args.append(QVariantMap { | |
| 134 | + {"type", QString(OSCTag::String)}, | |
| 135 | + {"value", tmp}, | |
| 136 | + }); | |
| 137 | + } break; | |
| 138 | + | |
| 139 | + case OSCTag::Blob: { | |
| 140 | + const char* ptr = data.constData() + cursor; | |
| 141 | + auto len = qFromBigEndian<qint32>(ptr); | |
| 142 | + cursor += 4; | |
| 143 | + | |
| 144 | + args.append(QVariantMap { | |
| 145 | + {"type", QString(OSCTag::Blob)}, | |
| 146 | + {"value", QByteArray(data.constData() + cursor, len)}, | |
| 147 | + }); | |
| 148 | + | |
| 149 | + cursor = next4(cursor + len); | |
| 150 | + } break; | |
| 151 | + | |
| 152 | + case OSCTag::False: | |
| 153 | + args.append(QVariantMap { | |
| 154 | + {"type", QString(OSCTag::False)}, | |
| 155 | + {"value", false}, | |
| 156 | + }); | |
| 157 | + break; | |
| 158 | + | |
| 159 | + case OSCTag::True: | |
| 160 | + args.append(QVariantMap { | |
| 161 | + {"type", QString(OSCTag::True)}, | |
| 162 | + {"value", true}, | |
| 163 | + }); | |
| 164 | + break; | |
| 165 | + | |
| 166 | + case OSCTag::Null: | |
| 167 | + args.append(QVariantMap { | |
| 168 | + {"type", QString(OSCTag::Null)}, | |
| 169 | + {"value", QVariant()}, | |
| 170 | + }); | |
| 171 | + break; | |
| 172 | + | |
| 173 | + default: | |
| 174 | + qCCritical(osc_cat, "Unknown type '%c'", static_cast<char>(argTypes[arg])); | |
| 175 | + return; | |
| 176 | + } | |
| 177 | + } | |
| 178 | + | |
| 179 | + emit packetReceived(addr, args); | |
| 61 | 180 | } |
| 62 | 181 | |
| 63 | −void OSCScriptingInterface::sendMessage(const QString& address, const QString &argumentTypes, const QVariantList& arguments) { | |
| 182 | +void OSCScriptingInterface::sendPacket(const QString& address, const QVariantList& arguments) { | |
| 64 | 183 | Q_ASSERT(_socket); |
| 65 | 184 | |
| 66 | 185 | // FIXME: how do you get the script engine??? |
| 67 | 186 | struct HackRemoveThis { |
| 68 | 187 | void raiseException(QString msg) { |
| 69 | − qCCritical(osc) << msg; | |
| 188 | + qCCritical(osc_cat) << msg; | |
| 70 | 189 | } |
| 71 | 190 | }; |
| 72 | 191 | |
| 73 | 192 | auto scriptEngine = std::make_unique<HackRemoveThis>(); //_scriptManager->engine(); |
| 74 | 193 | |
| 75 | − if (address.length() < 2 || address[0] != '/') { | |
| 194 | + if (address.length() < 2 || !address.startsWith('/') || address.endsWith('/')) { | |
| 76 | 195 | scriptEngine->raiseException("address must be at least two characters and start with '/'"); |
| 77 | 196 | return; |
| 78 | 197 | } |
| @@ -82,11 +201,6 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 3 unmodified lines | ||
| 82 | 201 | return; |
| 83 | 202 | } |
| 84 | 203 | |
| 85 | − if (argumentTypes.length() != arguments.length()) { | |
| 86 | − scriptEngine->raiseException(QString("argumentTypes.length (%1) does not match arguments.length (%2)").arg(argumentTypes.length()).arg(arguments.length())); | |
| 87 | − return; | |
| 88 | − } | |
| 89 | − | |
| 90 | 204 | auto pad4 = [](QByteArray& array) { |
| 91 | 205 | array.append((4 - array.length() % 4) % 4, 0); |
| 92 | 206 | }; |
| @@ -105,19 +219,26 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 12 unmodified lines | ||
| 105 | 219 | for (int i = 0; i < arguments.length(); i++) { |
| 106 | 220 | auto arg = arguments[i]; |
| 107 | 221 | |
| 108 | − if (!typeNameMap.contains(argumentTypes[i])) { | |
| 109 | − scriptEngine->raiseException(QString("Unknown type tag '%1'").arg(argumentTypes[i])); | |
| 110 | − return; | |
| 222 | + QVariant::Type expectedType; | |
| 223 | + QVariant value; | |
| 224 | + | |
| 225 | + if (arg.type() == QVariant::Map) { | |
| 226 | + auto map = arg.toMap(); | |
| 227 | + auto typeName = map.value("type").toString(); | |
| 228 | + if (!typeNameMap.contains(typeName[0])) { | |
| 229 | + scriptEngine->raiseException(QString("Unknown type '%c' on argument %d").arg(typeName).arg(i)); | |
| 230 | + return; | |
| 231 | + } | |
| 232 | + expectedType = typeNameMap[typeName[0]]; | |
| 233 | + value = map.value("value"); | |
| 234 | + } else { | |
| 235 | + expectedType = arg.type(); | |
| 236 | + value = arg; | |
| 111 | 237 | } |
| 112 | 238 | |
| 113 | − auto expectedType = typeNameMap[argumentTypes[i]]; | |
| 114 | − if ( | |
| 115 | − expectedType != arg.type() && | |
| 116 | − // JS doesn't have ints, so allow doubles to be interpreted as ints | |
| 117 | − arg.type() != QVariant::Double | |
| 118 | − ) { | |
| 119 | − scriptEngine->raiseException(QString("Specified argument type '%1' does not match value of type '%2'").arg(QVariant::typeToName(expectedType)).arg(arg.typeName())); | |
| 120 | − return; | |
| 239 | + if (value.isNull()) { | |
| 240 | + bytes.append(OSCTag::Null); | |
| 241 | + continue; | |
| 121 | 242 | } |
| 122 | 243 | |
| 123 | 244 | switch (expectedType) { |
| @@ -127,7 +248,7 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 3 unmodified lines | ||
| 127 | 248 | auto* ptr = bodyBytes.data() + bodyBytes.length(); |
| 128 | 249 | bodyBytes.append(4, 0); |
| 129 | 250 | |
| 130 | − qToBigEndian(static_cast<qint32>(arg.toDouble()), ptr); | |
| 251 | + qToBigEndian(static_cast<qint32>(value.toDouble()), ptr); | |
| 131 | 252 | } break; |
| 132 | 253 | |
| 133 | 254 | case QVariant::Double: { |
| @@ -137,14 +258,14 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 3 unmodified lines | ||
| 137 | 258 | bodyBytes.append(4, 0); |
| 138 | 259 | |
| 139 | 260 | // FIXME: use std::bit_cast instead of this hack once c++20 is in |
| 140 | − auto tmp = static_cast<float>(arg.toDouble()); | |
| 261 | + auto tmp = static_cast<float>(value.toDouble()); | |
| 141 | 262 | qToBigEndian(*reinterpret_cast<quint32*>(&tmp), ptr); |
| 142 | 263 | } break; |
| 143 | 264 | |
| 144 | 265 | case QVariant::String: { |
| 145 | 266 | bytes.append(OSCTag::String); |
| 146 | 267 | |
| 147 | − auto stringArg = arg.toString(); | |
| 268 | + auto stringArg = value.toString(); | |
| 148 | 269 | |
| 149 | 270 | if (stringArg.contains(QChar(0))) { |
| 150 | 271 | scriptEngine->raiseException("String contains null (0x00) character"); |
| @@ -161,7 +282,7 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 10 unmodified lines | ||
| 161 | 282 | case QVariant::ByteArray: { |
| 162 | 283 | bytes.append(OSCTag::Blob); |
| 163 | 284 | |
| 164 | − auto bytesArg = arg.toByteArray(); | |
| 285 | + auto bytesArg = value.toByteArray(); | |
| 165 | 286 | |
| 166 | 287 | // write length prefix |
| 167 | 288 | auto* ptr = bodyBytes.data() + bodyBytes.length(); |
| @@ -175,10 +296,13 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 7 unmodified lines | ||
| 175 | 296 | pad4(bodyBytes); |
| 176 | 297 | } break; |
| 177 | 298 | |
| 178 | − // unreachable | |
| 179 | − default: | |
| 180 | − Q_ASSERT(false); | |
| 299 | + case QVariant::Bool: | |
| 300 | + bytes.append(value.toBool() ? OSCTag::True : OSCTag::False); | |
| 181 | 301 | break; |
| 302 | + | |
| 303 | + default: | |
| 304 | + scriptEngine->raiseException(QString("Unserializable type %1").arg(value.typeName())); | |
| 305 | + return; | |
| 182 | 306 | } |
| 183 | 307 | } |
| 184 | 308 | |
| @@ -191,5 +315,5 @@ void OSCScriptingInterface::sendMessage(const QString& address, const QString &a · 6 unmodified lines | ||
| 191 | 315 | |
| 192 | 316 | _socket->writeDatagram(bytes, QHostAddress(_sendHost.get()), _sendPort.get()); |
| 193 | 317 | |
| 194 | − qCDebug(osc) << QHostAddress(_sendHost.get()) << _sendPort.get() << ":" << bytes.toHex(' '); | |
| 318 | + qCDebug(osc_cat) << QHostAddress(_sendHost.get()) << _sendPort.get() << ":" << bytes.toHex(' '); | |
| 195 | 319 | } |