├── .DS_Store ├── hasher.h ├── web ├── css │ └── style.css ├── index.html ├── js │ └── main.js └── vendor │ └── qwebchannel.js ├── hasher.pro ├── hasher.cpp ├── main.cpp ├── shared ├── websocketclientwrapper.h ├── websockettransport.h ├── websocketclientwrapper.cpp └── websockettransport.cpp ├── README.md └── hasher.pro.user /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ynonp/qt-webchannel-demo/HEAD/.DS_Store -------------------------------------------------------------------------------- /hasher.h: -------------------------------------------------------------------------------- 1 | #ifndef HASHER_H 2 | #define HASHER_H 3 | 4 | #include 5 | 6 | 7 | class Hasher : public QObject 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit Hasher(QObject *parent = 0); 12 | 13 | signals: 14 | void notifyResult(QString method, QString hex); 15 | 16 | public slots: 17 | void md5(QString text); 18 | void sha256(QString text); 19 | void sha3(QString text); 20 | 21 | 22 | }; 23 | 24 | #endif // HASHER_H 25 | -------------------------------------------------------------------------------- /web/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 20px; 3 | background: #d2d2d2; 4 | } 5 | 6 | 7 | input { 8 | width: 80%; 9 | max-width: 400px; 10 | } 11 | 12 | label { 13 | min-width:100px; 14 | display:inline-block; 15 | } 16 | 17 | .controls { 18 | margin: 10px; 19 | padding-bottom:10px; 20 | border-bottom: 1px solid purple; 21 | margin-bottom:20px; 22 | } 23 | 24 | .results { 25 | margin: 10px; 26 | } 27 | 28 | .result label { 29 | display:inline-block; 30 | margin-bottom:10px; 31 | } 32 | -------------------------------------------------------------------------------- /hasher.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2015-07-19T10:28:20 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core webchannel websockets 8 | 9 | QT -= gui 10 | 11 | TARGET = hasher 12 | CONFIG += console 13 | CONFIG -= app_bundle 14 | 15 | TEMPLATE = app 16 | 17 | 18 | SOURCES += main.cpp \ 19 | shared/websocketclientwrapper.cpp \ 20 | shared/websockettransport.cpp \ 21 | hasher.cpp 22 | 23 | HEADERS += \ 24 | shared/websocketclientwrapper.h \ 25 | shared/websockettransport.h \ 26 | hasher.h 27 | -------------------------------------------------------------------------------- /hasher.cpp: -------------------------------------------------------------------------------- 1 | #include "hasher.h" 2 | #include 3 | #include 4 | 5 | Hasher::Hasher(QObject *parent) : QObject(parent) 6 | { 7 | 8 | } 9 | 10 | void Hasher::md5(QString text) 11 | { 12 | QString res = QCryptographicHash::hash(text.toLatin1(), QCryptographicHash::Md5).toHex(); 13 | qDebug() << "MD5 Got: " << res; 14 | 15 | emit notifyResult("md5", res); 16 | } 17 | 18 | void Hasher::sha256(QString text) 19 | { 20 | QString res = QCryptographicHash::hash(text.toLatin1(), QCryptographicHash::Sha256).toHex(); 21 | qDebug() << "SHA2 Got: " << res; 22 | emit notifyResult("sha2", res); 23 | } 24 | 25 | void Hasher::sha3(QString text) 26 | { 27 | QString res = QCryptographicHash::hash(text.toLatin1(), QCryptographicHash::Sha3_512).toHex(); 28 | qDebug() << "SHA3 Got: " << res; 29 | emit notifyResult("sha3", res); 30 | } 31 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | index 6 | 7 | 8 | 9 |
10 |

Status: 11 | Disconnected 12 |

