-
-
Save gencer/c8a1ba785a68faf37d99069c4bd21f47 to your computer and use it in GitHub Desktop.
QWebChannel Javascript Connector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>QWebChannel Client</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.css" /> | |
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script> | |
<script type="text/javascript" src="qwebchannel.js"></script> | |
<script type="text/javascript" src="qwebchannel-client.js"></script> | |
</head> | |
<body> | |
<img id="imageCanvas" width="640" height="480"></img> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var wsUri = "ws://localhost:12345"; | |
window.onload = function() { | |
var socket = new WebSocket(wsUri); | |
socket.onclose = function() | |
{ | |
console.error("web channel closed"); | |
}; | |
socket.onerror = function(error) | |
{ | |
console.error("web channel error: " + error); | |
}; | |
socket.onopen = function() | |
{ | |
window.channel = new QWebChannel(socket, function(channel) { | |
// connect to the changed signal of a property | |
channel.objects.chatserver.propertyListChanged.connect(function() { | |
channel.objects.socketServer.propertyList.forEach(function(property) { | |
// Use property | |
}); | |
}); | |
// connect to a signal | |
channel.objects.socketServer.newImage.connect(function(blob) { | |
var image = document.getElementById("imageCanvas"); | |
image.src = "data:image/png;base64," + blob; | |
}); | |
// connect to a signal | |
channel.objects.socketServer.keepAlive.connect(function() { | |
//call a method | |
channel.objects.socketServer.keepAliveResponse() | |
}); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************************************** | |
** | |
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). | |
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected], author Milian Wolff <[email protected]> | |
** Contact: http://www.qt-project.org/legal | |
** | |
** This file is part of the QtWebChannel module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL21$ | |
** Commercial License Usage | |
** Licensees holding valid commercial Qt licenses may use this file in | |
** accordance with the commercial license agreement provided with the | |
** Software or, alternatively, in accordance with the terms contained in | |
** a written agreement between you and Digia. For licensing terms and | |
** conditions see http://qt.digia.com/licensing. For further information | |
** use the contact form at http://qt.digia.com/contact-us. | |
** | |
** GNU Lesser General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU Lesser | |
** General Public License version 2.1 or version 3 as published by the Free | |
** Software Foundation and appearing in the file LICENSE.LGPLv21 and | |
** LICENSE.LGPLv3 included in the packaging of this file. Please review the | |
** following information to ensure the GNU Lesser General Public License | |
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Digia gives you certain additional | |
** rights. These rights are described in the Digia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
"use strict"; | |
var QWebChannelMessageTypes = { | |
signal: 1, | |
propertyUpdate: 2, | |
init: 3, | |
idle: 4, | |
debug: 5, | |
invokeMethod: 6, | |
connectToSignal: 7, | |
disconnectFromSignal: 8, | |
setProperty: 9, | |
response: 10, | |
}; | |
var QWebChannel = function(transport, initCallback) | |
{ | |
if (typeof transport !== "object" || typeof transport.send !== "function") { | |
console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + | |
" Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); | |
return; | |
} | |
var channel = this; | |
this.transport = transport; | |
this.send = function(data) | |
{ | |
if (typeof(data) !== "string") { | |
data = JSON.stringify(data); | |
} | |
channel.transport.send(data); | |
} | |
this.transport.onmessage = function(message) | |
{ | |
var data = message.data; | |
if (typeof data === "string") { | |
data = JSON.parse(data); | |
} | |
switch (data.type) { | |
case QWebChannelMessageTypes.signal: | |
channel.handleSignal(data); | |
break; | |
case QWebChannelMessageTypes.response: | |
channel.handleResponse(data); | |
break; | |
case QWebChannelMessageTypes.propertyUpdate: | |
channel.handlePropertyUpdate(data); | |
break; | |
case QWebChannelMessageTypes.init: | |
channel.handleInit(data); | |
break; | |
default: | |
console.error("invalid message received:", message.data); | |
break; | |
} | |
} | |
this.execCallbacks = {}; | |
this.execId = 0; | |
this.exec = function(data, callback) | |
{ | |
if (!callback) { | |
// if no callback is given, send directly | |
channel.send(data); | |
return; | |
} | |
if (channel.execId === Number.MAX_VALUE) { | |
// wrap | |
channel.execId = Number.MIN_VALUE; | |
} | |
if (data.hasOwnProperty("id")) { | |
console.error("Cannot exec message with property id: " + JSON.stringify(data)); | |
return; | |
} | |
data.id = channel.execId++; | |
channel.execCallbacks[data.id] = callback; | |
channel.send(data); | |
}; | |
this.objects = {}; | |
this.handleSignal = function(message) | |
{ | |
var object = channel.objects[message.object]; | |
if (object) { | |
object.signalEmitted(message.signal, message.args); | |
} else { | |
console.warn("Unhandled signal: " + message.object + "::" + message.signal); | |
} | |
} | |
this.handleResponse = function(message) | |
{ | |
if (!message.hasOwnProperty("id")) { | |
console.error("Invalid response message received: ", JSON.stringify(message)); | |
return; | |
} | |
channel.execCallbacks[message.id](message.data); | |
delete channel.execCallbacks[message.id]; | |
} | |
this.handlePropertyUpdate = function(message) | |
{ | |
for (var i in message.data) { | |
var data = message.data[i]; | |
var object = channel.objects[data.object]; | |
if (object) { | |
object.propertyUpdate(data.signals, data.properties); | |
} else { | |
console.warn("Unhandled property update: " + data.object + "::" + data.signal); | |
} | |
} | |
channel.exec({type: QWebChannelMessageTypes.idle}); | |
} | |
// prevent multiple initialization which might happen with multiple webchannel clients. | |
this.initialized = false; | |
this.handleInit = function(message) | |
{ | |
if (channel.initialized) { | |
return; | |
} | |
channel.initialized = true; | |
for (var objectName in message.data) { | |
var data = message.data[objectName]; | |
var object = new QObject(objectName, data, channel); | |
} | |
// now unwrap properties, which might reference other registered objects | |
for (var objectName in channel.objects) { | |
channel.objects[objectName].unwrapProperties(); | |
} | |
if (initCallback) { | |
initCallback(channel); | |
} | |
channel.exec({type: QWebChannelMessageTypes.idle}); | |
} | |
this.debug = function(message) | |
{ | |
channel.send({type: QWebChannelMessageTypes.debug, data: message}); | |
}; | |
channel.exec({type: QWebChannelMessageTypes.init}); | |
}; | |
function QObject(name, data, webChannel) | |
{ | |
this.__id__ = name; | |
webChannel.objects[name] = this; | |
// List of callbacks that get invoked upon signal emission | |
this.__objectSignals__ = {}; | |
// Cache of all properties, updated when a notify signal is emitted | |
this.__propertyCache__ = {}; | |
var object = this; | |
// ---------------------------------------------------------------------- | |
this.unwrapQObject = function(response) | |
{ | |
if (response instanceof Array) { | |
// support list of objects | |
var ret = new Array(response.length); | |
for (var i = 0; i < response.length; ++i) { | |
ret[i] = object.unwrapQObject(response[i]); | |
} | |
return ret; | |
} | |
if (!response | |
|| !response["__QObject*__"] | |
|| response["id"] === undefined) { | |
return response; | |
} | |
var objectId = response.id; | |
if (webChannel.objects[objectId]) | |
return webChannel.objects[objectId]; | |
if (!response.data) { | |
console.error("Cannot unwrap unknown QObject " + objectId + " without data."); | |
return; | |
} | |
var qObject = new QObject( objectId, response.data, webChannel ); | |
qObject.destroyed.connect(function() { | |
if (webChannel.objects[objectId] === qObject) { | |
delete webChannel.objects[objectId]; | |
// reset the now deleted QObject to an empty {} object | |
// just assigning {} though would not have the desired effect, but the | |
// below also ensures all external references will see the empty map | |
// NOTE: this detour is necessary to workaround QTBUG-40021 | |
var propertyNames = []; | |
for (var propertyName in qObject) { | |
propertyNames.push(propertyName); | |
} | |
for (var idx in propertyNames) { | |
delete qObject[propertyNames[idx]]; | |
} | |
} | |
}); | |
// here we are already initialized, and thus must directly unwrap the properties | |
qObject.unwrapProperties(); | |
return qObject; | |
} | |
this.unwrapProperties = function() | |
{ | |
for (var propertyIdx in object.__propertyCache__) { | |
object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); | |
} | |
} | |
function addSignal(signalData, isPropertyNotifySignal) | |
{ | |
var signalName = signalData[0]; | |
var signalIndex = signalData[1]; | |
object[signalName] = { | |
connect: function(callback) { | |
if (typeof(callback) !== "function") { | |
console.error("Bad callback given to connect to signal " + signalName); | |
return; | |
} | |
object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; | |
object.__objectSignals__[signalIndex].push(callback); | |
if (!isPropertyNotifySignal && signalName !== "destroyed") { | |
// only required for "pure" signals, handled separately for properties in propertyUpdate | |
// also note that we always get notified about the destroyed signal | |
webChannel.exec({ | |
type: QWebChannelMessageTypes.connectToSignal, | |
object: object.__id__, | |
signal: signalIndex | |
}); | |
} | |
}, | |
disconnect: function(callback) { | |
if (typeof(callback) !== "function") { | |
console.error("Bad callback given to disconnect from signal " + signalName); | |
return; | |
} | |
object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; | |
var idx = object.__objectSignals__[signalIndex].indexOf(callback); | |
if (idx === -1) { | |
console.error("Cannot find connection of signal " + signalName + " to " + callback.name); | |
return; | |
} | |
object.__objectSignals__[signalIndex].splice(idx, 1); | |
if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { | |
// only required for "pure" signals, handled separately for properties in propertyUpdate | |
webChannel.exec({ | |
type: QWebChannelMessageTypes.disconnectFromSignal, | |
object: object.__id__, | |
signal: signalIndex | |
}); | |
} | |
} | |
}; | |
} | |
/** | |
* Invokes all callbacks for the given signalname. Also works for property notify callbacks. | |
*/ | |
function invokeSignalCallbacks(signalName, signalArgs) | |
{ | |
var connections = object.__objectSignals__[signalName]; | |
if (connections) { | |
connections.forEach(function(callback) { | |
callback.apply(callback, signalArgs); | |
}); | |
} | |
} | |
this.propertyUpdate = function(signals, propertyMap) | |
{ | |
// update property cache | |
for (var propertyIndex in propertyMap) { | |
var propertyValue = propertyMap[propertyIndex]; | |
object.__propertyCache__[propertyIndex] = propertyValue; | |
} | |
for (var signalName in signals) { | |
// Invoke all callbacks, as signalEmitted() does not. This ensures the | |
// property cache is updated before the callbacks are invoked. | |
invokeSignalCallbacks(signalName, signals[signalName]); | |
} | |
} | |
this.signalEmitted = function(signalName, signalArgs) | |
{ | |
invokeSignalCallbacks(signalName, signalArgs); | |
} | |
function addMethod(methodData) | |
{ | |
var methodName = methodData[0]; | |
var methodIdx = methodData[1]; | |
object[methodName] = function() { | |
var args = []; | |
var callback; | |
for (var i = 0; i < arguments.length; ++i) { | |
if (typeof arguments[i] === "function") | |
callback = arguments[i]; | |
else | |
args.push(arguments[i]); | |
} | |
webChannel.exec({ | |
"type": QWebChannelMessageTypes.invokeMethod, | |
"object": object.__id__, | |
"method": methodIdx, | |
"args": args | |
}, function(response) { | |
if (response !== undefined) { | |
var result = object.unwrapQObject(response); | |
if (callback) { | |
(callback)(result); | |
} | |
} | |
}); | |
}; | |
} | |
function bindGetterSetter(propertyInfo) | |
{ | |
var propertyIndex = propertyInfo[0]; | |
var propertyName = propertyInfo[1]; | |
var notifySignalData = propertyInfo[2]; | |
// initialize property cache with current value | |
// NOTE: if this is an object, it is not directly unwrapped as it might | |
// reference other QObject that we do not know yet | |
object.__propertyCache__[propertyIndex] = propertyInfo[3]; | |
if (notifySignalData) { | |
if (notifySignalData[0] === 1) { | |
// signal name is optimized away, reconstruct the actual name | |
notifySignalData[0] = propertyName + "Changed"; | |
} | |
addSignal(notifySignalData, true); | |
} | |
Object.defineProperty(object, propertyName, { | |
get: function () { | |
var propertyValue = object.__propertyCache__[propertyIndex]; | |
if (propertyValue === undefined) { | |
// This shouldn't happen | |
console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); | |
} | |
return propertyValue; | |
}, | |
set: function(value) { | |
if (value === undefined) { | |
console.warn("Property setter for " + propertyName + " called with undefined value!"); | |
return; | |
} | |
object.__propertyCache__[propertyIndex] = value; | |
webChannel.exec({ | |
"type": QWebChannelMessageTypes.setProperty, | |
"object": object.__id__, | |
"property": propertyIndex, | |
"value": value | |
}); | |
} | |
}); | |
} | |
// ---------------------------------------------------------------------- | |
data.methods.forEach(addMethod); | |
data.properties.forEach(bindGetterSetter); | |
data.signals.forEach(function(signal) { addSignal(signal, false); }); | |
for (var name in data.enums) { | |
object[name] = data.enums[name]; | |
} | |
} | |
//required for use with nodejs | |
if (typeof module === 'object') { | |
module.exports = { | |
QWebChannel: QWebChannel | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment