mirror of
https://github.com/scratchfoundation/scratchx.git
synced 2024-11-28 18:45:49 -05:00
Updated extension interface JS from main site
This should allow communication with the latest version of the Scratch Device Plugin, including the Scratch Device Plugin for Chrome.
This commit is contained in:
parent
ddb0f9bb6e
commit
1f61e1ab3e
5 changed files with 524 additions and 104 deletions
|
@ -285,6 +285,9 @@
|
||||||
<script type="text/javascript" src="libs/jquery-1.11.2.min.js"></script>
|
<script type="text/javascript" src="libs/jquery-1.11.2.min.js"></script>
|
||||||
<script type="text/javascript" src="libs/underscore-min.js" async></script>
|
<script type="text/javascript" src="libs/underscore-min.js" async></script>
|
||||||
<script type="text/javascript" src="libs/ZeroClipboard.min.js" async></script>
|
<script type="text/javascript" src="libs/ZeroClipboard.min.js" async></script>
|
||||||
|
<script type="text/javascript" src="scratch_extensions/scratch_proxies.js" defer></script>
|
||||||
|
<script type="text/javascript" src="scratch_extensions/scratch_plugin.js" defer></script>
|
||||||
|
<script type="text/javascript" src="scratch_extensions/scratch_nmh.js" defer></script>
|
||||||
<script type="text/javascript" src="scratch_extensions/scratch_ext.js" defer></script>
|
<script type="text/javascript" src="scratch_extensions/scratch_ext.js" defer></script>
|
||||||
<script type="text/javascript" src="js/scratchx.js" defer></script>
|
<script type="text/javascript" src="js/scratchx.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
//
|
//
|
||||||
// Scratch 2.0 extension manager which Scratch communicates with to initialize extensions and communicate with them.
|
// Scratch 2.0 extension manager which Scratch communicates with to initialize extensions and communicate with them.
|
||||||
// The extension manager also handles creating the browser plugin to enable access to HID and serial devices.
|
// The extension manager also handles creating the browser plugin to enable access to HID and serial devices.
|
||||||
window.ScratchExtensions = new (function(){
|
window.ScratchExtensions = new (function () {
|
||||||
var pluginName = 'Scratch Device Plugin'; // will be 'Scratch Plugin for Devices'
|
|
||||||
var pluginAvailable = (window.ActiveXObject || !!navigator.plugins[pluginName]) && !!window.ArrayBuffer;
|
|
||||||
var plugin = null;
|
var plugin = null;
|
||||||
var handlers = {};
|
var handlers = {};
|
||||||
var blockDefs = {};
|
var blockDefs = {};
|
||||||
|
@ -16,27 +14,34 @@ window.ScratchExtensions = new (function(){
|
||||||
var poller = null;
|
var poller = null;
|
||||||
var lib = this;
|
var lib = this;
|
||||||
|
|
||||||
lib.register = function(name, descriptor, handler, deviceSpec) {
|
var isOffline = (Scratch && Scratch.FlashApp && Scratch.FlashApp.ASobj &&
|
||||||
if(name in handlers) {
|
Scratch.FlashApp.ASobj.isOffline && Scratch.FlashApp.ASobj.isOffline());
|
||||||
console.log('Scratch extension "'+name+'" already exists!');
|
var pluginAvailable = function () {
|
||||||
|
return !!window.ArrayBuffer && !!(
|
||||||
|
isOffline ||
|
||||||
|
(window.ScratchPlugin && window.ScratchPlugin.isAvailable()) ||
|
||||||
|
(window.ScratchDeviceHost && window.ScratchDeviceHost.isAvailable())
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
lib.register = function (name, descriptor, handler, deviceSpec) {
|
||||||
|
if (name in handlers) {
|
||||||
|
console.log('Scratch extension "' + name + '" already exists!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(deviceSpec) {
|
if (deviceSpec && !plugin) {
|
||||||
if(!pluginAvailable && window.ActiveXObject) {
|
if (pluginAvailable()) {
|
||||||
|
setTimeout(createDevicePlugin, 10);
|
||||||
|
} else if (window.ScratchPlugin.useActiveX) {
|
||||||
JSsetProjectBanner('Sorry, your version of Internet Explorer is not supported. Please upgrade to version 10 or 11.');
|
JSsetProjectBanner('Sorry, your version of Internet Explorer is not supported. Please upgrade to version 10 or 11.');
|
||||||
}
|
}
|
||||||
if(pluginAvailable && !plugin) setTimeout(createDevicePlugin, 10);
|
|
||||||
|
|
||||||
// Wait a moment to access the plugin and claim any devices that plugins are
|
|
||||||
// interested in.
|
|
||||||
setTimeout(checkPolling, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers[name] = handler;
|
handlers[name] = handler;
|
||||||
blockDefs[name] = descriptor.blocks;
|
blockDefs[name] = descriptor.blocks;
|
||||||
if(descriptor.menus) menuDefs[name] = descriptor.menus;
|
if (descriptor.menus) menuDefs[name] = descriptor.menus;
|
||||||
if(deviceSpec) deviceSpecs[name] = deviceSpec;
|
if (deviceSpec) deviceSpecs[name] = deviceSpec;
|
||||||
|
|
||||||
// Show the blocks in Scratch!
|
// Show the blocks in Scratch!
|
||||||
var extObj = {
|
var extObj = {
|
||||||
|
@ -51,81 +56,86 @@ window.ScratchExtensions = new (function(){
|
||||||
};
|
};
|
||||||
|
|
||||||
var loadingURL;
|
var loadingURL;
|
||||||
lib.loadExternalJS = function(url) {
|
lib.loadExternalJS = function (url) {
|
||||||
var scr = document.createElement("script");
|
var scr = document.createElement("script");
|
||||||
scr.src = url;// + "?ts=" + new Date().getTime();
|
scr.src = url;// + "?ts=" + new Date().getTime();
|
||||||
loadingURL = url;
|
loadingURL = url;
|
||||||
document.getElementsByTagName("head")[0].appendChild(scr);
|
document.getElementsByTagName("head")[0].appendChild(scr);
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.loadLocalJS = function(code) {
|
lib.loadLocalJS = function (code) {
|
||||||
// Run the extension code in the global scope
|
// Run the extension code in the global scope
|
||||||
try {
|
try {
|
||||||
(new Function(code))();
|
(new Function(code))();
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.log(e.stack.toString());
|
console.log(e.stack.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.unregister = function(name) {
|
lib.unregister = function (name) {
|
||||||
try { handlers[name]._shutdown(); } catch(e){}
|
try { handlers[name]._shutdown(); } catch (e) { }
|
||||||
delete handlers[name];
|
delete handlers[name];
|
||||||
delete blockDefs[name];
|
delete blockDefs[name];
|
||||||
delete menuDefs[name];
|
delete menuDefs[name];
|
||||||
delete deviceSpecs[name];
|
delete deviceSpecs[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.canAccessDevices = function() { return pluginAvailable; };
|
lib.canAccessDevices = function () { return pluginAvailable(); };
|
||||||
lib.getReporter = function(ext_name, reporter, args) {
|
lib.getReporter = function (ext_name, reporter, args) {
|
||||||
return handlers[ext_name][reporter].apply(handlers[ext_name], args);
|
return handlers[ext_name][reporter].apply(handlers[ext_name], args);
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.getReporterAsync = function(ext_name, reporter, args, job_id) {
|
lib.getReporterAsync = function (ext_name, reporter, args, job_id) {
|
||||||
var callback = function(retval) {
|
var callback = function (retval) {
|
||||||
Scratch.FlashApp.ASobj.ASextensionReporterDone(ext_name, job_id, retval);
|
Scratch.FlashApp.ASobj.ASextensionReporterDone(ext_name, job_id, retval);
|
||||||
}
|
}
|
||||||
args.push(callback);
|
args.push(callback);
|
||||||
handlers[ext_name][reporter].apply(handlers[ext_name], args);
|
handlers[ext_name][reporter].apply(handlers[ext_name], args);
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.runCommand = function(ext_name, command, args) {
|
lib.getReporterForceAsync = function (ext_name, reporter, args, job_id) {
|
||||||
|
var retval = handlers[ext_name][reporter].apply(handlers[ext_name], args);
|
||||||
|
Scratch.FlashApp.ASobj.ASextensionReporterDone(ext_name, job_id, retval);
|
||||||
|
};
|
||||||
|
|
||||||
|
lib.runCommand = function (ext_name, command, args) {
|
||||||
handlers[ext_name][command].apply(handlers[ext_name], args);
|
handlers[ext_name][command].apply(handlers[ext_name], args);
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.runAsync = function(ext_name, command, args, job_id) {
|
lib.runAsync = function (ext_name, command, args, job_id) {
|
||||||
var callback = function() {
|
var callback = function () {
|
||||||
Scratch.FlashApp.ASobj.ASextensionCallDone(ext_name, job_id);
|
Scratch.FlashApp.ASobj.ASextensionCallDone(ext_name, job_id);
|
||||||
}
|
}
|
||||||
args.push(callback);
|
args.push(callback);
|
||||||
handlers[ext_name][command].apply(handlers[ext_name], args);
|
handlers[ext_name][command].apply(handlers[ext_name], args);
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.getStatus = function(ext_name) {
|
lib.getStatus = function (ext_name) {
|
||||||
if(!(ext_name in handlers))
|
if (!(ext_name in handlers))
|
||||||
return {status: 0, msg: 'Not loaded'};
|
return { status: 0, msg: 'Not loaded' };
|
||||||
|
|
||||||
if(ext_name in deviceSpecs && !pluginAvailable)
|
if (ext_name in deviceSpecs && !pluginAvailable())
|
||||||
return {status: 0, msg: 'Missing browser plugin'};
|
return { status: 0, msg: 'Missing browser plugin' };
|
||||||
|
|
||||||
return handlers[ext_name]._getStatus();
|
return handlers[ext_name]._getStatus();
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.notify = function(text) {
|
lib.notify = function (text) {
|
||||||
if(window.JSsetProjectBanner) JSsetProjectBanner(text);
|
if (window.JSsetProjectBanner) JSsetProjectBanner(text);
|
||||||
else alert(text);
|
else alert(text);
|
||||||
};
|
};
|
||||||
|
|
||||||
lib.resetPlugin = function() {
|
lib.resetPlugin = function () {
|
||||||
if (plugin && plugin.reset) plugin.reset();
|
if (plugin && plugin.reset) plugin.reset();
|
||||||
shutdown();
|
shutdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
$(window).unload(function(e) {
|
$(window).unload(function (e) {
|
||||||
shutdown();
|
shutdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
function shutdown() {
|
function shutdown() {
|
||||||
for(var extName in handlers)
|
for (var extName in handlers)
|
||||||
handlers[extName]._shutdown();
|
handlers[extName]._shutdown();
|
||||||
handlers = {};
|
handlers = {};
|
||||||
stopPolling();
|
stopPolling();
|
||||||
|
@ -133,65 +143,88 @@ window.ScratchExtensions = new (function(){
|
||||||
|
|
||||||
function checkDevices() {
|
function checkDevices() {
|
||||||
var awaitingSpecs = {};
|
var awaitingSpecs = {};
|
||||||
for(var ext_name in deviceSpecs)
|
for (var ext_name in deviceSpecs)
|
||||||
if(!devices[ext_name]) {
|
if (!devices[ext_name]) {
|
||||||
var spec = deviceSpecs[ext_name];
|
var spec = deviceSpecs[ext_name];
|
||||||
if(spec.type == 'hid') {
|
if (spec.type == 'hid') {
|
||||||
if(!awaitingSpecs['hid']) awaitingSpecs['hid'] = {};
|
if (!awaitingSpecs['hid']) awaitingSpecs['hid'] = {};
|
||||||
awaitingSpecs['hid'][spec.vendor + '_' + spec.product] = ext_name;
|
awaitingSpecs['hid'][spec.vendor + '_' + spec.product] = ext_name;
|
||||||
}
|
}
|
||||||
else if(spec.type == 'serial')
|
else if (spec.type == 'serial')
|
||||||
awaitingSpecs['serial'] = ext_name;
|
awaitingSpecs['serial'] = ext_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(awaitingSpecs['hid']) {
|
if (awaitingSpecs['hid']) {
|
||||||
var deviceList = plugin.hid_list();
|
plugin.hid_list(function (deviceList) {
|
||||||
var hidList = awaitingSpecs['hid'];
|
var hidList = awaitingSpecs['hid'];
|
||||||
for (var i = 0; i < deviceList.length; i++) {
|
for (var i = 0; i < deviceList.length; i++) {
|
||||||
var ext_name = hidList[deviceList[i]["vendor_id"] + '_' + deviceList[i]["product_id"]];
|
var ext_name = hidList[deviceList[i]["vendor_id"] + '_' + deviceList[i]["product_id"]];
|
||||||
if (ext_name)
|
if (ext_name)
|
||||||
handlers[ext_name]._deviceConnected(new hidDevice(deviceList[i], ext_name));
|
handlers[ext_name]._deviceConnected(new hidDevice(deviceList[i], ext_name));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(awaitingSpecs['serial']) {
|
if (awaitingSpecs['serial']) {
|
||||||
var ext_name = awaitingSpecs['serial'];
|
var ext_name = awaitingSpecs['serial'];
|
||||||
var deviceList = plugin.serial_list();
|
plugin.serial_list(function (deviceList) {
|
||||||
for (var i = 0; i < deviceList.length; i++) {
|
for (var i = 0; i < deviceList.length; i++) {
|
||||||
handlers[ext_name]._deviceConnected(new serialDevice(deviceList[i], ext_name));
|
handlers[ext_name]._deviceConnected(new serialDevice(deviceList[i], ext_name));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!shouldLookForDevices())
|
if (!shouldLookForDevices())
|
||||||
stopPolling();
|
stopPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPolling() {
|
function checkPolling() {
|
||||||
if(poller || !shouldLookForDevices()) return;
|
if (poller || !shouldLookForDevices()) return;
|
||||||
|
|
||||||
poller = setInterval(checkDevices, 500);
|
poller = setInterval(checkDevices, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPolling() {
|
function stopPolling() {
|
||||||
if(poller) clearInterval(poller);
|
if (poller) clearInterval(poller);
|
||||||
poller = null;
|
poller = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldLookForDevices() {
|
function shouldLookForDevices() {
|
||||||
for(var ext_name in deviceSpecs)
|
for (var ext_name in deviceSpecs)
|
||||||
if(!devices[ext_name])
|
if (!devices[ext_name])
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDevicePlugin() {
|
function createDevicePlugin() {
|
||||||
if(plugin) return;
|
if (plugin) return;
|
||||||
|
|
||||||
var pluginContainer = document.createElement('div');
|
// TODO: delegate more of this to the other files
|
||||||
document.getElementById('scratch').parentNode.appendChild(pluginContainer);
|
if (isOffline) {
|
||||||
pluginContainer.innerHTML = '<object type="application/x-scratchdeviceplugin" width="1" height="1" codebase="/scratchr2/static/ext/ScratchDevicePlugin.cab"> </object>';
|
// Talk to the AIR Native Extension through the offline editor's plugin emulation.
|
||||||
plugin = pluginContainer.firstChild;
|
plugin = Scratch.FlashApp.ASobj.getPlugin();
|
||||||
|
} else if (window.ScratchDeviceHost && window.ScratchDeviceHost.isAvailable()) {
|
||||||
|
// Talk to the Native Messaging Host through a Chrome extension.
|
||||||
|
plugin = window.ScratchDeviceHost;
|
||||||
|
} else {
|
||||||
|
if (window.ScratchPlugin.useActiveX) {
|
||||||
|
// we must be on IE or similar
|
||||||
|
plugin = new ActiveXObject(window.ScratchPlugin.axObjectName);
|
||||||
|
} else {
|
||||||
|
// Not IE: try NPAPI
|
||||||
|
var pluginContainer = document.createElement('div');
|
||||||
|
document.getElementById('scratch').parentNode.appendChild(pluginContainer);
|
||||||
|
pluginContainer.innerHTML = '<object type="application/x-scratchdeviceplugin" width="1" height="1"> </object>';
|
||||||
|
plugin = pluginContainer.firstChild;
|
||||||
|
}
|
||||||
|
// Talk to the actual plugin, but make it pretend to be asynchronous.
|
||||||
|
plugin = new window.ScratchPlugin.PluginWrapper(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a moment to access the plugin and claim any devices that plugins are
|
||||||
|
// interested in.
|
||||||
|
setTimeout(checkPolling, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidDevice(info, ext_name) {
|
function hidDevice(info, ext_name) {
|
||||||
|
@ -205,44 +238,47 @@ window.ScratchExtensions = new (function(){
|
||||||
this.info = info;
|
this.info = info;
|
||||||
|
|
||||||
function disconnect() {
|
function disconnect() {
|
||||||
setTimeout(function(){
|
setTimeout(function () {
|
||||||
self.close();
|
self.close();
|
||||||
handlers[ext_name]._deviceRemoved(self);
|
handlers[ext_name]._deviceRemoved(self);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.open = function() {
|
this.open = function (readyCallback) {
|
||||||
try {
|
try {
|
||||||
dev = plugin.hid_open(this.id);
|
plugin.hid_open(this.id, function (d) {
|
||||||
if(window.ActiveXObject) dev = dev(this.id);
|
dev = d;
|
||||||
dev.set_nonblocking(true);
|
dev.set_nonblocking(true);
|
||||||
//devices[ext_name][path] = this;
|
//devices[ext_name][path] = this;
|
||||||
devices[ext_name] = this;
|
devices[ext_name] = this;
|
||||||
|
|
||||||
|
if (readyCallback) readyCallback(this);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch(e) {}
|
catch (e) { }
|
||||||
};
|
};
|
||||||
this.close = function() {
|
this.close = function () {
|
||||||
if(!dev) return;
|
if (!dev) return;
|
||||||
dev.close();
|
dev.close();
|
||||||
delete devices[ext_name];
|
delete devices[ext_name];
|
||||||
dev = null;
|
dev = null;
|
||||||
|
|
||||||
checkPolling();
|
checkPolling();
|
||||||
};
|
};
|
||||||
this.write = function(data) {
|
this.write = function (data, callback) {
|
||||||
if(!dev) return;
|
if (!dev) return;
|
||||||
var len = dev.write(data);
|
dev.write(data, function (len) {
|
||||||
if(window.ActiveXObject) len = len(data);
|
if (len < 0) disconnect();
|
||||||
if(len < 0) disconnect();
|
if (callback) callback(len);
|
||||||
return len;
|
});
|
||||||
};
|
};
|
||||||
this.read = function(len) {
|
this.read = function (callback, len) {
|
||||||
if(!dev) return null;
|
if (!dev) return null;
|
||||||
if(!len) len = 65;
|
if (!len) len = 65;
|
||||||
var data = dev.read(len);
|
dev.read(len, function (data) {
|
||||||
if(window.ActiveXObject) data = data(len);
|
if (data.byteLength == 0) disconnect();
|
||||||
if(data.byteLength == 0) disconnect();
|
callback(data);
|
||||||
return data;
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,32 +290,42 @@ window.ScratchExtensions = new (function(){
|
||||||
//if(!(ext_name in devices)) devices[ext_name] = {};
|
//if(!(ext_name in devices)) devices[ext_name] = {};
|
||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.open = function(opts) {
|
this.open = function (opts, readyCallback) {
|
||||||
try {
|
try {
|
||||||
dev = plugin.serial_open(this.id, opts);
|
plugin.serial_open(this.id, opts, function (d) {
|
||||||
// dev.set_disconnect_handler(function() {
|
// dev.set_disconnect_handler(function () {
|
||||||
// self.close();
|
// self.close();
|
||||||
// handlers[ext_name]._deviceRemoved(self);
|
// handlers[ext_name]._deviceRemoved(self);
|
||||||
// });
|
// });
|
||||||
//devices[ext_name][path] = this;
|
// devices[ext_name][path] = this;
|
||||||
devices[ext_name] = this;
|
dev = d;
|
||||||
|
devices[ext_name] = this;
|
||||||
|
|
||||||
|
dev.set_error_handler(function (message) {
|
||||||
|
alert('Serial device error\n\nDevice: ' + id + '\nError: ' + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (readyCallback) readyCallback(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log('Error opening serial device ' + id + ': ' + e);
|
||||||
}
|
}
|
||||||
catch(e) {}
|
|
||||||
};
|
};
|
||||||
this.close = function() {
|
this.close = function () {
|
||||||
if(!dev) return;
|
if (!dev) return;
|
||||||
dev.close();
|
dev.close();
|
||||||
delete devices[ext_name];
|
delete devices[ext_name];
|
||||||
dev = null;
|
dev = null;
|
||||||
|
|
||||||
checkPolling();
|
checkPolling();
|
||||||
};
|
};
|
||||||
this.send = function(data) {
|
this.send = function (data) {
|
||||||
if(!dev) return;
|
if (!dev) return;
|
||||||
dev.send(data);
|
dev.send(data);
|
||||||
};
|
};
|
||||||
this.set_receive_handler = function(handler) {
|
this.set_receive_handler = function (handler) {
|
||||||
if(!dev) return;
|
if (!dev) return;
|
||||||
dev.set_receive_handler(handler);
|
dev.set_receive_handler(handler);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
184
scratch_extensions/scratch_nmh.js
Normal file
184
scratch_extensions/scratch_nmh.js
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
// Communicate with the Scratch Native Messaging Host through an extension.
|
||||||
|
window.ScratchDeviceHost = new (function () {
|
||||||
|
var self = this;
|
||||||
|
var isConnected = false;
|
||||||
|
|
||||||
|
self.isAvailable = function () {
|
||||||
|
return isConnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!window.chrome) return;
|
||||||
|
|
||||||
|
var extensionID = 'clmabinlolakdafkoajkfjjengcdmnpm';
|
||||||
|
var callNumber = 0;
|
||||||
|
var port = chrome.runtime.connect(extensionID);
|
||||||
|
console.assert(port, "Failed to create port");
|
||||||
|
|
||||||
|
var messageHandlers = {};
|
||||||
|
port.onMessage.addListener(function (message) {
|
||||||
|
var messageName = message[0];
|
||||||
|
if (messageName == "@") {
|
||||||
|
var callbackToken = message[1];
|
||||||
|
var returnValue = message[2];
|
||||||
|
var callback = pendingCallbacks[callbackToken];
|
||||||
|
delete pendingCallbacks[callbackToken];
|
||||||
|
if (callback) callback(returnValue);
|
||||||
|
} else {
|
||||||
|
var handler = messageHandlers[messageName];
|
||||||
|
if (handler) {
|
||||||
|
handler(message);
|
||||||
|
} else {
|
||||||
|
console.log("SDH-Page: Unrecognized message " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
messageHandlers["serialRecv"] = function (message) {
|
||||||
|
var path = message[1];
|
||||||
|
var data = message[2];
|
||||||
|
|
||||||
|
var device = serialDevices[path];
|
||||||
|
if (device && device.receiveHandler) {
|
||||||
|
device.receiveHandler(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
messageHandlers["serialError"] = function (message) {
|
||||||
|
var path = message[1];
|
||||||
|
var errorMessage = message[2];
|
||||||
|
|
||||||
|
var device = serialDevices[path];
|
||||||
|
if (device && device.errorHandler) {
|
||||||
|
device.errorHandler(errorMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var pendingCallbacks = {};
|
||||||
|
function sendMessage(message, callback) {
|
||||||
|
var callbackToken = (callNumber++).toString();
|
||||||
|
pendingCallbacks[callbackToken] = callback;
|
||||||
|
port.postMessage([callbackToken, message]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(["version"], function (version) {
|
||||||
|
isConnected = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
self.hid_list = function (callback, opt_vendorID, opt_productID) {
|
||||||
|
var message = ["hid_list", opt_vendorID || 0, opt_productID || 0];
|
||||||
|
sendMessage(message, function (deviceList) {
|
||||||
|
if (callback) callback(deviceList);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.hid_open = function (path, callback) {
|
||||||
|
var message = ["hid_open_raw", path];
|
||||||
|
sendMessage(message, function (result) {
|
||||||
|
var device;
|
||||||
|
if (result) {
|
||||||
|
device = new HidDevice(path);
|
||||||
|
ScratchProxies.AddHidProxies(device);
|
||||||
|
var claimMessage = ["claim", path];
|
||||||
|
sendMessage(claimMessage);
|
||||||
|
}
|
||||||
|
if (callback) callback(device);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.serial_list = function (callback) {
|
||||||
|
sendMessage(["serial_list"], function (deviceList) {
|
||||||
|
if (callback) callback(deviceList);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.serial_open = function (path, opts, callback) {
|
||||||
|
var message = ["serial_open_raw", path];
|
||||||
|
if (opts) message.push(opts);
|
||||||
|
sendMessage(message, function (result) {
|
||||||
|
var device;
|
||||||
|
if (result) {
|
||||||
|
device = new SerialDevice(path);
|
||||||
|
ScratchProxies.AddSerialProxies(device);
|
||||||
|
var claimMessage = ["claim", path];
|
||||||
|
sendMessage(claimMessage);
|
||||||
|
}
|
||||||
|
if (callback) callback(device);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.reset = function () {
|
||||||
|
sendMessage(["reset"]);
|
||||||
|
};
|
||||||
|
self.version = function (callback) {
|
||||||
|
sendMessage(["version"], function (result) {
|
||||||
|
if (callback) callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function HidDevice(path) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.write_raw = function (arrayBuffer, callback) {
|
||||||
|
var message = ["write_raw", path, arrayBuffer];
|
||||||
|
sendMessage(message, function (result) {
|
||||||
|
if (callback) callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.send_feature_report_raw = function (arrayBuffer, callback) {
|
||||||
|
var message = ["send_feature_report_raw", path, arrayBuffer];
|
||||||
|
sendMessage(message, function (result) {
|
||||||
|
if (callback) callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.read_raw = function (size, callback) {
|
||||||
|
var message = ["read_raw", path, size];
|
||||||
|
sendMessage(message, function (data) {
|
||||||
|
if (callback) callback(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.get_feature_report_raw = function (size, callback) {
|
||||||
|
var message = ["get_feature_report_raw", path, size];
|
||||||
|
sendMessage(message, function (data) {
|
||||||
|
if (callback) callback(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.set_nonblocking = function (flag, callback) {
|
||||||
|
var message = ["set_nonblocking", path, flag];
|
||||||
|
sendMessage(message, function (result) {
|
||||||
|
if (callback) callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.close = function () {
|
||||||
|
sendMessage(["close", path]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var serialDevices = {}; // path -> SerialDevice
|
||||||
|
function SerialDevice(path) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.receiveHandler = undefined;
|
||||||
|
self.errorHandler = undefined;
|
||||||
|
|
||||||
|
serialDevices[path] = self;
|
||||||
|
|
||||||
|
self.send_raw = function (data) {
|
||||||
|
var message = ["serial_send_raw", path, data];
|
||||||
|
sendMessage(message);
|
||||||
|
};
|
||||||
|
self.close = function () {
|
||||||
|
var message = ["serial_close", path];
|
||||||
|
sendMessage(message);
|
||||||
|
};
|
||||||
|
self.is_open = function (callback) {
|
||||||
|
var message = ["serial_is_open", path];
|
||||||
|
sendMessage(message, function (result) {
|
||||||
|
if (callback) callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.set_receive_handler_raw = function (callback) {
|
||||||
|
self.receiveHandler = callback;
|
||||||
|
var message = ["serial_recv_start", path];
|
||||||
|
sendMessage(message);
|
||||||
|
};
|
||||||
|
self.set_error_handler = function (callback) {
|
||||||
|
self.errorHandler = callback;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
96
scratch_extensions/scratch_plugin.js
Normal file
96
scratch_extensions/scratch_plugin.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
window.ScratchPlugin = new (function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var pluginName = 'Scratch Device Plugin'; // will be 'Scratch Plugin for Devices'
|
||||||
|
self.useActiveX = window.hasOwnProperty('ActiveXObject');
|
||||||
|
self.axObjectName = 'MITMediaLab.ScratchDevicePlugin'; // name of ActiveX object
|
||||||
|
self.isAvailable = function () {
|
||||||
|
return !!(self.useActiveX || navigator.plugins[pluginName]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// These wrappers make the plugin act asynchronous, matching the API found in AIR and NMH.
|
||||||
|
// The one difference is that callbacks are triggered before the initiating call returns.
|
||||||
|
self.PluginWrapper = function (plugin) {
|
||||||
|
var self = this;
|
||||||
|
self.hid_list = function (callback, opt_vendorID, opt_productID) {
|
||||||
|
var deviceList = plugin.hid_list(opt_vendorID, opt_productID);
|
||||||
|
if (callback) callback(deviceList);
|
||||||
|
};
|
||||||
|
self.hid_open = function (path, callback) {
|
||||||
|
var device = plugin.hid_open_raw(path);
|
||||||
|
if (device) {
|
||||||
|
device = new HidWrapper(device);
|
||||||
|
ScratchProxies.AddHidProxies(device);
|
||||||
|
}
|
||||||
|
if (callback) callback(device);
|
||||||
|
};
|
||||||
|
self.serial_list = function (callback) {
|
||||||
|
var deviceList = plugin.serial_list();
|
||||||
|
if (callback) callback(deviceList);
|
||||||
|
};
|
||||||
|
self.serial_open = function (path, opts, callback) {
|
||||||
|
var device = plugin.serial_open_raw(path, opts);
|
||||||
|
if (device) {
|
||||||
|
device = new SerialWrapper(device);
|
||||||
|
ScratchProxies.AddSerialProxies(device);
|
||||||
|
}
|
||||||
|
if (callback) callback(device);
|
||||||
|
};
|
||||||
|
self.reset = function () {
|
||||||
|
plugin.reset();
|
||||||
|
};
|
||||||
|
self.version = function (callback) {
|
||||||
|
var result = plugin.version();
|
||||||
|
if (callback) callback(result);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function HidWrapper(device) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.write_raw = function (arrayBuffer, callback) {
|
||||||
|
var result = device.write_raw(arrayBuffer);
|
||||||
|
if (callback) callback(result);
|
||||||
|
};
|
||||||
|
self.send_feature_report_raw = function (arrayBuffer, callback) {
|
||||||
|
var result = device.send_feature_report_raw(arrayBuffer);
|
||||||
|
if (callback) callback(result);
|
||||||
|
};
|
||||||
|
self.read_raw = function (size, callback) {
|
||||||
|
var data = device.read_raw(size);
|
||||||
|
if (callback) callback(data);
|
||||||
|
};
|
||||||
|
self.get_feature_report_raw = function (size, callback) {
|
||||||
|
var data = device.get_feature_report_raw(size);
|
||||||
|
if (callback) callback(data);
|
||||||
|
};
|
||||||
|
self.set_nonblocking = function (flag, callback) {
|
||||||
|
var result = device.set_nonblocking(flag);
|
||||||
|
if (callback) callback(result);
|
||||||
|
};
|
||||||
|
self.close = function () {
|
||||||
|
device.close();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function SerialWrapper(device) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.send_raw = function (data) {
|
||||||
|
device.send_raw(data);
|
||||||
|
};
|
||||||
|
self.close = function () {
|
||||||
|
device.close();
|
||||||
|
};
|
||||||
|
self.is_open = function (callback) {
|
||||||
|
var result = device.is_open();
|
||||||
|
if (callback) callback(result);
|
||||||
|
};
|
||||||
|
self.set_receive_handler_raw = function (callback) {
|
||||||
|
device.set_receive_handler_raw(callback);
|
||||||
|
};
|
||||||
|
self.set_error_handler = function (callback) {
|
||||||
|
device.set_error_handler(callback);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})();
|
91
scratch_extensions/scratch_proxies.js
Normal file
91
scratch_extensions/scratch_proxies.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// Extend hardware devices with some hardware-API-agnostic data conversion wrappers
|
||||||
|
window.ScratchProxies = new (function () {
|
||||||
|
var self = this;
|
||||||
|
var charsBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
|
||||||
|
function ab_to_b64(arraybuffer) {
|
||||||
|
var bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = '';
|
||||||
|
|
||||||
|
for (i = 0; i < len; i += 3) {
|
||||||
|
base64 += charsBase64[bytes[i] >> 2];
|
||||||
|
base64 += charsBase64[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
|
||||||
|
base64 += charsBase64[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
|
||||||
|
base64 += charsBase64[bytes[i + 2] & 63];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((len % 3) === 2) {
|
||||||
|
base64 = base64.substring(0, base64.length - 1) + '=';
|
||||||
|
} else if (len % 3 === 1) {
|
||||||
|
base64 = base64.substring(0, base64.length - 2) + '==';
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
function b64_to_ab(base64) {
|
||||||
|
var bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;
|
||||||
|
if (base64[base64.length - 1] === '=') {
|
||||||
|
--bufferLength;
|
||||||
|
if (base64[base64.length - 2] === '=') {
|
||||||
|
--bufferLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);
|
||||||
|
for (i = 0; i < len; i += 4) {
|
||||||
|
encoded1 = charsBase64.indexOf(base64[i]);
|
||||||
|
encoded2 = charsBase64.indexOf(base64[i + 1]);
|
||||||
|
encoded3 = charsBase64.indexOf(base64[i + 2]);
|
||||||
|
encoded4 = charsBase64.indexOf(base64[i + 3]);
|
||||||
|
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
|
||||||
|
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
||||||
|
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arraybuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.AddHidProxies = function (device) {
|
||||||
|
device.write = function (arrayBuffer, callback) {
|
||||||
|
var bufferBase64 = ab_to_b64(arrayBuffer);
|
||||||
|
device.write_raw(bufferBase64, callback);
|
||||||
|
};
|
||||||
|
device.send_feature_report = function (arrayBuffer, callback) {
|
||||||
|
var bufferBase64 = ab_to_b64(arrayBuffer);
|
||||||
|
device.send_feature_report_raw(bufferBase64, callback);
|
||||||
|
};
|
||||||
|
device.read = function (size, callback) {
|
||||||
|
device.read_raw(size, function (data) {
|
||||||
|
if (callback) {
|
||||||
|
data = b64_to_ab(data);
|
||||||
|
callback(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
device.get_feature_report = function (size, callback) {
|
||||||
|
device.get_feature_report_raw(size, function (data) {
|
||||||
|
if (callback) {
|
||||||
|
data = b64_to_ab(data);
|
||||||
|
callback(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
self.AddSerialProxies = function (device) {
|
||||||
|
device.send = function (arrayBuffer, callback) {
|
||||||
|
var bufferBase64 = ab_to_b64(arrayBuffer);
|
||||||
|
device.send_raw(bufferBase64, function (result) {
|
||||||
|
if (callback) callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
device.set_receive_handler = function (callback) {
|
||||||
|
device.set_receive_handler_raw(function (data) {
|
||||||
|
if (callback) {
|
||||||
|
data = b64_to_ab(data);
|
||||||
|
callback(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})();
|
Loading…
Reference in a new issue