13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /web/js/main.js: -------------------------------------------------------------------------------- 1 | var ws = new WebSocket("ws://localhost:12345"); 2 | var api; 3 | 4 | var el = { 5 | status: document.querySelector('#conn-status'), 6 | go: document.querySelector('#btn-go'), 7 | inp: document.querySelector('#inp-text'), 8 | md5: document.querySelector('#res-md5'), 9 | sha2: document.querySelector('#res-sha256'), 10 | sha3: document.querySelector('#res-sha3') 11 | }; 12 | 13 | ws.onopen = function() { 14 | el.status.innerHTML = "Connected"; 15 | 16 | new QWebChannel(ws, function(channel) { 17 | // make dialog object accessible globally 18 | api = channel.objects.hasher; 19 | 20 | api.notifyResult.connect(function(method, hex) { 21 | el[method].value = hex; 22 | }); 23 | }); 24 | }; 25 | 26 | ws.onerror = function() { 27 | el.status.innerHTML = "Error"; 28 | }; 29 | 30 | ws.onclose = function() { 31 | el.status.innerHTML = "Closed"; 32 | }; 33 | 34 | el.go.addEventListener('click', function() { 35 | if (! api ) { return; } 36 | 37 | var txt = el.inp.value; 38 | api.md5(txt); 39 | api.sha256(txt); 40 | api.sha3(txt); 41 | }); 42 | 43 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "shared/websocketclientwrapper.h" 6 | #include "shared/websockettransport.h" 7 | 8 | #include "hasher.h" 9 | 10 | int main(int argc, char *argv[]) 11 | { 12 | QCoreApplication app(argc, argv); 13 | 14 | // setup the QWebSocketServer 15 | QWebSocketServer server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode); 16 | if (!server.listen(QHostAddress::LocalHost, 12345)) { 17 | qFatal("Failed to open web socket server."); 18 | return 1; 19 | } 20 | 21 | // wrap WebSocket clients in QWebChannelAbstractTransport objects 22 | WebSocketClientWrapper clientWrapper(&server); 23 | 24 | // setup the channel 25 | QWebChannel channel; 26 | QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected, 27 | &channel, &QWebChannel::connectTo); 28 | 29 | // setup the dialog and publish it to the QWebChannel 30 | Hasher hasher; 31 | channel.registerObject(QStringLiteral("hasher"), &hasher); 32 | 33 | return app.exec(); 34 | } 35 | -------------------------------------------------------------------------------- /shared/websocketclientwrapper.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:LGPL21$ 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 http://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at http://www.qt.io/contact-us. 16 | ** 17 | ** GNU Lesser General Public License Usage 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser 19 | ** General Public License version 2.1 or version 3 as published by the Free 20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22 | ** following information to ensure the GNU Lesser General Public License 23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25 | ** 26 | ** As a special exception, The Qt Company gives you certain additional 27 | ** rights. These rights are described in The Qt Company LGPL Exception 28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29 | ** 30 | ** $QT_END_LICENSE$ 31 | ** 32 | ****************************************************************************/ 33 | 34 | #ifndef WEBSOCKETTRANSPORTSERVER_H 35 | #define WEBSOCKETTRANSPORTSERVER_H 36 | 37 | #include 38 | 39 | QT_BEGIN_NAMESPACE 40 | 41 | class QWebSocketServer; 42 | class WebSocketTransport; 43 | 44 | class WebSocketClientWrapper : public QObject 45 | { 46 | Q_OBJECT 47 | 48 | public: 49 | WebSocketClientWrapper(QWebSocketServer *server, QObject *parent = 0); 50 | 51 | Q_SIGNALS: 52 | void clientConnected(WebSocketTransport* client); 53 | 54 | private Q_SLOTS: 55 | void handleNewConnection(); 56 | 57 | private: 58 | QWebSocketServer *m_server; 59 | }; 60 | 61 | QT_END_NAMESPACE 62 | 63 | #endif // WEBSOCKETTRANSPORTSERVER_H 64 | -------------------------------------------------------------------------------- /shared/websockettransport.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:LGPL21$ 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 http://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at http://www.qt.io/contact-us. 16 | ** 17 | ** GNU Lesser General Public License Usage 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser 19 | ** General Public License version 2.1 or version 3 as published by the Free 20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22 | ** following information to ensure the GNU Lesser General Public License 23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25 | ** 26 | ** As a special exception, The Qt Company gives you certain additional 27 | ** rights. These rights are described in The Qt Company LGPL Exception 28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29 | ** 30 | ** $QT_END_LICENSE$ 31 | ** 32 | ****************************************************************************/ 33 | 34 | #ifndef WEBSOCKETTRANSPORT_H 35 | #define WEBSOCKETTRANSPORT_H 36 | 37 | #include 38 | 39 | QT_BEGIN_NAMESPACE 40 | 41 | class QWebSocket; 42 | class WebSocketTransport : public QWebChannelAbstractTransport 43 | { 44 | Q_OBJECT 45 | public: 46 | explicit WebSocketTransport(QWebSocket *socket); 47 | virtual ~WebSocketTransport(); 48 | 49 | void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE; 50 | 51 | private Q_SLOTS: 52 | void textMessageReceived(const QString &message); 53 | 54 | private: 55 | QWebSocket *m_socket; 56 | }; 57 | 58 | QT_END_NAMESPACE 59 | 60 | #endif // WEBSOCKETTRANSPORT_H 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qt WebChannel Demo 2 | 3 | Qt 5.5 has added a cool new feature letting developers connect any existing C++ code with any web based frontend UI. The connection is made through a QWebChannel class. 4 | 5 | This repository shows a simple example of that connection based on the example in Qt documentation. Here's the important bits of code you need to understand to get started. 6 | 7 | ## Setup 8 | 9 | Setting up the code requires running a Socket Server on the Qt/C++ side and connecting to it from JS. The code to run the socket server is found in main. After server is up, we register a C++ class on the channel, so JS code can call methods on the object: 10 | 11 | QWebSocketServer server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode); 12 | if (!server.listen(QHostAddress::LocalHost, 12345)) { 13 | qFatal("Failed to open web socket server."); 14 | return 1; 15 | } 16 | 17 | // wrap WebSocket clients in QWebChannelAbstractTransport objects 18 | WebSocketClientWrapper clientWrapper(&server); 19 | 20 | // setup the channel 21 | QWebChannel channel; 22 | QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected, 23 | &channel, &QWebChannel::connectTo); 24 | 25 | // setup the dialog and publish it to the QWebChannel 26 | Hasher hasher; 27 | channel.registerObject(QStringLiteral("hasher"), &hasher); 28 | 29 | From JavaScript we need to connect to the web server. Notice the example uses localhost: 30 | 31 | var ws = new WebSocket("ws://localhost:12345"); 32 | ws.onopen = function() { 33 | el.status.innerHTML = "Connected"; 34 | 35 | new QWebChannel(ws, function(channel) { 36 | // make dialog object accessible globally 37 | api = channel.objects.hasher; 38 | 39 | api.notifyResult.connect(function(method, hex) { 40 | el[method].value = hex; 41 | }); 42 | }); 43 | }; 44 | 45 | ## Running Code 46 | After everythin's set up calling methods is easy. Each object registered on the channel is available to JS using channel.objects.objectName. The example uses an object named hasher so it is accessed from JS with channel.object.hasher 47 | 48 | On the C++ side, the object is just a normal QObject with signals and slots. Every slot of the object is available for JS side to call as a normal function call, and every signal is an object that JS can "connect" to. 49 | 50 | The lines: 51 | 52 | api.notifyResult.connect(function(method, hex) { 53 | el[method].value = hex; 54 | }); 55 | 56 | Start to listen on notifyResult signal and whenever it is emitted runs the callback. The lines: 57 | 58 | api.md5(txt); 59 | api.sha256(txt); 60 | api.sha3(txt); 61 | 62 | Call the slots `md5`, `sha256` and `sha3` of the C++ hasher object. 63 | 64 | -------------------------------------------------------------------------------- /shared/websocketclientwrapper.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:LGPL21$ 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 http://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at http://www.qt.io/contact-us. 16 | ** 17 | ** GNU Lesser General Public License Usage 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser 19 | ** General Public License version 2.1 or version 3 as published by the Free 20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22 | ** following information to ensure the GNU Lesser General Public License 23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25 | ** 26 | ** As a special exception, The Qt Company gives you certain additional 27 | ** rights. These rights are described in The Qt Company LGPL Exception 28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29 | ** 30 | ** $QT_END_LICENSE$ 31 | ** 32 | ****************************************************************************/ 33 | 34 | #include "websocketclientwrapper.h" 35 | 36 | #include 37 | 38 | #include "websockettransport.h" 39 | 40 | /*! 41 | \brief Wrapps connected QWebSockets clients in WebSocketTransport objects. 42 | 43 | This code is all that is required to connect incoming WebSockets to the WebChannel. Any kind 44 | of remote JavaScript client that supports WebSockets can thus receive messages and access the 45 | published objects. 46 | */ 47 | 48 | QT_BEGIN_NAMESPACE 49 | 50 | /*! 51 | Construct the client wrapper with the given parent. 52 | 53 | All clients connecting to the QWebSocketServer will be automatically wrapped 54 | in WebSocketTransport objects. 55 | */ 56 | WebSocketClientWrapper::WebSocketClientWrapper(QWebSocketServer *server, QObject *parent) 57 | : QObject(parent) 58 | , m_server(server) 59 | { 60 | connect(server, &QWebSocketServer::newConnection, 61 | this, &WebSocketClientWrapper::handleNewConnection); 62 | } 63 | 64 | /*! 65 | Wrap an incoming WebSocket connection in a WebSocketTransport object. 66 | */ 67 | void WebSocketClientWrapper::handleNewConnection() 68 | { 69 | emit clientConnected(new WebSocketTransport(m_server->nextPendingConnection())); 70 | } 71 | 72 | QT_END_NAMESPACE 73 | -------------------------------------------------------------------------------- /shared/websockettransport.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:LGPL21$ 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 http://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at http://www.qt.io/contact-us. 16 | ** 17 | ** GNU Lesser General Public License Usage 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser 19 | ** General Public License version 2.1 or version 3 as published by the Free 20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22 | ** following information to ensure the GNU Lesser General Public License 23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25 | ** 26 | ** As a special exception, The Qt Company gives you certain additional 27 | ** rights. These rights are described in The Qt Company LGPL Exception 28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29 | ** 30 | ** $QT_END_LICENSE$ 31 | ** 32 | ****************************************************************************/ 33 | 34 | #include "websockettransport.h" 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | /*! 43 | \brief QWebChannelAbstractSocket implementation that uses a QWebSocket internally. 44 | 45 | The transport delegates all messages received over the QWebSocket over its 46 | textMessageReceived signal. Analogously, all calls to sendTextMessage will 47 | be send over the QWebSocket to the remote client. 48 | */ 49 | 50 | QT_BEGIN_NAMESPACE 51 | 52 | /*! 53 | Construct the transport object and wrap the given socket. 54 | 55 | The socket is also set as the parent of the transport object. 56 | */ 57 | WebSocketTransport::WebSocketTransport(QWebSocket *socket) 58 | : QWebChannelAbstractTransport(socket) 59 | , m_socket(socket) 60 | { 61 | connect(socket, &QWebSocket::textMessageReceived, 62 | this, &WebSocketTransport::textMessageReceived); 63 | } 64 | 65 | /*! 66 | Destroys the WebSocketTransport. 67 | */ 68 | WebSocketTransport::~WebSocketTransport() 69 | { 70 | 71 | } 72 | 73 | /*! 74 | Serialize the JSON message and send it as a text message via the WebSocket to the client. 75 | */ 76 | void WebSocketTransport::sendMessage(const QJsonObject &message) 77 | { 78 | QJsonDocument doc(message); 79 | m_socket->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact))); 80 | } 81 | 82 | /*! 83 | Deserialize the stringified JSON messageData and emit messageReceived. 84 | */ 85 | void WebSocketTransport::textMessageReceived(const QString &messageData) 86 | { 87 | QJsonParseError error; 88 | QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error); 89 | if (error.error) { 90 | qWarning() << "Failed to parse text message as JSON object:" << messageData 91 | << "Error is:" << error.errorString(); 92 | return; 93 | } else if (!message.isObject()) { 94 | qWarning() << "Received JSON message that is not an object: " << messageData; 95 | return; 96 | } 97 | emit messageReceived(message.object(), this); 98 | } 99 | 100 | QT_END_NAMESPACE 101 | -------------------------------------------------------------------------------- /web/vendor/qwebchannel.js: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff 5 | ** Contact: http://www.qt.io/licensing/ 6 | ** 7 | ** This file is part of the QtWebChannel module of the Qt Toolkit. 8 | ** 9 | ** $QT_BEGIN_LICENSE:LGPL21$ 10 | ** Commercial License Usage 11 | ** Licensees holding valid commercial Qt licenses may use this file in 12 | ** accordance with the commercial license agreement provided with the 13 | ** Software or, alternatively, in accordance with the terms contained in 14 | ** a written agreement between you and The Qt Company. For licensing terms 15 | ** and conditions see http://www.qt.io/terms-conditions. For further 16 | ** information use the contact form at http://www.qt.io/contact-us. 17 | ** 18 | ** GNU Lesser General Public License Usage 19 | ** Alternatively, this file may be used under the terms of the GNU Lesser 20 | ** General Public License version 2.1 or version 3 as published by the Free 21 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 22 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 23 | ** following information to ensure the GNU Lesser General Public License 24 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 25 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 26 | ** 27 | ** As a special exception, The Qt Company gives you certain additional 28 | ** rights. These rights are described in The Qt Company LGPL Exception 29 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 30 | ** 31 | ** $QT_END_LICENSE$ 32 | ** 33 | ****************************************************************************/ 34 | 35 | "use strict"; 36 | 37 | var QWebChannelMessageTypes = { 38 | signal: 1, 39 | propertyUpdate: 2, 40 | init: 3, 41 | idle: 4, 42 | debug: 5, 43 | invokeMethod: 6, 44 | connectToSignal: 7, 45 | disconnectFromSignal: 8, 46 | setProperty: 9, 47 | response: 10, 48 | }; 49 | 50 | var QWebChannel = function(transport, initCallback) 51 | { 52 | if (typeof transport !== "object" || typeof transport.send !== "function") { 53 | console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + 54 | " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); 55 | return; 56 | } 57 | 58 | var channel = this; 59 | this.transport = transport; 60 | 61 | this.send = function(data) 62 | { 63 | if (typeof(data) !== "string") { 64 | data = JSON.stringify(data); 65 | } 66 | channel.transport.send(data); 67 | } 68 | 69 | this.transport.onmessage = function(message) 70 | { 71 | var data = message.data; 72 | if (typeof data === "string") { 73 | data = JSON.parse(data); 74 | } 75 | switch (data.type) { 76 | case QWebChannelMessageTypes.signal: 77 | channel.handleSignal(data); 78 | break; 79 | case QWebChannelMessageTypes.response: 80 | channel.handleResponse(data); 81 | break; 82 | case QWebChannelMessageTypes.propertyUpdate: 83 | channel.handlePropertyUpdate(data); 84 | break; 85 | default: 86 | console.error("invalid message received:", message.data); 87 | break; 88 | } 89 | } 90 | 91 | this.execCallbacks = {}; 92 | this.execId = 0; 93 | this.exec = function(data, callback) 94 | { 95 | if (!callback) { 96 | // if no callback is given, send directly 97 | channel.send(data); 98 | return; 99 | } 100 | if (channel.execId === Number.MAX_VALUE) { 101 | // wrap 102 | channel.execId = Number.MIN_VALUE; 103 | } 104 | if (data.hasOwnProperty("id")) { 105 | console.error("Cannot exec message with property id: " + JSON.stringify(data)); 106 | return; 107 | } 108 | data.id = channel.execId++; 109 | channel.execCallbacks[data.id] = callback; 110 | channel.send(data); 111 | }; 112 | 113 | this.objects = {}; 114 | 115 | this.handleSignal = function(message) 116 | { 117 | var object = channel.objects[message.object]; 118 | if (object) { 119 | object.signalEmitted(message.signal, message.args); 120 | } else { 121 | console.warn("Unhandled signal: " + message.object + "::" + message.signal); 122 | } 123 | } 124 | 125 | this.handleResponse = function(message) 126 | { 127 | if (!message.hasOwnProperty("id")) { 128 | console.error("Invalid response message received: ", JSON.stringify(message)); 129 | return; 130 | } 131 | channel.execCallbacks[message.id](message.data); 132 | delete channel.execCallbacks[message.id]; 133 | } 134 | 135 | this.handlePropertyUpdate = function(message) 136 | { 137 | for (var i in message.data) { 138 | var data = message.data[i]; 139 | var object = channel.objects[data.object]; 140 | if (object) { 141 | object.propertyUpdate(data.signals, data.properties); 142 | } else { 143 | console.warn("Unhandled property update: " + data.object + "::" + data.signal); 144 | } 145 | } 146 | channel.exec({type: QWebChannelMessageTypes.idle}); 147 | } 148 | 149 | this.debug = function(message) 150 | { 151 | channel.send({type: QWebChannelMessageTypes.debug, data: message}); 152 | }; 153 | 154 | channel.exec({type: QWebChannelMessageTypes.init}, function(data) { 155 | for (var objectName in data) { 156 | var object = new QObject(objectName, data[objectName], channel); 157 | } 158 | // now unwrap properties, which might reference other registered objects 159 | for (var objectName in channel.objects) { 160 | channel.objects[objectName].unwrapProperties(); 161 | } 162 | if (initCallback) { 163 | initCallback(channel); 164 | } 165 | channel.exec({type: QWebChannelMessageTypes.idle}); 166 | }); 167 | }; 168 | 169 | function QObject(name, data, webChannel) 170 | { 171 | this.__id__ = name; 172 | webChannel.objects[name] = this; 173 | 174 | // List of callbacks that get invoked upon signal emission 175 | this.__objectSignals__ = {}; 176 | 177 | // Cache of all properties, updated when a notify signal is emitted 178 | this.__propertyCache__ = {}; 179 | 180 | var object = this; 181 | 182 | // ---------------------------------------------------------------------- 183 | 184 | this.unwrapQObject = function(response) 185 | { 186 | if (response instanceof Array) { 187 | // support list of objects 188 | var ret = new Array(response.length); 189 | for (var i = 0; i < response.length; ++i) { 190 | ret[i] = object.unwrapQObject(response[i]); 191 | } 192 | return ret; 193 | } 194 | if (!response 195 | || !response["__QObject*__"] 196 | || response.id === undefined) { 197 | return response; 198 | } 199 | 200 | var objectId = response.id; 201 | if (webChannel.objects[objectId]) 202 | return webChannel.objects[objectId]; 203 | 204 | if (!response.data) { 205 | console.error("Cannot unwrap unknown QObject " + objectId + " without data."); 206 | return; 207 | } 208 | 209 | var qObject = new QObject( objectId, response.data, webChannel ); 210 | qObject.destroyed.connect(function() { 211 | if (webChannel.objects[objectId] === qObject) { 212 | delete webChannel.objects[objectId]; 213 | // reset the now deleted QObject to an empty {} object 214 | // just assigning {} though would not have the desired effect, but the 215 | // below also ensures all external references will see the empty map 216 | // NOTE: this detour is necessary to workaround QTBUG-40021 217 | var propertyNames = []; 218 | for (var propertyName in qObject) { 219 | propertyNames.push(propertyName); 220 | } 221 | for (var idx in propertyNames) { 222 | delete qObject[propertyNames[idx]]; 223 | } 224 | } 225 | }); 226 | // here we are already initialized, and thus must directly unwrap the properties 227 | qObject.unwrapProperties(); 228 | return qObject; 229 | } 230 | 231 | this.unwrapProperties = function() 232 | { 233 | for (var propertyIdx in object.__propertyCache__) { 234 | object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); 235 | } 236 | } 237 | 238 | function addSignal(signalData, isPropertyNotifySignal) 239 | { 240 | var signalName = signalData[0]; 241 | var signalIndex = signalData[1]; 242 | object[signalName] = { 243 | connect: function(callback) { 244 | if (typeof(callback) !== "function") { 245 | console.error("Bad callback given to connect to signal " + signalName); 246 | return; 247 | } 248 | 249 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 250 | object.__objectSignals__[signalIndex].push(callback); 251 | 252 | if (!isPropertyNotifySignal && signalName !== "destroyed") { 253 | // only required for "pure" signals, handled separately for properties in propertyUpdate 254 | // also note that we always get notified about the destroyed signal 255 | webChannel.exec({ 256 | type: QWebChannelMessageTypes.connectToSignal, 257 | object: object.__id__, 258 | signal: signalIndex 259 | }); 260 | } 261 | }, 262 | disconnect: function(callback) { 263 | if (typeof(callback) !== "function") { 264 | console.error("Bad callback given to disconnect from signal " + signalName); 265 | return; 266 | } 267 | object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; 268 | var idx = object.__objectSignals__[signalIndex].indexOf(callback); 269 | if (idx === -1) { 270 | console.error("Cannot find connection of signal " + signalName + " to " + callback.name); 271 | return; 272 | } 273 | object.__objectSignals__[signalIndex].splice(idx, 1); 274 | if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { 275 | // only required for "pure" signals, handled separately for properties in propertyUpdate 276 | webChannel.exec({ 277 | type: QWebChannelMessageTypes.disconnectFromSignal, 278 | object: object.__id__, 279 | signal: signalIndex 280 | }); 281 | } 282 | } 283 | }; 284 | } 285 | 286 | /** 287 | * Invokes all callbacks for the given signalname. Also works for property notify callbacks. 288 | */ 289 | function invokeSignalCallbacks(signalName, signalArgs) 290 | { 291 | var connections = object.__objectSignals__[signalName]; 292 | if (connections) { 293 | connections.forEach(function(callback) { 294 | callback.apply(callback, signalArgs); 295 | }); 296 | } 297 | } 298 | 299 | this.propertyUpdate = function(signals, propertyMap) 300 | { 301 | // update property cache 302 | for (var propertyIndex in propertyMap) { 303 | var propertyValue = propertyMap[propertyIndex]; 304 | object.__propertyCache__[propertyIndex] = propertyValue; 305 | } 306 | 307 | for (var signalName in signals) { 308 | // Invoke all callbacks, as signalEmitted() does not. This ensures the 309 | // property cache is updated before the callbacks are invoked. 310 | invokeSignalCallbacks(signalName, signals[signalName]); 311 | } 312 | } 313 | 314 | this.signalEmitted = function(signalName, signalArgs) 315 | { 316 | invokeSignalCallbacks(signalName, signalArgs); 317 | } 318 | 319 | function addMethod(methodData) 320 | { 321 | var methodName = methodData[0]; 322 | var methodIdx = methodData[1]; 323 | object[methodName] = function() { 324 | var args = []; 325 | var callback; 326 | for (var i = 0; i < arguments.length; ++i) { 327 | if (typeof arguments[i] === "function") 328 | callback = arguments[i]; 329 | else 330 | args.push(arguments[i]); 331 | } 332 | 333 | webChannel.exec({ 334 | "type": QWebChannelMessageTypes.invokeMethod, 335 | "object": object.__id__, 336 | "method": methodIdx, 337 | "args": args 338 | }, function(response) { 339 | if (response !== undefined) { 340 | var result = object.unwrapQObject(response); 341 | if (callback) { 342 | (callback)(result); 343 | } 344 | } 345 | }); 346 | }; 347 | } 348 | 349 | function bindGetterSetter(propertyInfo) 350 | { 351 | var propertyIndex = propertyInfo[0]; 352 | var propertyName = propertyInfo[1]; 353 | var notifySignalData = propertyInfo[2]; 354 | // initialize property cache with current value 355 | // NOTE: if this is an object, it is not directly unwrapped as it might 356 | // reference other QObject that we do not know yet 357 | object.__propertyCache__[propertyIndex] = propertyInfo[3]; 358 | 359 | if (notifySignalData) { 360 | if (notifySignalData[0] === 1) { 361 | // signal name is optimized away, reconstruct the actual name 362 | notifySignalData[0] = propertyName + "Changed"; 363 | } 364 | addSignal(notifySignalData, true); 365 | } 366 | 367 | Object.defineProperty(object, propertyName, { 368 | configurable: true, 369 | get: function () { 370 | var propertyValue = object.__propertyCache__[propertyIndex]; 371 | if (propertyValue === undefined) { 372 | // This shouldn't happen 373 | console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); 374 | } 375 | 376 | return propertyValue; 377 | }, 378 | set: function(value) { 379 | if (value === undefined) { 380 | console.warn("Property setter for " + propertyName + " called with undefined value!"); 381 | return; 382 | } 383 | object.__propertyCache__[propertyIndex] = value; 384 | webChannel.exec({ 385 | "type": QWebChannelMessageTypes.setProperty, 386 | "object": object.__id__, 387 | "property": propertyIndex, 388 | "value": value 389 | }); 390 | } 391 | }); 392 | 393 | } 394 | 395 | // ---------------------------------------------------------------------- 396 | 397 | data.methods.forEach(addMethod); 398 | 399 | data.properties.forEach(bindGetterSetter); 400 | 401 | data.signals.forEach(function(signal) { addSignal(signal, false); }); 402 | 403 | for (var name in data.enums) { 404 | object[name] = data.enums[name]; 405 | } 406 | } 407 | 408 | //required for use with nodejs 409 | if (typeof module === 'object') { 410 | module.exports = { 411 | QWebChannel: QWebChannel 412 | }; 413 | } 414 | -------------------------------------------------------------------------------- /hasher.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {e4c82457-a329-45e2-9d1d-5c5459e3c368} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | 0 45 | 8 46 | true 47 | 1 48 | true 49 | true 50 | true 51 | false 52 | 53 | 54 | 55 | ProjectExplorer.Project.PluginSettings 56 | 57 | 58 | 59 | ProjectExplorer.Project.Target.0 60 | 61 | Desktop Qt 5.5.0 clang 64bit 62 | Desktop Qt 5.5.0 clang 64bit 63 | qt.55.clang_64_kit 64 | 0 65 | 0 66 | 0 67 | 68 | /Users/ynonperek/tmp/qt/build-hasher-Desktop_Qt_5_5_0_clang_64bit-Debug 69 | 70 | 71 | true 72 | qmake 73 | 74 | QtProjectManager.QMakeBuildStep 75 | false 76 | true 77 | 78 | false 79 | false 80 | false 81 | 82 | 83 | true 84 | Make 85 | 86 | Qt4ProjectManager.MakeStep 87 | 88 | -w 89 | -r 90 | 91 | false 92 | 93 | 94 | 95 | 2 96 | Build 97 | 98 | ProjectExplorer.BuildSteps.Build 99 | 100 | 101 | 102 | true 103 | Make 104 | 105 | Qt4ProjectManager.MakeStep 106 | 107 | -w 108 | -r 109 | 110 | true 111 | clean 112 | 113 | 114 | 1 115 | Clean 116 | 117 | ProjectExplorer.BuildSteps.Clean 118 | 119 | 2 120 | false 121 | 122 | Debug 123 | 124 | Qt4ProjectManager.Qt4BuildConfiguration 125 | 2 126 | true 127 | 128 | 129 | /Users/ynonperek/tmp/qt/build-hasher-Desktop_Qt_5_5_0_clang_64bit-Release 130 | 131 | 132 | true 133 | qmake 134 | 135 | QtProjectManager.QMakeBuildStep 136 | false 137 | true 138 | 139 | false 140 | false 141 | false 142 | 143 | 144 | true 145 | Make 146 | 147 | Qt4ProjectManager.MakeStep 148 | 149 | -w 150 | -r 151 | 152 | false 153 | 154 | 155 | 156 | 2 157 | Build 158 | 159 | ProjectExplorer.BuildSteps.Build 160 | 161 | 162 | 163 | true 164 | Make 165 | 166 | Qt4ProjectManager.MakeStep 167 | 168 | -w 169 | -r 170 | 171 | true 172 | clean 173 | 174 | 175 | 1 176 | Clean 177 | 178 | ProjectExplorer.BuildSteps.Clean 179 | 180 | 2 181 | false 182 | 183 | Release 184 | 185 | Qt4ProjectManager.Qt4BuildConfiguration 186 | 0 187 | true 188 | 189 | 2 190 | 191 | 192 | 0 193 | Deploy 194 | 195 | ProjectExplorer.BuildSteps.Deploy 196 | 197 | 1 198 | Deploy locally 199 | 200 | ProjectExplorer.DefaultDeployConfiguration 201 | 202 | 1 203 | 204 | 205 | 206 | false 207 | false 208 | false 209 | false 210 | true 211 | 0.01 212 | 10 213 | true 214 | 1 215 | 25 216 | 217 | 1 218 | true 219 | false 220 | true 221 | valgrind 222 | 223 | 0 224 | 1 225 | 2 226 | 3 227 | 4 228 | 5 229 | 6 230 | 7 231 | 8 232 | 9 233 | 10 234 | 11 235 | 12 236 | 13 237 | 14 238 | 239 | -1 240 | 241 | 242 | 243 | false 244 | %{buildDir} 245 | Custom Executable 246 | 247 | ProjectExplorer.CustomExecutableRunConfiguration 248 | 3768 249 | false 250 | true 251 | false 252 | false 253 | true 254 | 255 | 1 256 | 257 | 258 | 259 | ProjectExplorer.Project.TargetCount 260 | 1 261 | 262 | 263 | ProjectExplorer.Project.Updater.FileVersion 264 | 18 265 | 266 | 267 | Version 268 | 18 269 | 270 | 271 | --------------------------------------------------------------------------------