aboutsummaryrefslogtreecommitdiffstats
path: root/examples/webchannel
diff options
context:
space:
mode:
Diffstat (limited to 'examples/webchannel')
-rw-r--r--examples/webchannel/standalone/core.py26
-rw-r--r--examples/webchannel/standalone/dialog.py33
-rw-r--r--examples/webchannel/standalone/dialog.ui48
-rw-r--r--examples/webchannel/standalone/doc/standalone.pngbin0 -> 47586 bytes
-rw-r--r--examples/webchannel/standalone/doc/standalone.rst8
-rw-r--r--examples/webchannel/standalone/index.html79
-rw-r--r--examples/webchannel/standalone/main.py63
-rw-r--r--examples/webchannel/standalone/standalone.pyproject4
-rw-r--r--examples/webchannel/standalone/ui_dialog.py57
-rw-r--r--examples/webchannel/standalone/websocketclientwrapper.py36
-rw-r--r--examples/webchannel/standalone/websockettransport.py52
11 files changed, 406 insertions, 0 deletions
diff --git a/examples/webchannel/standalone/core.py b/examples/webchannel/standalone/core.py
new file mode 100644
index 000000000..987a4ee1a
--- /dev/null
+++ b/examples/webchannel/standalone/core.py
@@ -0,0 +1,26 @@
+# Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+
+from PySide6.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().__init__(parent)
+ self._dialog = dialog
+ self._dialog.send_text.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.display_message(f"Received message: {text}")
diff --git a/examples/webchannel/standalone/dialog.py b/examples/webchannel/standalone/dialog.py
new file mode 100644
index 000000000..27cef86a7
--- /dev/null
+++ b/examples/webchannel/standalone/dialog.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+
+from PySide6.QtCore import Signal, Slot
+from PySide6.QtWidgets import QDialog
+from ui_dialog import Ui_Dialog
+
+
+class Dialog(QDialog):
+ send_text = Signal(str)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._ui = Ui_Dialog()
+ self._ui.setupUi(self)
+ self._ui.send.clicked.connect(self.clicked)
+ self._ui.input.returnPressed.connect(self._ui.send.animateClick)
+
+ @Slot(str)
+ def display_message(self, message):
+ self._ui.output.appendPlainText(message)
+
+ @Slot()
+ def clicked(self):
+ text = self._ui.input.text()
+ if not text:
+ return
+ self.send_text.emit(text)
+ self.display_message(f"Sent message: {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/doc/standalone.png b/examples/webchannel/standalone/doc/standalone.png
new file mode 100644
index 000000000..972b0fbbf
--- /dev/null
+++ b/examples/webchannel/standalone/doc/standalone.png
Binary files differ
diff --git a/examples/webchannel/standalone/doc/standalone.rst b/examples/webchannel/standalone/doc/standalone.rst
new file mode 100644
index 000000000..f22f32c23
--- /dev/null
+++ b/examples/webchannel/standalone/doc/standalone.rst
@@ -0,0 +1,8 @@
+WebChannel Standalone Example
+=============================
+
+A simple chat between a server and a remote client running in a browser.
+
+.. image:: standalone.png
+ :width: 400
+ :alt: WebChannel Standalone Screenshot
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..8459b1a92
--- /dev/null
+++ b/examples/webchannel/standalone/main.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+
+import os
+import sys
+
+from PySide6.QtWidgets import QApplication
+from PySide6.QtGui import QDesktopServices
+from PySide6.QtNetwork import QHostAddress, QSslSocket
+from PySide6.QtCore import (QFile, QFileInfo, QUrl)
+from PySide6.QtWebChannel import QWebChannel
+from PySide6.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__))
+ js_file_info = QFileInfo(f"{cur_dir}/qwebchannel.js")
+ if not js_file_info.exists():
+ QFile.copy(":/qtwebchannel/qwebchannel.js",
+ js_file_info.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
+ client_wrapper = WebSocketClientWrapper(server)
+
+ # setup the channel
+ channel = QWebChannel()
+ client_wrapper.client_connected.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(f"{cur_dir}/index.html")
+ QDesktopServices.openUrl(url)
+
+ display_url = url.toDisplayString()
+ message = f"Initialization complete, opening browser at {display_url}."
+ dialog.display_message(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..36c2fe400
--- /dev/null
+++ b/examples/webchannel/standalone/ui_dialog.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout, QLineEdit,
+ QPlainTextEdit, QPushButton, QSizePolicy, QWidget)
+
+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..a14672f25
--- /dev/null
+++ b/examples/webchannel/standalone/websocketclientwrapper.py
@@ -0,0 +1,36 @@
+# Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+from PySide6.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.
+ """
+ client_connected = 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().__init__(parent)
+ self._server = server
+ self._server.newConnection.connect(self.handle_new_connection)
+ self._transports = []
+
+ @Slot()
+ def handle_new_connection(self):
+ """Wrap an incoming WebSocket connection in a WebSocketTransport
+ object."""
+ socket = self._server.nextPendingConnection()
+ transport = WebSocketTransport(socket)
+ self._transports.append(transport)
+ self.client_connected.emit(transport)
diff --git a/examples/webchannel/standalone/websockettransport.py b/examples/webchannel/standalone/websockettransport.py
new file mode 100644
index 000000000..96e9b822a
--- /dev/null
+++ b/examples/webchannel/standalone/websockettransport.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+from PySide6.QtWebChannel import QWebChannelAbstractTransport
+from PySide6.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().__init__(socket)
+ self._socket = socket
+ self._socket.textMessageReceived.connect(self.text_message_received)
+ 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 text_message_received(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)