diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-02-04 15:54:11 +0100 |
---|---|---|
committer | Cristián Maureira-Fredes <cristian.maureira-fredes@qt.io> | 2020-02-11 19:46:27 +0100 |
commit | d4757ce27b3a675a54a6cc1d57c8b1987087299d (patch) | |
tree | b1d71d4399aea182e52436aff8c425fe8d2d5aa6 | |
parent | 8681f3a11c458ca9b092e96bbd24e51603342d6c (diff) |
Add the webchannel standalone example
Task-number: PYSIDE-1199
Change-Id: Icc138844b0cb5e7ccb502cbe840fc578fad8ca8f
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
-rw-r--r-- | examples/webchannel/standalone/core.py | 62 | ||||
-rw-r--r-- | examples/webchannel/standalone/dialog.py | 68 | ||||
-rw-r--r-- | examples/webchannel/standalone/dialog.ui | 48 | ||||
-rw-r--r-- | examples/webchannel/standalone/index.html | 79 | ||||
-rw-r--r-- | examples/webchannel/standalone/main.py | 99 | ||||
-rw-r--r-- | examples/webchannel/standalone/standalone.pyproject | 4 | ||||
-rw-r--r-- | examples/webchannel/standalone/ui_dialog.py | 55 | ||||
-rw-r--r-- | examples/webchannel/standalone/websocketclientwrapper.py | 72 | ||||
-rw-r--r-- | examples/webchannel/standalone/websockettransport.py | 88 |
9 files changed, 575 insertions, 0 deletions
diff --git a/examples/webchannel/standalone/core.py b/examples/webchannel/standalone/core.py new file mode 100644 index 000000000..9fb056496 --- /dev/null +++ b/examples/webchannel/standalone/core.py @@ -0,0 +1,62 @@ +############################################################################# +## +## Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + + +from PySide2.QtCore import QObject, Signal, Slot + + +class Core(QObject): + """An instance of this class gets published over the WebChannel and is then + accessible to HTML clients.""" + sendText = Signal(str) + + def __init__(self, dialog, parent=None): + super(Core, self).__init__(parent) + self._dialog = dialog + self._dialog.sendText.connect(self._emit_send_text) + + @Slot(str) + def _emit_send_text(self, text): + self.sendText.emit(text) + + @Slot(str) + def receiveText(self, text): + self._dialog.displayMessage("Received message: {}".format(text)) diff --git a/examples/webchannel/standalone/dialog.py b/examples/webchannel/standalone/dialog.py new file mode 100644 index 000000000..45951deb9 --- /dev/null +++ b/examples/webchannel/standalone/dialog.py @@ -0,0 +1,68 @@ +############################################################################# +## +## Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + + +from PySide2.QtCore import Signal, Slot +from PySide2.QtWidgets import QDialog +from ui_dialog import Ui_Dialog + + +class Dialog(QDialog): + sendText = Signal(str) + + def __init__(self, parent=None): + super(Dialog, self).__init__(parent) + self._ui = Ui_Dialog() + self._ui.setupUi(self) + self._ui.send.clicked.connect(self.clicked) + + @Slot(str) + def displayMessage(self, message): + self._ui.output.appendPlainText(message) + + @Slot() + def clicked(self): + text = self._ui.input.text() + if not text: + return + self.sendText.emit(text) + self.displayMessage("Sent message: {}".format(text)) + self._ui.input.clear() diff --git a/examples/webchannel/standalone/dialog.ui b/examples/webchannel/standalone/dialog.ui new file mode 100644 index 000000000..056a3f587 --- /dev/null +++ b/examples/webchannel/standalone/dialog.ui @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLineEdit" name="input"> + <property name="placeholderText"> + <string>Message Contents</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="send"> + <property name="text"> + <string>Send</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QPlainTextEdit" name="output"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="plainText"> + <string notr="true">Initializing WebChannel...</string> + </property> + <property name="backgroundVisible"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/webchannel/standalone/index.html b/examples/webchannel/standalone/index.html new file mode 100644 index 000000000..7c042cd0c --- /dev/null +++ b/examples/webchannel/standalone/index.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <script type="text/javascript" src="./qwebchannel.js"></script> + <script type="text/javascript"> + //BEGIN SETUP + function output(message) { + var output = document.getElementById("output"); + output.innerHTML = output.innerHTML + message + "\n"; + } + window.onload = function() { + if (location.search != "") + var baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(location.search)[1]); + else + var baseUrl = "ws://localhost:12345"; + + output("Connecting to WebSocket server at " + baseUrl + "."); + var socket = new WebSocket(baseUrl); + + socket.onclose = function() { + console.error("web channel closed"); + }; + socket.onerror = function(error) { + console.error("web channel error: " + error); + }; + socket.onopen = function() { + output("WebSocket connected, setting up QWebChannel."); + new QWebChannel(socket, function(channel) { + // make core object accessible globally + window.core = channel.objects.core; + + document.getElementById("send").onclick = function() { + var input = document.getElementById("input"); + var text = input.value; + if (!text) { + return; + } + + output("Sent message: " + text); + input.value = ""; + core.receiveText(text); + } + + core.sendText.connect(function(message) { + output("Received message: " + message); + }); + + core.receiveText("Client connected, ready to send/receive messages!"); + output("Connected to WebChannel, ready to send/receive messages!"); + }); + } + } + //END SETUP + </script> + <style type="text/css"> + html { + height: 100%; + width: 100%; + } + #input { + width: 400px; + margin: 0 10px 0 0; + } + #send { + width: 90px; + margin: 0; + } + #output { + width: 500px; + height: 300px; + } + </style> + </head> + <body> + <textarea id="output"></textarea><br /> + <input id="input" /><input type="submit" id="send" value="Send" onclick="javascript:click();" /> + </body> +</html> diff --git a/examples/webchannel/standalone/main.py b/examples/webchannel/standalone/main.py new file mode 100644 index 000000000..d3119141f --- /dev/null +++ b/examples/webchannel/standalone/main.py @@ -0,0 +1,99 @@ +############################################################################# +## +## Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + + +import os +import sys + +from PySide2.QtWidgets import QApplication +from PySide2.QtGui import QDesktopServices +from PySide2.QtNetwork import QHostAddress, QSslSocket +from PySide2.QtCore import (QFile, QFileInfo, QUrl) +from PySide2.QtWebChannel import QWebChannel +from PySide2.QtWebSockets import QWebSocketServer + +from dialog import Dialog +from core import Core +from websocketclientwrapper import WebSocketClientWrapper + + +if __name__ == '__main__': + app = QApplication(sys.argv) + if not QSslSocket.supportsSsl(): + print('The example requires SSL support.') + sys.exit(-1) + cur_dir = os.path.dirname(os.path.abspath(__file__)) + jsFileInfo = QFileInfo(cur_dir + "/qwebchannel.js") + if not jsFileInfo.exists(): + QFile.copy(":/qtwebchannel/qwebchannel.js", + jsFileInfo.absoluteFilePath()) + + # setup the QWebSocketServer + server = QWebSocketServer("QWebChannel Standalone Example Server", + QWebSocketServer.NonSecureMode) + if not server.listen(QHostAddress.LocalHost, 12345): + print("Failed to open web socket server.") + sys.exit(-1) + + # wrap WebSocket clients in QWebChannelAbstractTransport objects + clientWrapper = WebSocketClientWrapper(server) + + # setup the channel + channel = QWebChannel() + clientWrapper.clientConnected.connect(channel.connectTo) + + # setup the UI + dialog = Dialog() + + # setup the core and publish it to the QWebChannel + core = Core(dialog) + channel.registerObject("core", core) + + # open a browser window with the client HTML page + url = QUrl.fromLocalFile(cur_dir + "/index.html") + QDesktopServices.openUrl(url) + + message = "Initialization complete, opening browser at {}.".format( + url.toDisplayString()) + dialog.displayMessage(message) + dialog.show() + + sys.exit(app.exec_()) diff --git a/examples/webchannel/standalone/standalone.pyproject b/examples/webchannel/standalone/standalone.pyproject new file mode 100644 index 000000000..b4fcdfa8e --- /dev/null +++ b/examples/webchannel/standalone/standalone.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["main.py", "core.py", "dialog.py", "websocketclientwrapper.py", + "websockettransport.py", "dialog.ui", "index.html"] +} diff --git a/examples/webchannel/standalone/ui_dialog.py b/examples/webchannel/standalone/ui_dialog.py new file mode 100644 index 000000000..873edba10 --- /dev/null +++ b/examples/webchannel/standalone/ui_dialog.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'dialog.ui' +## +## Created by: Qt User Interface Compiler version 5.14.1 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide2.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint, + QRect, QSize, QUrl, Qt) +from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, + QFontDatabase, QIcon, QLinearGradient, QPalette, QPainter, QPixmap, + QRadialGradient) +from PySide2.QtWidgets import * + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + if not Dialog.objectName(): + Dialog.setObjectName(u"Dialog") + Dialog.resize(400, 300) + self.gridLayout = QGridLayout(Dialog) + self.gridLayout.setObjectName(u"gridLayout") + self.input = QLineEdit(Dialog) + self.input.setObjectName(u"input") + + self.gridLayout.addWidget(self.input, 1, 0, 1, 1) + + self.send = QPushButton(Dialog) + self.send.setObjectName(u"send") + + self.gridLayout.addWidget(self.send, 1, 1, 1, 1) + + self.output = QPlainTextEdit(Dialog) + self.output.setObjectName(u"output") + self.output.setReadOnly(True) + self.output.setPlainText(u"Initializing WebChannel...") + self.output.setBackgroundVisible(False) + + self.gridLayout.addWidget(self.output, 0, 0, 1, 2) + + + self.retranslateUi(Dialog) + + QMetaObject.connectSlotsByName(Dialog) + # setupUi + + def retranslateUi(self, Dialog): + Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None)) + self.input.setPlaceholderText(QCoreApplication.translate("Dialog", u"Message Contents", None)) + self.send.setText(QCoreApplication.translate("Dialog", u"Send", None)) + # retranslateUi + diff --git a/examples/webchannel/standalone/websocketclientwrapper.py b/examples/webchannel/standalone/websocketclientwrapper.py new file mode 100644 index 000000000..24505b03b --- /dev/null +++ b/examples/webchannel/standalone/websocketclientwrapper.py @@ -0,0 +1,72 @@ +############################################################################# +## +## Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from PySide2.QtCore import QObject, Signal, Slot + +from websockettransport import WebSocketTransport + + +class WebSocketClientWrapper(QObject): + """Wraps connected QWebSockets clients in WebSocketTransport objects. + + This code is all that is required to connect incoming WebSockets to + the WebChannel. Any kind of remote JavaScript client that supports + WebSockets can thus receive messages and access the published objects. + """ + clientConnected = Signal(WebSocketTransport) + + def __init__(self, server, parent=None): + """Construct the client wrapper with the given parent. All clients + connecting to the QWebSocketServer will be automatically wrapped + in WebSocketTransport objects.""" + super(WebSocketClientWrapper, self).__init__(parent) + self._server = server + self._server.newConnection.connect(self.handleNewConnection) + self._transports = [] + + @Slot() + def handleNewConnection(self): + """Wrap an incoming WebSocket connection in a WebSocketTransport + object.""" + socket = self._server.nextPendingConnection() + transport = WebSocketTransport(socket) + self._transports.append(transport) + self.clientConnected.emit(transport) diff --git a/examples/webchannel/standalone/websockettransport.py b/examples/webchannel/standalone/websockettransport.py new file mode 100644 index 000000000..4e42e7674 --- /dev/null +++ b/examples/webchannel/standalone/websockettransport.py @@ -0,0 +1,88 @@ +############################################################################# +## +## Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from PySide2.QtWebChannel import QWebChannelAbstractTransport +from PySide2.QtCore import QByteArray, QJsonDocument, Slot + + +class WebSocketTransport(QWebChannelAbstractTransport): + """QWebChannelAbstractSocket implementation using a QWebSocket internally. + + The transport delegates all messages received over the QWebSocket over + its textMessageReceived signal. Analogously, all calls to + sendTextMessage will be sent over the QWebSocket to the remote client. + """ + + def __init__(self, socket): + """Construct the transport object and wrap the given socket. + The socket is also set as the parent of the transport object.""" + super(WebSocketTransport, self).__init__(socket) + self._socket = socket + self._socket.textMessageReceived.connect(self.textMessageReceived) + self._socket.disconnected.connect(self._disconnected) + + def __del__(self): + """Destroys the WebSocketTransport.""" + self._socket.deleteLater() + + def _disconnected(self): + self.deleteLater() + + def sendMessage(self, message): + """Serialize the JSON message and send it as a text message via the + WebSocket to the client.""" + doc = QJsonDocument(message) + json_message = str(doc.toJson(QJsonDocument.Compact), "utf-8") + self._socket.sendTextMessage(json_message) + + @Slot(str) + def textMessageReceived(self, message_data_in): + """Deserialize the stringified JSON messageData and emit + messageReceived.""" + message_data = QByteArray(bytes(message_data_in, encoding='utf8')) + message = QJsonDocument.fromJson(message_data) + if message.isNull(): + print("Failed to parse text message as JSON object:", message_data) + return + if not message.isObject(): + print("Received JSON message that is not an object: ", message_data) + return + self.messageReceived.emit(message.object(), self) |