mirror of
https://github.com/scratchfoundation/scratchx.git
synced 2024-11-28 10:36:03 -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/underscore-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="js/scratchx.js" defer></script>
|
||||
</body>
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
//
|
||||
// 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.
|
||||
window.ScratchExtensions = new (function(){
|
||||
var pluginName = 'Scratch Device Plugin'; // will be 'Scratch Plugin for Devices'
|
||||
var pluginAvailable = (window.ActiveXObject || !!navigator.plugins[pluginName]) && !!window.ArrayBuffer;
|
||||
window.ScratchExtensions = new (function () {
|
||||
var plugin = null;
|
||||
var handlers = {};
|
||||
var blockDefs = {};
|
||||
|
@ -16,27 +14,34 @@ window.ScratchExtensions = new (function(){
|
|||
var poller = null;
|
||||
var lib = this;
|
||||
|
||||
lib.register = function(name, descriptor, handler, deviceSpec) {
|
||||
if(name in handlers) {
|
||||
console.log('Scratch extension "'+name+'" already exists!');
|
||||
var isOffline = (Scratch && Scratch.FlashApp && Scratch.FlashApp.ASobj &&
|
||||
Scratch.FlashApp.ASobj.isOffline && Scratch.FlashApp.ASobj.isOffline());
|
||||
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;
|
||||
}
|
||||
|
||||
if(deviceSpec) {
|
||||
if(!pluginAvailable && window.ActiveXObject) {
|
||||
if (deviceSpec && !plugin) {
|
||||
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.');
|
||||
}
|
||||
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;
|
||||
blockDefs[name] = descriptor.blocks;
|
||||
if(descriptor.menus) menuDefs[name] = descriptor.menus;
|
||||
if(deviceSpec) deviceSpecs[name] = deviceSpec;
|
||||
if (descriptor.menus) menuDefs[name] = descriptor.menus;
|
||||
if (deviceSpec) deviceSpecs[name] = deviceSpec;
|
||||
|
||||
// Show the blocks in Scratch!
|
||||
var extObj = {
|
||||
|
@ -51,81 +56,86 @@ window.ScratchExtensions = new (function(){
|
|||
};
|
||||
|
||||
var loadingURL;
|
||||
lib.loadExternalJS = function(url) {
|
||||
lib.loadExternalJS = function (url) {
|
||||
var scr = document.createElement("script");
|
||||
scr.src = url;// + "?ts=" + new Date().getTime();
|
||||
loadingURL = url;
|
||||
document.getElementsByTagName("head")[0].appendChild(scr);
|
||||
};
|
||||
|
||||
lib.loadLocalJS = function(code) {
|
||||
lib.loadLocalJS = function (code) {
|
||||
// Run the extension code in the global scope
|
||||
try {
|
||||
(new Function(code))();
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.log(e.stack.toString());
|
||||
}
|
||||
};
|
||||
|
||||
lib.unregister = function(name) {
|
||||
try { handlers[name]._shutdown(); } catch(e){}
|
||||
lib.unregister = function (name) {
|
||||
try { handlers[name]._shutdown(); } catch (e) { }
|
||||
delete handlers[name];
|
||||
delete blockDefs[name];
|
||||
delete menuDefs[name];
|
||||
delete deviceSpecs[name];
|
||||
};
|
||||
|
||||
lib.canAccessDevices = function() { return pluginAvailable; };
|
||||
lib.getReporter = function(ext_name, reporter, args) {
|
||||
lib.canAccessDevices = function () { return pluginAvailable(); };
|
||||
lib.getReporter = function (ext_name, reporter, args) {
|
||||
return handlers[ext_name][reporter].apply(handlers[ext_name], args);
|
||||
};
|
||||
|
||||
lib.getReporterAsync = function(ext_name, reporter, args, job_id) {
|
||||
var callback = function(retval) {
|
||||
lib.getReporterAsync = function (ext_name, reporter, args, job_id) {
|
||||
var callback = function (retval) {
|
||||
Scratch.FlashApp.ASobj.ASextensionReporterDone(ext_name, job_id, retval);
|
||||
}
|
||||
args.push(callback);
|
||||
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);
|
||||
};
|
||||
|
||||
lib.runAsync = function(ext_name, command, args, job_id) {
|
||||
var callback = function() {
|
||||
lib.runAsync = function (ext_name, command, args, job_id) {
|
||||
var callback = function () {
|
||||
Scratch.FlashApp.ASobj.ASextensionCallDone(ext_name, job_id);
|
||||
}
|
||||
args.push(callback);
|
||||
handlers[ext_name][command].apply(handlers[ext_name], args);
|
||||
};
|
||||
|
||||
lib.getStatus = function(ext_name) {
|
||||
if(!(ext_name in handlers))
|
||||
return {status: 0, msg: 'Not loaded'};
|
||||
lib.getStatus = function (ext_name) {
|
||||
if (!(ext_name in handlers))
|
||||
return { status: 0, msg: 'Not loaded' };
|
||||
|
||||
if(ext_name in deviceSpecs && !pluginAvailable)
|
||||
return {status: 0, msg: 'Missing browser plugin'};
|
||||
if (ext_name in deviceSpecs && !pluginAvailable())
|
||||
return { status: 0, msg: 'Missing browser plugin' };
|
||||
|
||||
return handlers[ext_name]._getStatus();
|
||||
};
|
||||
|
||||
lib.notify = function(text) {
|
||||
if(window.JSsetProjectBanner) JSsetProjectBanner(text);
|
||||
lib.notify = function (text) {
|
||||
if (window.JSsetProjectBanner) JSsetProjectBanner(text);
|
||||
else alert(text);
|
||||
};
|
||||
|
||||
lib.resetPlugin = function() {
|
||||
lib.resetPlugin = function () {
|
||||
if (plugin && plugin.reset) plugin.reset();
|
||||
shutdown();
|
||||
};
|
||||
|
||||
$(window).unload(function(e) {
|
||||
$(window).unload(function (e) {
|
||||
shutdown();
|
||||
});
|
||||
|
||||
function shutdown() {
|
||||
for(var extName in handlers)
|
||||
for (var extName in handlers)
|
||||
handlers[extName]._shutdown();
|
||||
handlers = {};
|
||||
stopPolling();
|
||||
|
@ -133,65 +143,88 @@ window.ScratchExtensions = new (function(){
|
|||
|
||||
function checkDevices() {
|
||||
var awaitingSpecs = {};
|
||||
for(var ext_name in deviceSpecs)
|
||||
if(!devices[ext_name]) {
|
||||
for (var ext_name in deviceSpecs)
|
||||
if (!devices[ext_name]) {
|
||||
var spec = deviceSpecs[ext_name];
|
||||
if(spec.type == 'hid') {
|
||||
if(!awaitingSpecs['hid']) awaitingSpecs['hid'] = {};
|
||||
if (spec.type == 'hid') {
|
||||
if (!awaitingSpecs['hid']) awaitingSpecs['hid'] = {};
|
||||
awaitingSpecs['hid'][spec.vendor + '_' + spec.product] = ext_name;
|
||||
}
|
||||
else if(spec.type == 'serial')
|
||||
else if (spec.type == 'serial')
|
||||
awaitingSpecs['serial'] = ext_name;
|
||||
}
|
||||
|
||||
if(awaitingSpecs['hid']) {
|
||||
var deviceList = plugin.hid_list();
|
||||
var hidList = awaitingSpecs['hid'];
|
||||
for (var i = 0; i < deviceList.length; i++) {
|
||||
var ext_name = hidList[deviceList[i]["vendor_id"] + '_' + deviceList[i]["product_id"]];
|
||||
if (ext_name)
|
||||
handlers[ext_name]._deviceConnected(new hidDevice(deviceList[i], ext_name));
|
||||
}
|
||||
if (awaitingSpecs['hid']) {
|
||||
plugin.hid_list(function (deviceList) {
|
||||
var hidList = awaitingSpecs['hid'];
|
||||
for (var i = 0; i < deviceList.length; i++) {
|
||||
var ext_name = hidList[deviceList[i]["vendor_id"] + '_' + deviceList[i]["product_id"]];
|
||||
if (ext_name)
|
||||
handlers[ext_name]._deviceConnected(new hidDevice(deviceList[i], ext_name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(awaitingSpecs['serial']) {
|
||||
if (awaitingSpecs['serial']) {
|
||||
var ext_name = awaitingSpecs['serial'];
|
||||
var deviceList = plugin.serial_list();
|
||||
for (var i = 0; i < deviceList.length; i++) {
|
||||
handlers[ext_name]._deviceConnected(new serialDevice(deviceList[i], ext_name));
|
||||
}
|
||||
plugin.serial_list(function (deviceList) {
|
||||
for (var i = 0; i < deviceList.length; i++) {
|
||||
handlers[ext_name]._deviceConnected(new serialDevice(deviceList[i], ext_name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(!shouldLookForDevices())
|
||||
if (!shouldLookForDevices())
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
function checkPolling() {
|
||||
if(poller || !shouldLookForDevices()) return;
|
||||
if (poller || !shouldLookForDevices()) return;
|
||||
|
||||
poller = setInterval(checkDevices, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if(poller) clearInterval(poller);
|
||||
if (poller) clearInterval(poller);
|
||||
poller = null;
|
||||
}
|
||||
|
||||
function shouldLookForDevices() {
|
||||
for(var ext_name in deviceSpecs)
|
||||
if(!devices[ext_name])
|
||||
for (var ext_name in deviceSpecs)
|
||||
if (!devices[ext_name])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function createDevicePlugin() {
|
||||
if(plugin) return;
|
||||
if (plugin) return;
|
||||
|
||||
var pluginContainer = document.createElement('div');
|
||||
document.getElementById('scratch').parentNode.appendChild(pluginContainer);
|
||||
pluginContainer.innerHTML = '<object type="application/x-scratchdeviceplugin" width="1" height="1" codebase="/scratchr2/static/ext/ScratchDevicePlugin.cab"> </object>';
|
||||
plugin = pluginContainer.firstChild;
|
||||
// TODO: delegate more of this to the other files
|
||||
if (isOffline) {
|
||||
// Talk to the AIR Native Extension through the offline editor's plugin emulation.
|
||||
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) {
|
||||
|
@ -205,44 +238,47 @@ window.ScratchExtensions = new (function(){
|
|||
this.info = info;
|
||||
|
||||
function disconnect() {
|
||||
setTimeout(function(){
|
||||
setTimeout(function () {
|
||||
self.close();
|
||||
handlers[ext_name]._deviceRemoved(self);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
this.open = function() {
|
||||
this.open = function (readyCallback) {
|
||||
try {
|
||||
dev = plugin.hid_open(this.id);
|
||||
if(window.ActiveXObject) dev = dev(this.id);
|
||||
dev.set_nonblocking(true);
|
||||
//devices[ext_name][path] = this;
|
||||
devices[ext_name] = this;
|
||||
plugin.hid_open(this.id, function (d) {
|
||||
dev = d;
|
||||
dev.set_nonblocking(true);
|
||||
//devices[ext_name][path] = this;
|
||||
devices[ext_name] = this;
|
||||
|
||||
if (readyCallback) readyCallback(this);
|
||||
});
|
||||
}
|
||||
catch(e) {}
|
||||
catch (e) { }
|
||||
};
|
||||
this.close = function() {
|
||||
if(!dev) return;
|
||||
this.close = function () {
|
||||
if (!dev) return;
|
||||
dev.close();
|
||||
delete devices[ext_name];
|
||||
dev = null;
|
||||
|
||||
checkPolling();
|
||||
};
|
||||
this.write = function(data) {
|
||||
if(!dev) return;
|
||||
var len = dev.write(data);
|
||||
if(window.ActiveXObject) len = len(data);
|
||||
if(len < 0) disconnect();
|
||||
return len;
|
||||
this.write = function (data, callback) {
|
||||
if (!dev) return;
|
||||
dev.write(data, function (len) {
|
||||
if (len < 0) disconnect();
|
||||
if (callback) callback(len);
|
||||
});
|
||||
};
|
||||
this.read = function(len) {
|
||||
if(!dev) return null;
|
||||
if(!len) len = 65;
|
||||
var data = dev.read(len);
|
||||
if(window.ActiveXObject) data = data(len);
|
||||
if(data.byteLength == 0) disconnect();
|
||||
return data;
|
||||
this.read = function (callback, len) {
|
||||
if (!dev) return null;
|
||||
if (!len) len = 65;
|
||||
dev.read(len, function (data) {
|
||||
if (data.byteLength == 0) disconnect();
|
||||
callback(data);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -254,32 +290,42 @@ window.ScratchExtensions = new (function(){
|
|||
//if(!(ext_name in devices)) devices[ext_name] = {};
|
||||
|
||||
this.id = id;
|
||||
this.open = function(opts) {
|
||||
this.open = function (opts, readyCallback) {
|
||||
try {
|
||||
dev = plugin.serial_open(this.id, opts);
|
||||
// dev.set_disconnect_handler(function() {
|
||||
// self.close();
|
||||
// handlers[ext_name]._deviceRemoved(self);
|
||||
// });
|
||||
//devices[ext_name][path] = this;
|
||||
devices[ext_name] = this;
|
||||
plugin.serial_open(this.id, opts, function (d) {
|
||||
// dev.set_disconnect_handler(function () {
|
||||
// self.close();
|
||||
// handlers[ext_name]._deviceRemoved(self);
|
||||
// });
|
||||
// devices[ext_name][path] = 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() {
|
||||
if(!dev) return;
|
||||
this.close = function () {
|
||||
if (!dev) return;
|
||||
dev.close();
|
||||
delete devices[ext_name];
|
||||
dev = null;
|
||||
|
||||
checkPolling();
|
||||
};
|
||||
this.send = function(data) {
|
||||
if(!dev) return;
|
||||
this.send = function (data) {
|
||||
if (!dev) return;
|
||||
dev.send(data);
|
||||
};
|
||||
this.set_receive_handler = function(handler) {
|
||||
if(!dev) return;
|
||||
this.set_receive_handler = function (handler) {
|
||||
if (!dev) return;
|
||||
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