1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt WebGL module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include "qwebglwebsocketserver.h"
31
32#include "qwebglcontext.h"
33#include "qwebglfunctioncall.h"
34#include "qwebglintegration.h"
35#include "qwebglintegration_p.h"
36#include "qwebglwindow.h"
37#include "qwebglwindow_p.h"
38
39#include <QtCore/private/qobject_p.h>
40#include <QtCore/qcoreevent.h>
41#include <QtCore/qdebug.h>
42#include <QtCore/qjsonarray.h>
43#include <QtCore/qjsondocument.h>
44#include <QtCore/qjsonobject.h>
45#include <QtCore/qwaitcondition.h>
46#include <QtGui/qevent.h>
47#include <QtGui/qguiapplication.h>
48#include <QtGui/qpa/qwindowsysteminterface.h>
49#include <QtWebSockets/qwebsocket.h>
50#include <QtWebSockets/qwebsocketserver.h>
51
52#include <cstring>
53
54QT_BEGIN_NAMESPACE
55
56static Q_LOGGING_CATEGORY(lc, "qt.qpa.webgl.websocketserver")
57
58inline QWebGLIntegration *webGLIntegration()
59{
60#ifdef QT_DEBUG
61 auto nativeInterface = dynamic_cast<QWebGLIntegration *>(qGuiApp->platformNativeInterface());
62 Q_ASSERT(nativeInterface);
63#else
64 auto nativeInterface = static_cast<QWebGLIntegration *>(qGuiApp->platformNativeInterface());
65#endif // QT_DEBUG
66 return nativeInterface;
67}
68
69class QWebGLWebSocketServerPrivate
70{
71public:
72 QWebSocketServer *server = nullptr;
73 quint16 initialPort = 0;
74};
75
76QWebGLWebSocketServer::QWebGLWebSocketServer(quint16 port, QObject *parent) :
77 QObject(parent),
78 d_ptr(new QWebGLWebSocketServerPrivate)
79{
80 d_ptr->initialPort = port;
81}
82
83QWebGLWebSocketServer::~QWebGLWebSocketServer()
84{}
85
86quint16 QWebGLWebSocketServer::port() const
87{
88 Q_D(const QWebGLWebSocketServer);
89 return d->server->serverPort();
90}
91
92QMutex *QWebGLWebSocketServer::mutex()
93{
94 return &QWebGLIntegrationPrivate::instance()->waitMutex;
95}
96
97QWaitCondition *QWebGLWebSocketServer::waitCondition()
98{
99 return &QWebGLIntegrationPrivate::instance()->waitCondition;
100}
101
102QVariant QWebGLWebSocketServer::queryValue(int id)
103{
104 QMutexLocker locker(&QWebGLIntegrationPrivate::instance()->waitMutex);
105 if (QWebGLIntegrationPrivate::instance()->receivedResponses.contains(akey: id))
106 return QWebGLIntegrationPrivate::instance()->receivedResponses.take(akey: id);
107 return QVariant();
108}
109
110void QWebGLWebSocketServer::create()
111{
112 Q_D(QWebGLWebSocketServer);
113 const QString serverName = QLatin1String("qtwebgl");
114 const QUrl url(QString::fromUtf8(str: qgetenv(varName: "QT_WEBGL_WEBSOCKETSERVER")));
115 QHostAddress hostAddress(url.host());
116 if (!url.isValid() || url.isEmpty() || !(url.scheme() == "ws" || url.scheme() == "wss")) {
117 d->server = new QWebSocketServer(serverName, QWebSocketServer::NonSecureMode);
118 hostAddress = QHostAddress::Any;
119 } else {
120 d->server = new QWebSocketServer(serverName,
121#if QT_CONFIG(ssl)
122 url.scheme() == "wss" ? QWebSocketServer::SecureMode :
123#endif
124 QWebSocketServer::NonSecureMode);
125 }
126 if (d->server->listen(address: hostAddress, port: url.port(defaultPort: d->initialPort))) {
127 connect(sender: d->server, signal: &QWebSocketServer::newConnection,
128 receiver: this, slot: &QWebGLWebSocketServer::onNewConnection);
129 } else {
130 qCCritical(lc, "The WebSocket Server cannot start: %s",
131 qPrintable(d->server->errorString()));
132 }
133
134 QMutexLocker lock(&QWebGLIntegrationPrivate::instance()->waitMutex);
135 QWebGLIntegrationPrivate::instance()->waitCondition.wakeAll();
136}
137
138void QWebGLWebSocketServer::sendMessage(QWebSocket *socket,
139 MessageType type,
140 const QVariantMap &values)
141{
142 if (!socket)
143 return;
144 QString typeString;
145 switch (type) {
146 case MessageType::Connect:
147 typeString = QStringLiteral("connect");
148 qCDebug(lc) << "Sending connect to " << socket << values;
149 break;
150 case MessageType::GlCommand: {
151 const auto functionName = values["function"].toString().toUtf8();
152 const auto parameters = values["parameters"].toList();
153 const quint32 parameterCount = parameters.size();
154 qCDebug(lc, "Sending gl_command %s to %p with %d parameters",
155 qPrintable(functionName), socket, parameterCount);
156 QByteArray data;
157 {
158 QDataStream stream(&data, QIODevice::WriteOnly);
159 stream << QWebGLContext::functionIndex(functionName);
160 if (values.contains(akey: "id")) {
161 auto ok = false;
162 stream << quint32(values["id"].toUInt(ok: &ok));
163 Q_ASSERT(ok);
164 }
165 const std::function<void(const QVariantList &)> serialize = [&stream, &serialize](
166 const QVariantList &parameters) {
167 for (const auto &value : parameters) {
168 if (value.isNull()) {
169 stream << (quint8)'n';
170 } else switch (value.type()) {
171 case QVariant::Int:
172 stream << (quint8)'i' << value.toInt();
173 break;
174 case QVariant::UInt:
175 stream << (quint8)'u' << value.toUInt();
176 break;
177 case QVariant::Bool:
178 stream << (quint8)'b' << (quint8)value.toBool();
179 break;
180 case QVariant::Double:
181 stream << (quint8)'d' << value.toDouble();
182 break;
183 case QVariant::String:
184 stream << (quint8)'s' << value.toString().toUtf8();
185 break;
186 case QVariant::ByteArray: {
187 const auto byteArray = value.toByteArray();
188 if (byteArray.isNull())
189 stream << (quint8)'n';
190 else
191 stream << (quint8)'x' << byteArray;
192 break;
193 }
194 case QVariant::List: {
195 const auto list = value.toList();
196 stream << quint8('a') << quint8(list.size());
197 serialize(list);
198 break;
199 }
200 default:
201 qCCritical(lc, "Unsupported type: %d", value.type());
202 break;
203 }
204 }
205 };
206 serialize(parameters);
207 stream << (quint32)0xbaadf00d;
208 }
209 socket->sendBinaryMessage(data);
210 return;
211 }
212 case MessageType::CreateCanvas:
213 qCDebug(lc) << "Sending create_canvas to " << socket << values;
214 typeString = QStringLiteral("create_canvas");
215 break;
216 case MessageType::DestroyCanvas:
217 return; // TODO: In current implementation the canvas is not destroyed
218 qCDebug(lc) << "Sending destroy_canvas to " << socket << values;
219 typeString = QStringLiteral("destroy_canvas");
220 break;
221 case MessageType::OpenUrl:
222 qCDebug(lc) << "Sending open_url to " << socket << values;
223 typeString = QStringLiteral("open_url");
224 break;
225 case MessageType::ChangeTitle:
226 qCDebug(lc) << "Sending change_title to " << socket << values;
227 typeString = QStringLiteral("changle_title");
228 break;
229 }
230 QJsonDocument document;
231 auto commandObject = QJsonObject::fromVariantMap(map: values);
232 commandObject["type"] = typeString;
233 document.setObject(commandObject);
234 auto data = document.toJson(format: QJsonDocument::Compact);
235 socket->sendTextMessage(message: data);
236}
237
238bool QWebGLWebSocketServer::event(QEvent *event)
239{
240 int type = event->type();
241 if (type == QWebGLFunctionCall::type()) {
242 auto e = static_cast<QWebGLFunctionCall *>(event);
243 QVariantMap values {
244 { "function", e->functionName() },
245 { "parameters", e->parameters() }
246 };
247 if (e->id() != -1)
248 values.insert(akey: "id", avalue: e->id());
249 auto integrationPrivate = QWebGLIntegrationPrivate::instance();
250 auto clientData = integrationPrivate->findClientData(surface: e->surface());
251 if (clientData && clientData->socket) {
252 sendMessage(socket: clientData->socket, type: MessageType::GlCommand, values);
253 if (e->isBlocking())
254 integrationPrivate->pendingResponses.append(t: e->id());
255 return true;
256 }
257 return false;
258 }
259 return QObject::event(event);
260}
261
262void QWebGLWebSocketServer::onNewConnection()
263{
264 Q_D(QWebGLWebSocketServer);
265 QWebSocket *socket = d->server->nextPendingConnection();
266 if (socket) {
267 connect(sender: socket, signal: &QWebSocket::disconnected, receiver: this, slot: &QWebGLWebSocketServer::onDisconnect);
268 connect(sender: socket, signal: &QWebSocket::textMessageReceived, receiver: this,
269 slot: &QWebGLWebSocketServer::onTextMessageReceived);
270
271 const QVariantMap values{
272 {
273 QStringLiteral("debug"),
274#ifdef QT_DEBUG
275 true
276#else
277 false
278#endif
279 },
280 { QStringLiteral("loadingScreen"), qgetenv(varName: "QT_WEBGL_LOADINGSCREEN") },
281 { QStringLiteral("mouseTracking"), qgetenv(varName: "QT_WEBGL_MOUSETRACKING") },
282 { QStringLiteral("supportedFunctions"),
283 QVariant::fromValue(value: QWebGLContext::supportedFunctions()) },
284 { "sysinfo",
285 QVariantMap {
286 { QStringLiteral("buildAbi"), QSysInfo::buildAbi() },
287 { QStringLiteral("buildCpuArchitecture"), QSysInfo::buildCpuArchitecture() },
288 { QStringLiteral("currentCpuArchitecture"),
289 QSysInfo::currentCpuArchitecture() },
290 { QStringLiteral("kernelType"), QSysInfo::kernelType() },
291 { QStringLiteral("machineHostName"), QSysInfo::machineHostName() },
292 { QStringLiteral("prettyProductName"), QSysInfo::prettyProductName() },
293 { QStringLiteral("productType"), QSysInfo::productType() },
294 { QStringLiteral("productVersion"), QSysInfo::productVersion() },
295 }
296 }
297 };
298
299 sendMessage(socket, type: MessageType::Connect, values);
300 }
301}
302
303void QWebGLWebSocketServer::onDisconnect()
304{
305 QWebSocket *socket = qobject_cast<QWebSocket *>(object: sender());
306 Q_ASSERT(socket);
307 QWebGLIntegrationPrivate::instance()->clientDisconnected(socket);
308 socket->deleteLater();
309}
310
311void QWebGLWebSocketServer::onTextMessageReceived(const QString &message)
312{
313 const auto socket = qobject_cast<QWebSocket *>(object: sender());
314 QWebGLIntegrationPrivate::instance()->onTextMessageReceived(socket, message);
315}
316
317QT_END_NAMESPACE
318

source code of qtwebglplugin/src/plugins/platforms/webgl/qwebglwebsocketserver.cpp