2400 lines
69 KiB
JavaScript
2400 lines
69 KiB
JavaScript
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
//download.js v3.1, by dandavis; 2008-2014. [CCBY2] see http://danml.com/download.html for tests/usage
|
|
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
|
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
|
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
|
|
|
|
// https://github.com/rndme/download
|
|
|
|
// data can be a string, Blob, File, or dataURL
|
|
function download(data, strFileName, strMimeType) {
|
|
|
|
var self = window, // this script is only for browsers anyway...
|
|
u = "application/octet-stream", // this default mime also triggers iframe downloads
|
|
m = strMimeType || u,
|
|
x = data,
|
|
D = document,
|
|
a = D.createElement("a"),
|
|
z = function(a){return String(a);},
|
|
B = (self.Blob || self.MozBlob || self.WebKitBlob || z);
|
|
B=B.call ? B.bind(self) : Blob ;
|
|
var fn = strFileName || "download",
|
|
blob,
|
|
fr;
|
|
|
|
|
|
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
|
x=[x, m];
|
|
m=x[0];
|
|
x=x[1];
|
|
}
|
|
|
|
//go ahead and download dataURLs right away
|
|
if(String(x).match(/^data\:[\w+\-]+\/[\w+\-]+[,;]/)){
|
|
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
|
|
navigator.msSaveBlob(d2b(x), fn) :
|
|
saver(x) ; // everyone else can save dataURLs un-processed
|
|
}//end if dataURL passed?
|
|
|
|
blob = x instanceof B ?
|
|
x :
|
|
new B([x], {type: m}) ;
|
|
|
|
|
|
function d2b(u) {
|
|
var p= u.split(/[:;,]/),
|
|
t= p[1],
|
|
dec= p[2] == "base64" ? atob : decodeURIComponent,
|
|
bin= dec(p.pop()),
|
|
mx= bin.length,
|
|
i= 0,
|
|
uia= new Uint8Array(mx);
|
|
|
|
for(i;i<mx;++i) uia[i]= bin.charCodeAt(i);
|
|
|
|
return new B([uia], {type: t});
|
|
}
|
|
|
|
function saver(url, winMode){
|
|
|
|
if ('download' in a) { //html5 A[download]
|
|
a.href = url;
|
|
a.setAttribute("download", fn);
|
|
a.innerHTML = "downloading...";
|
|
D.body.appendChild(a);
|
|
setTimeout(function() {
|
|
a.click();
|
|
D.body.removeChild(a);
|
|
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(a.href);}, 250 );}
|
|
}, 66);
|
|
return true;
|
|
}
|
|
|
|
if(typeof safari !=="undefined" ){ // handle non-a[download] safari as best we can:
|
|
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u);
|
|
if(!window.open(url)){ // popup blocked, offer direct download:
|
|
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//do iframe dataURL download (old ch+FF):
|
|
var f = D.createElement("iframe");
|
|
D.body.appendChild(f);
|
|
|
|
if(!winMode){ // force a mime that will download:
|
|
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u);
|
|
}
|
|
f.src=url;
|
|
setTimeout(function(){ D.body.removeChild(f); }, 333);
|
|
|
|
}//end saver
|
|
|
|
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
|
|
return navigator.msSaveBlob(blob, fn);
|
|
}
|
|
|
|
if(self.URL){ // simple fast and modern way using Blob and URL:
|
|
saver(self.URL.createObjectURL(blob), true);
|
|
}else{
|
|
// handle non-Blob()+non-URL browsers:
|
|
if(typeof blob === "string" || blob.constructor===z ){
|
|
try{
|
|
return saver( "data:" + m + ";base64," + self.btoa(blob) );
|
|
}catch(y){
|
|
return saver( "data:" + m + "," + encodeURIComponent(blob) );
|
|
}
|
|
}
|
|
|
|
// Blob but not URL:
|
|
fr=new FileReader();
|
|
fr.onload=function(e){
|
|
saver(this.result);
|
|
};
|
|
fr.readAsDataURL(blob);
|
|
}
|
|
return true;
|
|
} /* end download() */
|
|
|
|
module.exports = download;
|
|
|
|
},{}],2:[function(require,module,exports){
|
|
// -------------------------------------------------
|
|
// ------------------ UTF8 Helpers -----------------
|
|
// -------------------------------------------------
|
|
// http://en.wikipedia.org/wiki/UTF-8
|
|
"use strict";
|
|
|
|
function UTF8StreamToUnicode() {
|
|
|
|
this.stream = new Uint8Array(5);
|
|
this.ofs = 0;
|
|
|
|
this.Put = function(key) {
|
|
this.stream[this.ofs] = key;
|
|
this.ofs++;
|
|
switch(this.ofs) {
|
|
case 1:
|
|
if (this.stream[0] < 0x80) {
|
|
this.ofs = 0;
|
|
return this.stream[0];
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if ((this.stream[0]&0xE0) == 0xC0)
|
|
if ((this.stream[1]&0xC0) == 0x80) {
|
|
this.ofs = 0;
|
|
return ((this.stream[0]&0x1F)<<6) |
|
|
((this.stream[1]&0x3F)<<0);
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
if ((this.stream[0]&0xF0) == 0xE0)
|
|
if ((this.stream[1]&0xC0) == 0x80)
|
|
if ((this.stream[2]&0xC0) == 0x80) {
|
|
this.ofs = 0;
|
|
return ((this.stream[0]&0xF ) << 12) |
|
|
((this.stream[1]&0x3F) << 6) |
|
|
((this.stream[2]&0x3F) << 0);
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
if ((this.stream[0]&0xF8) == 0xF0)
|
|
if ((this.stream[1]&0xC0) == 0x80)
|
|
if ((this.stream[2]&0xC0) == 0x80)
|
|
if ((this.stream[3]&0xC0) == 0x80) {
|
|
this.ofs = 0;
|
|
return ((this.stream[0]&0x7 ) << 18) |
|
|
((this.stream[1]&0x3F) << 12) |
|
|
((this.stream[2]&0x3F) << 6) |
|
|
((this.stream[3]&0x3F) << 0);
|
|
}
|
|
this.ofs = 0;
|
|
return -1; //obviously illegal character, so reset
|
|
break;
|
|
|
|
default:
|
|
this.ofs = 0;
|
|
return -1;
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
function UnicodeToUTF8Stream(key) {
|
|
key = key|0;
|
|
if (key < 0x80) {
|
|
return [key];
|
|
} else
|
|
if (key <= 0x7FF) {
|
|
return [
|
|
(key >> 6) | 0xC0,
|
|
(key & 0x3F) | 0x80
|
|
];
|
|
} else
|
|
if (key <= 0xFFFF) {
|
|
return [
|
|
(key >> 12) | 0xE0,
|
|
((key >> 6) & 0x3F) | 0x80,
|
|
(key & 0x3F) | 0x80
|
|
];
|
|
} else
|
|
if (key <= 0x10FFFF) {
|
|
return [
|
|
(key >> 18) | 0xF0,
|
|
((key >> 12) & 0x3F) | 0x80,
|
|
((key >> 6) & 0x3F) | 0x80,
|
|
(key & 0x3F) | 0x80
|
|
];
|
|
} else {
|
|
//message.Debug("Error in utf-8 encoding: Invalid key");
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function UTF8Length(s)
|
|
{
|
|
var length = 0;
|
|
for(var i=0; i<s.length; i++) {
|
|
var key = s.charCodeAt(i);
|
|
if (key < 0x80) {
|
|
length += 1;
|
|
} else
|
|
if (key <= 0x7FF) {
|
|
length += 2;
|
|
} else
|
|
if (key <= 0xFFFF) {
|
|
length += 3;
|
|
} else
|
|
if (key <= 0x10FFFF) {
|
|
length += 4;
|
|
} else {
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
module.exports.UTF8StreamToUnicode = UTF8StreamToUnicode;
|
|
module.exports.UTF8Length = UTF8Length;
|
|
module.exports.UnicodeToUTF8Stream = UnicodeToUTF8Stream;
|
|
|
|
},{}],3:[function(require,module,exports){
|
|
// manages the websocket connection for the ethmac peripheral
|
|
|
|
var message = require('../messagehandler');
|
|
|
|
"use strict";
|
|
|
|
function Ethernet(relayURL) {
|
|
this.url = relayURL;
|
|
this.onmessage = function(e) { };
|
|
this.ntries = 0;
|
|
this.OpenSocket();
|
|
}
|
|
|
|
function EthernetMessageHandler(e) {
|
|
// if we recv binary data, call the onmessage handler
|
|
// which was assigned to this Ethernet object
|
|
if (e.data instanceof ArrayBuffer) {
|
|
this.onmessage(e);
|
|
} else
|
|
// otherwise, this might be a textual "ping" message to keep
|
|
// the connection alive
|
|
if (e.data.toString().indexOf('ping:') == 0) {
|
|
this.socket.send('pong:' + e.data.toString().substring(5));
|
|
}
|
|
}
|
|
|
|
function EthernetOpenHandler(e) {
|
|
this.ntries = 0;
|
|
}
|
|
|
|
function EthernetCloseHandler(e) {
|
|
// reopen websocket if it closes
|
|
if (this.ntries > 3) {
|
|
message.Debug("Websocket error: Connection failed");
|
|
return;
|
|
}
|
|
this.ntries++;
|
|
message.Debug("Websocket closed. Reopening.");
|
|
this.OpenSocket();
|
|
}
|
|
|
|
function EthernetErrorHandler(e) {
|
|
// just report the error to console, close event
|
|
// will handle reopening if possible
|
|
message.Debug("Websocket error:");
|
|
message.Debug(e);
|
|
}
|
|
|
|
Ethernet.prototype.OpenSocket = function() {
|
|
try {
|
|
this.socket = new WebSocket(this.url);
|
|
} catch(err) {
|
|
delete this.socket;
|
|
EthernetErrorHandler(err);
|
|
return;
|
|
}
|
|
this.socket.binaryType = 'arraybuffer';
|
|
this.socket.onmessage = EthernetMessageHandler.bind(this);
|
|
this.socket.onclose = EthernetCloseHandler.bind(this);
|
|
this.socket.onopen = EthernetOpenHandler.bind(this);
|
|
this.socket.onerror = EthernetErrorHandler.bind(this);
|
|
}
|
|
|
|
Ethernet.prototype.SendFrame = function(data) {
|
|
if (typeof this.socket == "undefined") return;
|
|
try {
|
|
this.socket.send(data);
|
|
} catch (err) {
|
|
// this is unusual error, object exists, but send does not work
|
|
EthernetErrorHandler(err);
|
|
}
|
|
}
|
|
|
|
Ethernet.prototype.Close = function() {
|
|
this.socket.onclose = undefined;
|
|
this.socket.close();
|
|
}
|
|
|
|
module.exports = Ethernet;
|
|
|
|
},{"../messagehandler":9}],4:[function(require,module,exports){
|
|
var message = require('../messagehandler');
|
|
var download = require('../../lib/download');
|
|
var utils = require('../utils');
|
|
|
|
"use strict";
|
|
|
|
function Filesystem(syncURL, userid) {
|
|
this.syncURL = syncURL;
|
|
this.userid = userid;
|
|
}
|
|
|
|
Filesystem.prototype.TAR = function(path) {
|
|
message.Register("tar", function(d){download(d, "user.tar", "application/x-tar");} );
|
|
message.Send("tar", path);
|
|
}
|
|
|
|
Filesystem.prototype.Sync = function(path) {
|
|
message.Register("sync", this.OnSync.bind(this));
|
|
message.Send("sync", path);
|
|
}
|
|
|
|
Filesystem.prototype.OnSync = function(d) {
|
|
utils.UploadBinaryResource(this.syncURL, this.userid + ".tar", d,
|
|
function(response) {
|
|
alert(
|
|
"Message from Server:" + response + "\n" +
|
|
"The home folder '/home/alice' has been synced with the server\n" +
|
|
"In order to access the data at a later date,\n" +
|
|
"start the next session with the current url with the user id\n" +
|
|
"The folder size is currently limited to 1MB. Note that the feature is experimental.\n" +
|
|
"The content can be downloaded under http://jor1k.com/sync/tarballs/" + this.userid+".tar.bz2"
|
|
);
|
|
}.bind(this),
|
|
function(msg) {alert(msg);}
|
|
);
|
|
}
|
|
|
|
Filesystem.prototype.UploadExternalFile = function(f) {
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
message.Send("MergeFile",
|
|
{name: "home/alice/"+f.name, data: new Uint8Array(reader.result)});
|
|
}.bind(this);
|
|
reader.readAsArrayBuffer(f);
|
|
}
|
|
|
|
Filesystem.prototype.MergeFile = function(fileName, data) {
|
|
function stringToUint(string) {
|
|
var charList = string.split(''),
|
|
uintArray = [];
|
|
for (var i = 0; i < charList.length; i++) {
|
|
uintArray.push(charList[i].charCodeAt(0));
|
|
}
|
|
return new Uint8Array(uintArray);
|
|
}
|
|
message.Send("MergeFile", {name: fileName, data: stringToUint(data)});
|
|
}
|
|
|
|
Filesystem.prototype.MergeBinaryFile = function(fileName, data) {
|
|
message.Send("MergeFile", {name: fileName, data: data});
|
|
}
|
|
|
|
Filesystem.prototype.CreateDirectory = function(dirctoryName) {
|
|
message.Send("CreateDirectory", dirctoryName );
|
|
}
|
|
|
|
Filesystem.prototype.ReadFile = function(fileName, callback) {
|
|
message.Register("ReadFile", callback);
|
|
message.Send("ReadFile", { name: fileName });
|
|
}
|
|
|
|
//deletes contents of specified directory.
|
|
Filesystem.prototype.DeleteDirContents = function(dirPath) {
|
|
message.Send("DeleteDirContents", dirPath);
|
|
}
|
|
|
|
//deletes file, recursively deletes dir
|
|
Filesystem.prototype.DeleteNode = function(nodeName) {
|
|
message.Send("DeleteNode", nodeName);
|
|
}
|
|
|
|
Filesystem.prototype.Rename = function(oldPath, newPath) {
|
|
message.Send("Rename", {oldPath:oldPath, newPath: newPath});
|
|
}
|
|
|
|
Filesystem.prototype.WatchFile = function(fileName, callback) {
|
|
message.Register("WatchFileEvent", callback);
|
|
message.Send("WatchFile", { name: fileName });
|
|
}
|
|
|
|
Filesystem.prototype.WatchDirectory = function(directoryPath, callback) {
|
|
message.Register("WatchDirectoryEvent", callback);
|
|
message.Send("WatchDirectory", { name: directoryPath });
|
|
}
|
|
|
|
module.exports = Filesystem;
|
|
|
|
},{"../../lib/download":1,"../messagehandler":9,"../utils":11}],5:[function(require,module,exports){
|
|
var message = require('../messagehandler');
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
function Framebuffer(fbid, fps) {
|
|
this.fbid = fbid;
|
|
|
|
this.Init(fbid);
|
|
this.SetFPS(fps);
|
|
}
|
|
|
|
|
|
// Init Framebuffer if it exists
|
|
Framebuffer.prototype.Init = function(fbid) {
|
|
this.fbcanvas = document.getElementById(fbid);
|
|
if(!this.fbcanvas) return;
|
|
|
|
this.fbctx = this.fbcanvas.getContext("2d");
|
|
this.fbctx.fillStyle = "rgba(0, 0, 0, 255)";
|
|
this.fbctx.fillRect ( 0, 0 , this.fbcanvas.width, this.fbcanvas.height );
|
|
|
|
this.fbimageData = this.fbctx.createImageData(this.fbcanvas.width, this.fbcanvas.height);
|
|
|
|
this.fbcanvas.onmousedown = function(event) {
|
|
this.fbcanvas.style.border = "2px solid #FF0000";
|
|
var rect = this.fbcanvas.getBoundingClientRect();
|
|
var x = (event.clientX - rect.left)*640/rect.width;
|
|
var y = (event.clientY - rect.top)*400/rect.height;
|
|
message.Send("tsmousedown", {x:x, y:y});
|
|
}.bind(this);
|
|
|
|
this.fbcanvas.onmouseup = function(event) {
|
|
var rect = this.fbcanvas.getBoundingClientRect();
|
|
var x = (event.clientX - rect.left)*640/rect.width;
|
|
var y = (event.clientY - rect.top)*400/rect.height;
|
|
message.Send("tsmouseup", {x:x, y:y});
|
|
}.bind(this);
|
|
|
|
this.fbcanvas.onmousemove = function(event) {
|
|
var rect = this.fbcanvas.getBoundingClientRect();
|
|
var x = (event.clientX - rect.left)*640/rect.width;
|
|
var y = (event.clientY - rect.top)*400/rect.height;
|
|
message.Send("tsmousemove", {x:x, y:y});
|
|
}.bind(this);
|
|
}
|
|
|
|
|
|
// receive interval of the contents of the framebuffer
|
|
Framebuffer.prototype.SetFPS = function(fps) {
|
|
this.fps = fps;
|
|
if(!this.fbcanvas) return;
|
|
if (this.fbinterval) {
|
|
window.clearInterval(this.fbinterval);
|
|
}
|
|
if (fps != 0) {
|
|
this.fbinterval = window.setInterval(function(){message.Send("GetFB", 0)}.bind(this), 1000/this.fps);
|
|
}
|
|
}
|
|
|
|
|
|
Framebuffer.prototype.Update = function(buffer) {
|
|
if(!this.fbcanvas) return;
|
|
//if(this.userpaused) return;
|
|
var i=0, n = buffer.length;
|
|
var data = this.fbimageData.data;
|
|
|
|
var offset = 0x0;
|
|
for (i = 0; i < n; i++) {
|
|
var x = buffer[i];
|
|
data[offset++] = (x >> 24) & 0xF8;
|
|
data[offset++] = (x >> 19) & 0xFC;
|
|
data[offset++] = (x >> 13) & 0xF8;
|
|
data[offset++] = 0xFF;
|
|
data[offset++] = (x >> 8) & 0xF8;
|
|
data[offset++] = (x >> 3) & 0xFC;
|
|
data[offset++] = (x << 3) & 0xF8;
|
|
data[offset++] = 0xFF;
|
|
}
|
|
|
|
//data.set(buffer);
|
|
this.fbctx.putImageData(this.fbimageData, 0, 0); // at coords 0,0
|
|
}
|
|
|
|
|
|
module.exports = Framebuffer;
|
|
|
|
},{"../messagehandler":9}],6:[function(require,module,exports){
|
|
// Provides a loop sound buffer.
|
|
|
|
var message = require('../messagehandler');
|
|
|
|
"use strict";
|
|
|
|
function LoopSoundBuffer(samplerate) {
|
|
this.enabled = false;
|
|
this.nperiods = 8; // number of periods
|
|
|
|
this.source = new Array(this.nperiods);
|
|
this.soundbuffer = new Array(this.nperiods);
|
|
|
|
this.period = 0;
|
|
this.periodsize = 0;
|
|
this.bufferpos = 0;
|
|
|
|
if (typeof AudioContext == "undefined") return;
|
|
|
|
this.context = new AudioContext();
|
|
this.SetRate(samplerate);
|
|
}
|
|
|
|
LoopSoundBuffer.prototype.SetRate = function(rate) {
|
|
if (this.samplerate == rate) return;
|
|
this.samplerate = rate;
|
|
this.periodsize = Math.floor(this.samplerate/4); // 250ms
|
|
this.sampleslen = this.periodsize*this.nperiods;
|
|
this.buffer = new Float32Array(this.sampleslen);
|
|
|
|
for(var i=0; i<this.nperiods; i++) {
|
|
this.soundbuffer[i] = this.context.createBuffer(1, this.periodsize, this.samplerate);
|
|
}
|
|
}
|
|
|
|
LoopSoundBuffer.prototype.OnEnded = function()
|
|
{
|
|
if (!this.enabled) return;
|
|
this.PlayBuffer(this.period);
|
|
this.period++;
|
|
}
|
|
|
|
LoopSoundBuffer.prototype.Enabled = function(e)
|
|
{
|
|
this.enabled = e;
|
|
if (!e) return;
|
|
this.period = 0;
|
|
this.basetime = this.context.currentTime;
|
|
this.PlayBuffer(0);
|
|
this.PlayBuffer(1);
|
|
this.period = 2;
|
|
this.bufferpos = this.periodsize*(this.period+4);
|
|
}
|
|
|
|
LoopSoundBuffer.prototype.PlayBuffer = function(period)
|
|
{
|
|
if (!this.enabled) return;
|
|
var idx = period % this.nperiods;
|
|
var buffer = this.soundbuffer[idx].getChannelData(0);
|
|
var offset = idx * this.periodsize;
|
|
for(var i=0; i<this.periodsize; i++) {
|
|
buffer[i] = this.buffer[i + offset];
|
|
this.buffer[i+offset] = 0;
|
|
}
|
|
var source = this.context.createBufferSource(); // creates a sound source
|
|
source.buffer = this.soundbuffer[idx];
|
|
source.connect(this.context.destination);
|
|
source.onended = this.OnEnded.bind(this);
|
|
source.start(this.basetime + period*(this.periodsize)/this.samplerate);
|
|
|
|
// save the source. Otherwise the garbage collector might take them and the function OnEnded is not executed
|
|
this.source[idx] = source;
|
|
}
|
|
|
|
LoopSoundBuffer.prototype.AddBuffer = function(addbuffer)
|
|
{
|
|
if (!this.enabled) return;
|
|
|
|
var currentperiod = (this.bufferpos / this.periodsize);
|
|
if ((currentperiod) < (this.period+2)) {
|
|
this.bufferpos = this.periodsize*(this.period+4);
|
|
//message.Debug("Warning: Sound buffer underrun, resetting");
|
|
}
|
|
if (currentperiod > (this.period+5)) {
|
|
this.bufferpos = this.periodsize*(this.period+4);
|
|
//message.Debug("Warning: Sound buffer overrun, resetting");
|
|
}
|
|
|
|
for(var i=0; i<addbuffer.length; i++) {
|
|
this.buffer[this.bufferpos%this.sampleslen] = addbuffer[i]/128.;
|
|
this.bufferpos++;
|
|
}
|
|
}
|
|
|
|
module.exports = LoopSoundBuffer;
|
|
|
|
},{"../messagehandler":9}],7:[function(require,module,exports){
|
|
// -------------------------------------------------
|
|
// -------------- Terminal Input -------------------
|
|
// -------------------------------------------------
|
|
|
|
// for the special keys look at
|
|
// http://www2.gar.no/glinkj/help/cmds/ansm.htm
|
|
// http://www.comptechdoc.org/os/linux/howlinuxworks/linux_hlkeycodes.html
|
|
|
|
"use strict";
|
|
|
|
var UTF8 = require('../../lib/utf8');
|
|
|
|
function TerminalInput(SendChars)
|
|
{
|
|
this.CTRLpressed = false;
|
|
this.ALTpressed = false;
|
|
this.SHIFTpressed = false;
|
|
this.SendChars = SendChars;
|
|
this.enabled = true;
|
|
}
|
|
|
|
|
|
TerminalInput.prototype.OnKeyPress = function(e) {
|
|
if (!this.enabled) {
|
|
return;
|
|
}
|
|
var key = 0;
|
|
key = e.charCode;
|
|
if (key == 0) {
|
|
return false;
|
|
}
|
|
// Define that the control key has this effect only if special keys have been pressed A..Z a..z. Otherwise some foreign keyboards will not work
|
|
if ((this.CTRLpressed) && (((key >= 0x41) && (key <= 0x5A)) || ((key >= 0x61) && (key <= 0x7A)))) {
|
|
key &= 0x1F;
|
|
}
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(key));
|
|
return false;
|
|
};
|
|
|
|
TerminalInput.prototype.OnKeyUp = function(e)
|
|
{
|
|
if (!this.enabled) {
|
|
return;
|
|
}
|
|
var keycode = e.keyCode;
|
|
var unicode = e.charCode;
|
|
var ie = document.all ? true : false;
|
|
if (keycode == 17)
|
|
{
|
|
this.CTRLpressed = false;
|
|
if (document.getElementById('ctrl'))
|
|
{
|
|
if ( ie )
|
|
document.getElementById('ctrl').innerText='';
|
|
else
|
|
document.getElementById('ctrl').innerHTML='';
|
|
}
|
|
}
|
|
else if (keycode == 18)
|
|
{
|
|
this.ALTpressed = false;
|
|
if (document.getElementById('alt'))
|
|
{
|
|
if ( ie )
|
|
document.getElementById('alt').innerText='';
|
|
else
|
|
document.getElementById('alt').innerHTML='';
|
|
}
|
|
}
|
|
else if (keycode == 16 )
|
|
{
|
|
this.SHIFTpressed = false;
|
|
if (document.getElementById('shift'))
|
|
{
|
|
if ( ie )
|
|
document.getElementById('shift').innerText='';
|
|
else
|
|
document.getElementById('shift').innerHTML='';
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
TerminalInput.prototype.OnKeyDown = function(e)
|
|
{
|
|
if (!this.enabled)
|
|
{
|
|
return;
|
|
}
|
|
var keycode = e.keyCode;
|
|
var unicode = e.charCode;
|
|
var ie = document.all ? true : false;
|
|
|
|
// alert ('oscpu : ' + navigator.oscpu );
|
|
// alert ('platform : ' + navigator.platform );
|
|
|
|
var os = navigator.platform.indexOf('MacIntel');
|
|
var ua = navigator.userAgent;
|
|
var safari = ua.indexOf("Safari") != -1;
|
|
// alert('Safari :' + safari);
|
|
|
|
// alert ('os = ' + os);
|
|
// var os = navigator.oscpu;
|
|
|
|
// CTRL + x key handling for chrome
|
|
if ((this.CTRLpressed) && (!this.ALTpressed) && (!this.SHIFTpressed) && (keycode >= 65) && (keycode <= 90))
|
|
{
|
|
this.SendChars([(keycode-32) & 0x1F]);
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
// alert ( 'UserAgent = ' + ua + ' alt = ' + this.ALTpressed + ' shift = ' + this.SHIFTpressed + ' touche = ' + keycode + '(old=' + this.lastChar + ') OSX = ' + os);
|
|
if ( document.getElementById('shift'))
|
|
{
|
|
var txt = ( this.SHIFTpressed ) ? 'shift' : '';
|
|
if ( ie )
|
|
document.getElementById('shift').innerText = txt;
|
|
else
|
|
document.getElementById('shift').innerHTML = txt;
|
|
|
|
}
|
|
|
|
if ( document.getElementById('alt'))
|
|
{
|
|
var txt = ( this.ALTpressed ) ? 'alt' : '';
|
|
if ( ie )
|
|
document.getElementById('alt').innerText = txt;
|
|
else
|
|
document.getElementById('alt').innerHTML = txt;
|
|
|
|
}
|
|
|
|
if ( document.getElementById('ctrl'))
|
|
{
|
|
var txt = ( this.CTRLpressed ) ? 'ctrl' : '';
|
|
if ( ie )
|
|
document.getElementById('ctrl').innerText = txt;
|
|
else
|
|
document.getElementById('ctrl').innerHTML = txt;
|
|
|
|
}
|
|
|
|
if ( document.getElementById('key'))
|
|
{
|
|
if ( ie )
|
|
document.getElementById('key').innerText=keycode;
|
|
else
|
|
document.getElementById('key').innerHTML=keycode;
|
|
}
|
|
|
|
// TODO tab?
|
|
var oldk=this.lastChar;
|
|
this.lastChar=false; // auto-nettoyage
|
|
|
|
switch (keycode)
|
|
{
|
|
case 8:
|
|
// del
|
|
this.SendChars([0x7F]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 9:
|
|
//tab
|
|
break;
|
|
case 16:
|
|
this.SHIFTpressed = true;
|
|
if ( document.getElementById('shift'))
|
|
{
|
|
if ( ie )
|
|
document.getElementById('shift').innerText='shift';
|
|
else
|
|
document.getElementById('shift').innerHTML='shift';
|
|
}
|
|
return;
|
|
break;
|
|
case 17:
|
|
// CTRL
|
|
this.CTRLpressed = true;
|
|
if ( document.getElementById('ctrl'))
|
|
{
|
|
if ( ie )
|
|
document.getElementById('ctrl').innerText='ctrl';
|
|
else
|
|
document.getElementById('ctrl').innerHTML='ctrl';
|
|
}
|
|
|
|
//e.preventDefault();
|
|
//return false;
|
|
return;
|
|
break;
|
|
case 18:
|
|
// Alt
|
|
this.ALTpressed = true;
|
|
if ( document.getElementById('alt'))
|
|
{
|
|
if ( ie )
|
|
document.getElementById('alt').innerText='alt';
|
|
else
|
|
document.getElementById('alt').innerHTML='alt';
|
|
}
|
|
return;
|
|
break;
|
|
case 32:
|
|
// french mac keyboard
|
|
// add ˆ
|
|
if ( oldk == 160 )
|
|
{
|
|
this.SendChars([0x5e]); // ^
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
else if ( oldk == 170 )
|
|
{
|
|
this.SendChars([0xc2, 0xa8]); // ¨
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 38:
|
|
// up
|
|
this.SendChars([0x1B, 0x5B, 0x41]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 37:
|
|
// left
|
|
this.SendChars([0x1B, 0x5B, 0x44]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 39:
|
|
// right
|
|
this.SendChars([0x1B, 0x5B, 0x43]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 40:
|
|
// down
|
|
this.SendChars([0x1B, 0x5B, 0x42]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 65:
|
|
// french mac keyboard
|
|
if ( oldk == 160 )
|
|
{
|
|
// add â
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(226)); // â
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
case 69:
|
|
// french mac keyboard
|
|
if ( oldk == 160 )
|
|
{
|
|
// add ê
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(234)); // ê
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
else if ( oldk == 170 )
|
|
{
|
|
// add ë
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(235)); // ë
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
case 73:
|
|
// french mac keyboard
|
|
if ( oldk == 160 )
|
|
{
|
|
// add î
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(238)); // î
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
else if ( oldk == 170 )
|
|
{
|
|
// add ï
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(239)); // ï
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
// french mac keyboard
|
|
case 76:
|
|
if ( (this.CTRLpressed) && (this.SHIFTpressed) && (os != -1))
|
|
{
|
|
// Add pipe char on Mac
|
|
this.SendChars([0x7C]);
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
// french mac keyboard
|
|
case 78:
|
|
if ( (this.ALTpressed) && (!this.CTRLpressed) && (!this.SHIFTpressed) && (os != -1))
|
|
{
|
|
// Add tilde char on Mac
|
|
this.SendChars([0x7E]);
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
case 79:
|
|
// french mac keyboard
|
|
// add ô
|
|
if ( oldk == 160 )
|
|
{
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(244)); // ô
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
else if ( oldk == 170 )
|
|
{
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(246)); // ö
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
case 85:
|
|
// french mac keyboard
|
|
if ( oldk == 160 )
|
|
{
|
|
// add û
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(251)); // û
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
else if ( oldk == 170 )
|
|
{
|
|
// add ü
|
|
this.SendChars(UTF8.UnicodeToUTF8Stream(252)); // ü
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 112:
|
|
case 113:
|
|
case 114:
|
|
case 115:
|
|
case 116:
|
|
// F1 - F5
|
|
this.SendChars([0x1B, 0x5B, 0x5B, keycode-112+0x41]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 117:
|
|
case 118:
|
|
case 119:
|
|
// F6 - F8
|
|
this.SendChars([0x1B, 0x5B, 0x31, keycode-117+0x37, 0x7E]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 120:
|
|
case 121:
|
|
// F9 - F10
|
|
this.SendChars([0x1B, 0x5B, 0x32, keycode-120+0x30, 0x7E]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 36:
|
|
// pos1
|
|
this.SendChars([0x1b, 0x5b, 0x48]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 35:
|
|
// end
|
|
this.SendChars([0x1b, 0x5b, 0x46]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 33:
|
|
// Page up
|
|
this.SendChars([0x1b, 0x5b, 0x35, 0x7e]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 34:
|
|
// Page down
|
|
this.SendChars([0x1b, 0x5b, 0x36, 0x7e]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 45:
|
|
// ins
|
|
this.SendChars([0x1b, 0x5b, 0x32, 0x7e]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
case 46:
|
|
// del
|
|
this.SendChars([0x1b, 0x5b, 0x33, 0x7e]);
|
|
e.preventDefault();
|
|
return false;
|
|
break;
|
|
// french mac keyboard
|
|
case 160:
|
|
// detecte l'usage de la touche ^ (uniquement pour mac & Firefox)
|
|
if ( ( os != -1 ) && ( ua.indexOf("Firefox") != -1 ) )
|
|
{
|
|
// ^ ou ¨
|
|
this.lastChar = 160;
|
|
if ( this.SHIFTpressed )
|
|
this.lastChar = 170;
|
|
return false;
|
|
}
|
|
break;
|
|
// french mac keyboard
|
|
case 192:
|
|
// ` ..... (only for mac && Firefox)
|
|
if ( ( os != -1 ) && ( ua.indexOf("Firefox") != -1 ) )
|
|
{
|
|
if ( this.SHIFTpressed )
|
|
this.SendChars([0xc2, 0xa3]); // pound £
|
|
else
|
|
this.SendChars([0x60]); // backquote `
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
// french mac keyboard
|
|
case 219:
|
|
// detecte l'usage de la touche ^ (pour tous les autres navigateurs -- exception:Firefox --)
|
|
// Sur Safari -> c'est aussi le code de la touche (
|
|
// alert ('os = ' + os + ' ctrl = ' + this.CTRLpressed + ' alt = ' + this.ALTpressed );
|
|
if ( ( os == -1 ) && ( ua.indexOf("Firefox") == -1 ) && (this.CTRLpressed) && (this.ALTpressed) )
|
|
{
|
|
// Chrome sur PC => ]
|
|
this.SendChars([0x5D]); // ]
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
else if ( ( os != -1 ) && ( ua.indexOf("Firefox") == -1 ) && (!this.SHIFTpressed) && (!this.ALTpressed) )
|
|
{
|
|
// Sur mac ( Chrome.... )
|
|
// ^ ou ¨
|
|
this.lastChar = 160;
|
|
if ( this.SHIFTpressed )
|
|
this.lastChar = 170;
|
|
return false;
|
|
}
|
|
else if ( ( safari ) && (this.ALTpressed) && (!this.SHIFTpressed) )
|
|
{
|
|
this.SendChars([0x7B]); // {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
else if ( ( safari ) && (this.ALTpressed) && (this.SHIFTpressed) )
|
|
{
|
|
this.SendChars([0x5B]); // [
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
// french mac keyboard
|
|
case 220:
|
|
// ` ..... (only for mac && !Firefox)
|
|
// interference entre les touches ALT+SHIFT+/ et ALT+SHIFT+l et ` (même keycode)
|
|
|
|
if ( ( os != -1 ) && ( ua.indexOf("Firefox") == -1 ) )
|
|
{
|
|
if ( this.SHIFTpressed && this.ALTpressed )
|
|
this.SendChars([0x5c]); // \
|
|
else if ( this.SHIFTpressed && this.CTRLpressed)
|
|
this.SendChars([0x7C]); // |
|
|
else if ( this.SHIFTpressed )
|
|
this.SendChars([0xc2, 0xa3]); // `
|
|
else
|
|
this.SendChars([0x60]); // £
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ((keycode != 0) && (keycode <= 0x1F)) {
|
|
this.SendChars([keycode]);
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
return;
|
|
};
|
|
|
|
|
|
module.exports = TerminalInput;
|
|
|
|
},{"../../lib/utf8":2}],8:[function(require,module,exports){
|
|
// -------------------------------------------------
|
|
// --------------- Terminal Emulator ---------------
|
|
// -------------------------------------------------
|
|
// http://lxr.free-electrons.com/source/drivers/tty/vt/vt.c
|
|
|
|
"use strict";
|
|
|
|
var UTF8 = require('../../lib/utf8');
|
|
var message = require('../messagehandler');
|
|
|
|
var Colors = new Array(
|
|
// standard colors
|
|
"#000000", "#BB0000", "#00BB00", "#BBBB00",
|
|
"#0000BB", "#BB00BB", "#00BBBB", "#BBBBBB",
|
|
// brighter colors
|
|
"#555555", "#FF5555", "#55FF55", "#FFFF55",
|
|
"#5555FF", "#FF55FF", "#55FFFF", "#FFFFFF",
|
|
// dimmed colors
|
|
"#000000", "#770000", "#007700", "#777700",
|
|
"#000077", "#770077", "#007777", "#777777"
|
|
);
|
|
|
|
// constructor
|
|
function Terminal(nrows, ncolumns, elemId) {
|
|
this.nrows = nrows;
|
|
this.ncolumns = ncolumns;
|
|
|
|
var ele = document.getElementById(elemId);
|
|
if (ele.tagName == "CANVAS") {
|
|
this.canvas = ele;
|
|
this.context = this.canvas.getContext("2d");
|
|
this.context.font = "13px courier,fixed,swiss,monospace,sans-serif";
|
|
} else {
|
|
this.Table = ele;
|
|
this.rowelements = new Array(this.nrows);
|
|
for (var i = 0; i < nrows; i++) {
|
|
var TR = this.Table.insertRow(0);
|
|
var TD = document.createElement("td");
|
|
this.rowelements[i] = TD;
|
|
TR.appendChild(TD);
|
|
}
|
|
}
|
|
|
|
this.cursorvisible = false;
|
|
this.escapetype = 0;
|
|
this.escapestring = "";
|
|
this.cursorx = 0;
|
|
this.cursory = 0;
|
|
this.scrolltop = 0;
|
|
this.cursortype = 1;
|
|
this.scrollbottom = this.nrows-1;
|
|
|
|
this.attr_color = 0x7;
|
|
this.attr_reverse = false;
|
|
this.attr_italic = false;
|
|
this.attr_intensity = 0x1;
|
|
|
|
this.pauseblink = false;
|
|
this.OnCharReceived = function (){};
|
|
|
|
this.framerequested = false;
|
|
this.timeout = 30; // the time in ms when the next frame is drawn
|
|
|
|
this.updaterow = new Uint8Array(this.nrows);
|
|
|
|
this.utf8converter = new UTF8.UTF8StreamToUnicode();
|
|
|
|
this.trows = 40;
|
|
this.brows = this.trows - this.nrows;
|
|
this.bufferp = 0;
|
|
this.screen = new Array(this.trows);
|
|
this.color = new Array(this.trows);
|
|
for (var i = 0; i < this.trows; i++) {
|
|
this.updaterow[i] = 1;
|
|
this.screen[i] = new Uint16Array(this.ncolumns);
|
|
this.color[i] = new Uint16Array(this.ncolumns);
|
|
|
|
for (var j = 0; j < this.ncolumns; j++) {
|
|
this.screen[i][j] = 0x20;
|
|
this.color[i][j] = this.attr_color;
|
|
}
|
|
}
|
|
this.deletedScreenRow = this.screen[0];
|
|
this.deletedColorRow = this.color[0];
|
|
//message.Debug("Inside constructor");
|
|
this.UpdateScreen();
|
|
this.Blink();
|
|
|
|
//if (!this.canvas) this.Table.addEventListener("wheel", this.UpdateScreenForScroll.bind(this));
|
|
}
|
|
|
|
// Stop blinking cursor when the VM is paused
|
|
Terminal.prototype.PauseBlink = function(pause) {
|
|
pause = !! pause;
|
|
this.pauseblink = pause;
|
|
if (this.cursortype) {
|
|
this.cursorvisible = ! pause;
|
|
}
|
|
this.PrepareUpdateRow(this.cursory, this.cursorx);
|
|
}
|
|
|
|
Terminal.prototype.GetColor = function() {
|
|
var c = this.attr_color;
|
|
if (this.attr_reverse) {
|
|
c = ((c & 0x7) << 8) | ((c >> 8)) & 0x7;
|
|
}
|
|
if (this.attr_intensity == 2) {
|
|
c = c | 0x8;
|
|
} else
|
|
if (this.attr_intensity == 0) {
|
|
c = c | 0x10;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
Terminal.prototype.Blink = function() {
|
|
this.cursorvisible = !this.cursorvisible;
|
|
if(!this.pauseblink) this.PrepareUpdateRow(this.cursory, this.cursorx);
|
|
window.setTimeout(this.Blink.bind(this), 500); // update every half second
|
|
};
|
|
|
|
Terminal.prototype.deepCopy = function(oldObj) {
|
|
var newObj = oldObj;
|
|
if (oldObj && typeof oldObj === 'object') {
|
|
newObj = Object.prototype.toString.call(oldObj) === "[object Array]" ? [] : {};
|
|
for (var i in oldObj) {
|
|
newObj[i] = this.deepCopy(oldObj[i]);
|
|
}
|
|
}
|
|
return newObj;
|
|
}
|
|
|
|
Terminal.prototype.DeleteRow = function(row) {
|
|
var deletedScreenRow = this.deepCopy(this.screen[this.brows + row]);
|
|
var deletedColorRow = this.deepCopy(this.color[this.brows + row]);
|
|
if(row == 23){
|
|
for(var i = 0;i < this.brows - 1;i++){
|
|
this.screen[i] = this.screen[i + 1];
|
|
this.color[i] = this.color[i + 1];
|
|
}
|
|
this.screen[this.brows - 1] = deletedScreenRow;
|
|
this.color[this.brows - 1] = deletedColorRow;
|
|
}
|
|
for (var j = 0; j < this.ncolumns; j++) {
|
|
this.screen[this.brows + row][j] = 0x20;
|
|
this.color[this.brows + row][j] = this.attr_color;
|
|
}
|
|
this.PrepareUpdateRow(row);
|
|
};
|
|
|
|
Terminal.prototype.DeleteArea = function(row, column, row2, column2) {
|
|
for (var i = row; i <= row2; i++) {
|
|
for (var j = column; j <= column2; j++) {
|
|
this.screen[this.brows + i][j] = 0x20;
|
|
this.color[this.brows + i][j] = this.attr_color;
|
|
}
|
|
this.PrepareUpdateRow(i);
|
|
}
|
|
};
|
|
|
|
|
|
Terminal.prototype.UpdateRowCanvas = function(row) {
|
|
var y = row << 4;
|
|
var line = this.screen[this.brows + row];
|
|
var c = this.color[this.brows + row][0]|0;
|
|
var n = 0;
|
|
|
|
for (var column = 0; column < this.ncolumns; column++) {
|
|
|
|
var cnew = this.color[this.brows + row][column]|0;
|
|
|
|
if (this.cursorvisible)
|
|
if (row == this.cursory)
|
|
if (column == this.cursorx) {
|
|
cnew |= 0x600;
|
|
}
|
|
|
|
if (c != cnew) {
|
|
var x = (column - n) << 3;
|
|
this.context.fillStyle = Colors[(c >>> 8) & 0x1F];
|
|
this.context.fillRect(x, y, n*8, 16);
|
|
this.context.fillStyle = Colors[c & 0x1F];
|
|
for(var i=0; i<n; i++) {
|
|
this.context.fillText(String.fromCharCode(line[column - n + i]), x+(i<<3), y+12);
|
|
}
|
|
c = cnew;
|
|
n = 0;
|
|
}
|
|
|
|
n++;
|
|
}
|
|
|
|
var x = (column - n) << 3;
|
|
this.context.fillStyle = Colors[(c >>> 8) & 0x1F];
|
|
this.context.fillRect(x, y, n*8, 16);
|
|
this.context.fillStyle = Colors[c & 0x1F];
|
|
for(var i=0; i<n; i++) {
|
|
this.context.fillText(String.fromCharCode(line[column - n + i]), x+(i<<3), y+12);
|
|
}
|
|
|
|
};
|
|
|
|
Terminal.prototype.GetSpan = function(c, line, idx, n) {
|
|
var html = "<span style=\"color:" + Colors[c & 0x1F] + ";background-color:" + Colors[(c >> 8) & 0x1F] + "\">";
|
|
for(var i=0; i<n; i++) {
|
|
switch (line[idx + i])
|
|
{
|
|
case 0x20:
|
|
html += " ";
|
|
break;
|
|
|
|
case 0x26: // '&'
|
|
html += "&";
|
|
break;
|
|
|
|
case 0x3C: // '<'
|
|
html += "<";
|
|
break;
|
|
|
|
case 0x3E: // '>'
|
|
html += ">";
|
|
break;
|
|
|
|
default:
|
|
html += String.fromCharCode(line[idx + i]);
|
|
break;
|
|
}
|
|
}
|
|
html += "</span>";
|
|
return html;
|
|
}
|
|
|
|
|
|
Terminal.prototype.UpdateRowTable = function(row) {
|
|
var y = row << 4;
|
|
var line = this.screen[this.brows + row];
|
|
var c = this.color[this.brows + row][0]|0;
|
|
var n = 0;
|
|
var html = "";
|
|
|
|
for (var column = 0; column < this.ncolumns; column++) {
|
|
|
|
var cnew = this.color[this.brows + row][column]|0;
|
|
|
|
if (this.cursorvisible)
|
|
if (row == this.cursory)
|
|
if (column == this.cursorx) {
|
|
cnew |= 0x600;
|
|
}
|
|
|
|
if (c != cnew) {
|
|
html += this.GetSpan(c, line, column - n, n);
|
|
c = cnew;
|
|
n = 0;
|
|
}
|
|
n++;
|
|
}
|
|
html += this.GetSpan(c, line, column - n, n);
|
|
this.rowelements[this.nrows - row - 1].innerHTML = html;
|
|
|
|
};
|
|
|
|
Terminal.prototype.UpdateRowTableForScroll = function(row) {
|
|
var y = row << 4;
|
|
var line = this.screen[this.brows + row - this.bufferp];
|
|
var c = this.color[this.brows + row - this.bufferp][0]|0;
|
|
var n = 0;
|
|
var html = "";
|
|
|
|
for (var column = 0; column < this.ncolumns; column++) {
|
|
|
|
var cnew = this.color[this.brows + row - this.bufferp][column]|0;
|
|
|
|
if (this.cursorvisible)
|
|
if (row == this.cursory)
|
|
if (column == this.cursorx) {
|
|
cnew |= 0x600;
|
|
}
|
|
|
|
if (c != cnew) {
|
|
html += this.GetSpan(c, line, column - n, n);
|
|
c = cnew;
|
|
n = 0;
|
|
}
|
|
n++;
|
|
}
|
|
html += this.GetSpan(c, line, column - n, n);
|
|
this.rowelements[this.nrows - row - 1].innerHTML = html;
|
|
|
|
};
|
|
|
|
Terminal.prototype.UpdateScreen = function() {
|
|
var nupdated = 0,i = 0;
|
|
for (i = 0; i < this.nrows; i++) {
|
|
if (!this.updaterow[i]) continue;
|
|
if (this.canvas) {
|
|
this.UpdateRowCanvas(i);
|
|
} else {
|
|
this.UpdateRowTable(i);
|
|
}
|
|
nupdated++;
|
|
this.updaterow[i] = 0;
|
|
}
|
|
this.framerequested = false;
|
|
if (nupdated >= (this.nrows-1)) {
|
|
this.timeout = 100;
|
|
} else {
|
|
this.timeout = 30;
|
|
}
|
|
}
|
|
|
|
Terminal.prototype.UpdateScreenForScroll = function() {
|
|
var i;
|
|
if(this.bufferp < this.brows) this.bufferp++;
|
|
else this.bufferp = 0; //show the original state before the scrolling started
|
|
for (i = this.nrows - 1; i >= 0; i--){
|
|
this.UpdateRowTableForScroll(i);
|
|
}
|
|
}
|
|
|
|
Terminal.prototype.PrepareUpdateRow = function(row) {
|
|
this.updaterow[row] = 1;
|
|
if (this.framerequested) return;
|
|
window.setTimeout(this.UpdateScreen.bind(this), this.timeout);
|
|
this.framerequested = true;
|
|
}
|
|
|
|
Terminal.prototype.ScrollDown = function(draw) {
|
|
var tempscreen = this.screen[this.brows + this.scrollbottom];
|
|
var tempcolor = this.color[this.brows + this.scrollbottom];
|
|
|
|
for (var i = this.scrollbottom-1; i >= this.scrolltop; i--) {
|
|
if (i == this.nrows-1) continue;
|
|
this.screen[this.brows + i + 1] = this.screen[this.brows + i];
|
|
this.color[this.brows + i + 1] = this.color[this.brows + i];
|
|
if (draw) this.PrepareUpdateRow(i+1);
|
|
}
|
|
this.screen[this.brows + this.scrolltop] = tempscreen;
|
|
this.color[this.brows + this.scrolltop] = tempcolor;
|
|
this.DeleteRow(this.scrolltop);
|
|
if (draw) this.PrepareUpdateRow(this.scrolltop);
|
|
}
|
|
|
|
Terminal.prototype.ScrollUp = function(draw) {
|
|
var tempscreen = this.screen[this.brows + this.scrolltop];
|
|
var tempcolor = this.color[this.brows + this.scrolltop];
|
|
|
|
for (var i = this.scrolltop+1; i <= this.scrollbottom; i++) {
|
|
if (i == 0) continue;
|
|
this.screen[this.brows + i - 1] = this.screen[this.brows + i];
|
|
this.color[this.brows + i - 1] = this.color[this.brows + i];
|
|
if (draw) this.PrepareUpdateRow(i-1);
|
|
}
|
|
|
|
this.screen[this.brows + this.scrollbottom] = tempscreen;
|
|
this.color[this.brows + this.scrollbottom] = tempcolor;
|
|
this.DeleteRow(this.scrollbottom);
|
|
if (draw) this.PrepareUpdateRow(this.scrollbottom);
|
|
};
|
|
|
|
Terminal.prototype.LineFeed = function() {
|
|
if (this.cursory != this.scrollbottom) {
|
|
this.cursory++;
|
|
if (this.cursorvisible) {
|
|
this.PrepareUpdateRow(this.cursory-1); // delete old cursor position
|
|
this.PrepareUpdateRow(this.cursory); // show new cursor position
|
|
}
|
|
return;
|
|
}
|
|
this.ScrollUp(true);
|
|
};
|
|
|
|
Terminal.prototype.ChangeCursor = function(Numbers) {
|
|
switch (Numbers.length) {
|
|
case 0:
|
|
this.cursorx = 0;
|
|
this.cursory = 0;
|
|
break;
|
|
case 1:
|
|
this.cursory = Numbers[0];
|
|
if (this.cursory) this.cursory--;
|
|
break;
|
|
case 2:
|
|
default:
|
|
// TODO check for boundaries
|
|
this.cursory = Numbers[0];
|
|
this.cursorx = Numbers[1];
|
|
if (this.cursorx) this.cursorx--;
|
|
if (this.cursory) this.cursory--;
|
|
break;
|
|
}
|
|
if (this.cursorx >= this.ncolumns) this.cursorx = this.ncolumns - 1;
|
|
if (this.cursory >= this.nrows) this.cursory = this.nrows - 1;
|
|
};
|
|
|
|
Terminal.prototype.ChangeColor = function(Numbers) {
|
|
|
|
if (Numbers.length == 0) { // reset;
|
|
this.attr_color = 0x7;
|
|
this.attr_reverse = false;
|
|
this.attr_italic = false;
|
|
this.attr_intensity = 1;
|
|
return;
|
|
}
|
|
|
|
var c = this.attr_color;
|
|
|
|
for (var i = 0; i < Numbers.length; i++) {
|
|
switch (Number(Numbers[i])) {
|
|
|
|
case 0: // reset
|
|
c = 0x7;
|
|
this.attr_reverse = false;
|
|
this.attr_italic = false;
|
|
this.attr_intensity = 1;
|
|
break;
|
|
case 1: // brighter foreground color
|
|
this.attr_intensity = 2;
|
|
break;
|
|
case 2: // dimmed foreground color
|
|
this.attr_intensity = 0;
|
|
break;
|
|
case 3: // italic
|
|
this.attr_italic = true;
|
|
break;
|
|
case 4: // underline ignored
|
|
break;
|
|
case 5: // extended colors or blink ignored
|
|
//i++;
|
|
break;
|
|
case 7: // reversed
|
|
this.attr_reverse = true;
|
|
break;
|
|
case 8: // hidden ignored
|
|
break;
|
|
case 10: // reset mapping ?
|
|
break;
|
|
case 21:
|
|
case 22:
|
|
this.attr_intensity = 1;
|
|
break;
|
|
case 23:
|
|
this.attr_italic = false;
|
|
break;
|
|
case 27: // no reverse
|
|
this.attr_reverse = false;
|
|
break;
|
|
|
|
case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37:
|
|
c = c & (0xFFF8) | (Numbers[i] - 30) & 0x7;
|
|
break;
|
|
case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47:
|
|
c = c & (0x00FF) | (((Numbers[i] - 40) & 0x7) << 8);
|
|
break;
|
|
case 39:
|
|
c = c & (0xFF00) | 0x7; // set standard foreground color
|
|
break;
|
|
case 49:
|
|
c = c & 0x00FF; // set standard background color
|
|
break;
|
|
default:
|
|
message.Warning("Color " + Numbers[i] + " not found");
|
|
break;
|
|
}
|
|
}
|
|
this.attr_color = c|0;
|
|
};
|
|
|
|
Terminal.prototype.ChangeMode = function(numbers, question, onoff) {
|
|
|
|
for(var i=0; i<numbers.length; i++) {
|
|
switch(numbers[i]) {
|
|
case 4: // insert mode
|
|
break;
|
|
|
|
case 7: // auto wrap on off
|
|
break;
|
|
|
|
case 25: // cursor on/off
|
|
this.cursortype = onoff;
|
|
break;
|
|
|
|
case 1000: //
|
|
break;
|
|
|
|
case 1006: //
|
|
break;
|
|
|
|
case 1005: //
|
|
break;
|
|
|
|
default:
|
|
message.Warning("Mode term parameter " + this.escapestring + " unknown");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Terminal.prototype.ChangeCursorType = function(numbers, question) {
|
|
if (!question) {
|
|
message.Warning("cursor parameter unknown");
|
|
return;
|
|
}
|
|
|
|
for(var i=0; i<numbers.length; i++) {
|
|
switch(numbers[i]) {
|
|
case 0:
|
|
//this.cursorvisible = false;
|
|
//this.cursortype = 0;
|
|
break;
|
|
case 1:
|
|
//this.cursortype = 1;
|
|
break;
|
|
default:
|
|
message.Warning("Term parameter " + this.escapestring + " unknown");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Terminal.prototype.HandleEscapeSequence = function() {
|
|
//message.Debug("Escape sequence:'" + this.escapestring+"'");
|
|
var i = 0;
|
|
if (this.escapestring == "[J") {
|
|
this.DeleteArea(this.cursory, this.cursorx, this.cursory, this.ncolumns - 1);
|
|
this.DeleteArea(this.cursory + 1, 0., this.nrows - 1, this.ncolumns - 1);
|
|
return;
|
|
} else
|
|
if (this.escapestring == "M") {
|
|
this.ScrollDown(true);
|
|
return;
|
|
}
|
|
// Testing for [x;y;z
|
|
var s = this.escapestring;
|
|
|
|
if (s.charAt(0) != "[") {
|
|
message.Warning("Short escape sequence unknown:'" + this.escapestring + "'");
|
|
return; // the short escape sequences must be handled earlier
|
|
}
|
|
|
|
s = s.substr(1); // delete first sign
|
|
|
|
var lastsign = s.substr(s.length - 1); // extract command
|
|
s = s.substr(0, s.length - 1); // remove command
|
|
|
|
var question = false;
|
|
if (s.charAt(0) == '?') {
|
|
question = true;
|
|
s = s.substr(1); // delete question mark
|
|
}
|
|
|
|
var numbers = s.split(";"); // if there are multiple numbers, split them
|
|
if (numbers[0].length == 0) {
|
|
numbers = [];
|
|
}
|
|
|
|
|
|
// the array must contain of numbers and not strings. Make this sure
|
|
for (i=0; i<numbers.length; i++) {
|
|
numbers[i] = Number(numbers[i]);
|
|
}
|
|
|
|
var oldcursory = this.cursory; // save current cursor position
|
|
var count = 0;
|
|
switch(lastsign) {
|
|
|
|
case 'l':
|
|
this.ChangeMode(numbers, question, true);
|
|
return;
|
|
|
|
case 'h':
|
|
this.ChangeMode(numbers, question, false);
|
|
return;
|
|
|
|
case 'c':
|
|
this.ChangeCursorType(numbers, question);
|
|
return;
|
|
}
|
|
|
|
if (question) {
|
|
message.Warning("Escape sequence unknown:'" + this.escapestring + "'");
|
|
return;
|
|
}
|
|
|
|
switch(lastsign) {
|
|
case 'm': // colors
|
|
this.ChangeColor(numbers);
|
|
return;
|
|
|
|
case 'A': // move cursor up
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
this.cursory -= count;
|
|
break;
|
|
|
|
case 'B': // move cursor down
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
this.cursory += count;
|
|
break;
|
|
|
|
case 'C': // move cursor right
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
this.cursorx += count;
|
|
break;
|
|
|
|
case 'D': // move cursor left
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
this.cursorx -= count;
|
|
if (this.cursorx < 0) this.cursorx = 0;
|
|
break;
|
|
|
|
case 'E': // move cursor down
|
|
count = numbers.length ? numbers[0] : 1;
|
|
this.cursory += count;
|
|
this.cursorx = 0;
|
|
break;
|
|
|
|
case 'F': // move cursor up
|
|
count = numbers.length ? numbers[0] : 1;
|
|
this.cursory -= count;
|
|
if (this.cursory < 0) this.cursory = 0;
|
|
this.cursorx = 0;
|
|
break;
|
|
|
|
case 'G': // change cursor column
|
|
count = numbers.length ? numbers[0] : 1;
|
|
this.cursorx = count;
|
|
if (this.cursorx) this.cursorx--;
|
|
break;
|
|
|
|
case 'H': // cursor position
|
|
case 'd':
|
|
case 'f':
|
|
this.ChangeCursor(numbers);
|
|
break;
|
|
|
|
case 'K': // erase
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (!numbers.length) {
|
|
this.DeleteArea(this.cursory, this.cursorx, this.cursory, this.ncolumns - 1);
|
|
} else
|
|
if (numbers[0] == 1) {
|
|
this.DeleteArea(this.cursory, 0., this.cursory, this.cursorx);
|
|
} else
|
|
if (numbers[0] == 2) {
|
|
this.DeleteRow(this.cursory);
|
|
}
|
|
break;
|
|
|
|
case 'L': // scroll down
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
var top = this.scrolltop;
|
|
this.scrolltop = this.cursory;
|
|
if (count == 1) {
|
|
this.ScrollDown(true);
|
|
} else {
|
|
for (var j = 0; j < count-1; j++) {
|
|
this.ScrollDown(false);
|
|
}
|
|
this.ScrollDown(true);
|
|
}
|
|
this.scrolltop = top;
|
|
break;
|
|
|
|
case 'M': // scroll up
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
var top = this.scrolltop;
|
|
this.scrolltop = this.cursory;
|
|
if (count == 1) {
|
|
this.ScrollUp(true);
|
|
} else {
|
|
for (var j = 0; j < count-1; j++) {
|
|
this.ScrollUp(false);
|
|
}
|
|
this.ScrollUp(true);
|
|
}
|
|
this.scrolltop = top;
|
|
break;
|
|
|
|
case 'P': /* shift left from cursor and fill with zero */
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
var n = 0;n
|
|
for (var j = this.cursorx+count; j < this.ncolumns; j++) {
|
|
this.screen[this.brows + this.cursory][this.cursorx+n] = this.screen[this.brows + this.cursory][j];
|
|
this.color[this.brows + this.cursory][this.cursorx+n] = this.color[this.brows + this.cursory][j];
|
|
n++;
|
|
}
|
|
this.DeleteArea(this.cursory, this.ncolumns-count, this.cursory, this.ncolumns-1);
|
|
this.PrepareUpdateRow(this.cursory);
|
|
break;
|
|
|
|
case 'r': // set scrolling region
|
|
if (numbers.length == 0) {
|
|
this.scrolltop = 0;
|
|
this.scrollbottom = this.nrows-1;
|
|
} else {
|
|
this.scrolltop = numbers[0];
|
|
this.scrollbottom = numbers[1];
|
|
if (this.scrolltop) this.scrolltop--;
|
|
if (this.scrollbottom) this.scrollbottom--;
|
|
}
|
|
return;
|
|
|
|
case 'X': // erase only number of characters in current line
|
|
count = numbers.length ? numbers[0] : 1;
|
|
if (count == 0) count = 1;
|
|
for (var j = 0; j < count; j++) {
|
|
this.screen[this.brows + this.cursory][this.cursorx+j] = 0x20;
|
|
this.color[this.brows + this.cursory][this.cursorx+j] = this.GetColor();
|
|
}
|
|
this.PrepareUpdateRow(this.cursory);
|
|
break;
|
|
|
|
default:
|
|
message.Warning("Escape sequence unknown:'" + this.escapestring + "'");
|
|
break;
|
|
}
|
|
|
|
if (this.cursorvisible) {
|
|
this.PrepareUpdateRow(this.cursory);
|
|
if (this.cursory != oldcursory) {
|
|
this.PrepareUpdateRow(oldcursory);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
Terminal.prototype.PutChar = function(c) {
|
|
var i = 0;
|
|
//message.Debug("Char:" + c + " " + String.fromCharCode(c));
|
|
// escape sequence (CS)
|
|
if (this.escapetype == 2) {
|
|
this.escapestring += String.fromCharCode(c);
|
|
if ((c >= 64) && (c <= 126)) {
|
|
this.HandleEscapeSequence();
|
|
this.escapetype = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// escape sequence
|
|
if ((this.escapetype == 0) && (c == 0x1B)) {
|
|
this.escapetype = 1;
|
|
this.escapestring = "";
|
|
return;
|
|
}
|
|
|
|
// starting escape sequence
|
|
if (this.escapetype == 1) {
|
|
this.escapestring += String.fromCharCode(c);
|
|
// Control Sequence Introducer ([)
|
|
if (c == 0x5B) {
|
|
this.escapetype = 2;
|
|
return;
|
|
}
|
|
this.HandleEscapeSequence();
|
|
this.escapetype = 0;
|
|
return;
|
|
}
|
|
switch (c) {
|
|
case 0xA:
|
|
// line feed
|
|
this.LineFeed();
|
|
this.OnCharReceived("\n");
|
|
return;
|
|
case 0xD:
|
|
// carriage return
|
|
this.cursorx = 0;
|
|
this.PrepareUpdateRow(this.cursory);
|
|
return;
|
|
case 0x7:
|
|
// beep
|
|
return;
|
|
case 0x8:
|
|
// back space
|
|
this.cursorx--;
|
|
if (this.cursorx < 0) {
|
|
this.cursorx = 0;
|
|
}
|
|
this.PrepareUpdateRow(this.cursory);
|
|
return;
|
|
case 0x9:
|
|
// horizontal tab
|
|
var spaces = 8 - (this.cursorx&7);
|
|
do
|
|
{
|
|
if (this.cursorx >= this.ncolumns) {
|
|
this.PrepareUpdateRow(this.cursory);
|
|
this.LineFeed();
|
|
this.cursorx = 0;
|
|
}
|
|
this.screen[this.brows + this.cursory][this.cursorx] = 0x20;
|
|
this.color[this.brows + this.cursory][this.cursorx] = this.attr_color;
|
|
this.cursorx++;
|
|
} while(spaces--);
|
|
this.PrepareUpdateRow(this.cursory);
|
|
return;
|
|
|
|
case 0x00: case 0x01: case 0x02: case 0x03:
|
|
case 0x04: case 0x05: case 0x06: case 0x0B:
|
|
case 0x0C: case 0x0E: case 0x0F:
|
|
case 0x10: case 0x11: case 0x12: case 0x13:
|
|
case 0x14: case 0x15: case 0x16: case 0x17:
|
|
case 0x18: case 0x19: case 0x1A: case 0x1B:
|
|
case 0x1C: case 0x1D: case 0x1E: case 0x1F:
|
|
case 0x7F:
|
|
message.Warning("unknown character " + c);
|
|
return;
|
|
}
|
|
|
|
if (this.cursorx >= this.ncolumns) {
|
|
this.LineFeed();
|
|
this.cursorx = 0;
|
|
}
|
|
|
|
c = this.utf8converter.Put(c);
|
|
if (c == -1) return;
|
|
var cx = this.cursorx;
|
|
var cy = this.cursory;
|
|
this.screen[this.brows + cy][cx] = c;
|
|
|
|
this.color[this.brows + cy][cx] = this.GetColor();
|
|
this.cursorx++;
|
|
//message.Debug("Write: " + String.fromCharCode(c));
|
|
this.PrepareUpdateRow(cy);
|
|
|
|
this.OnCharReceived(String.fromCharCode(c));
|
|
};
|
|
|
|
module.exports = Terminal;
|
|
|
|
},{"../../lib/utf8":2,"../messagehandler":9}],9:[function(require,module,exports){
|
|
// -------------------------------------------------
|
|
// ------------- MessageHandler --------------------
|
|
// -------------------------------------------------
|
|
|
|
"use strict";
|
|
|
|
var worker;
|
|
|
|
var run = true;
|
|
|
|
function Send(command, data) {
|
|
worker.postMessage(
|
|
{
|
|
"command" : command,
|
|
"data" : data
|
|
}
|
|
);
|
|
}
|
|
|
|
function Debug(message) {
|
|
console.log(message);
|
|
}
|
|
|
|
function Abort() {
|
|
Debug("Master: Abort execution.");
|
|
run = false;
|
|
Send("Abort", {});
|
|
throw new Error('Kill master');
|
|
}
|
|
|
|
function DoError(message) {
|
|
Send("Debug", "Error: " + message);
|
|
Abort();
|
|
}
|
|
|
|
function Warning(message) {
|
|
Send("Debug", "Warning: " + message);
|
|
}
|
|
|
|
var messagemap = new Object();
|
|
function Register(message, OnReceive) {
|
|
messagemap[message] = OnReceive;
|
|
}
|
|
|
|
// this is a global object of the worker
|
|
function OnMessage(e) {
|
|
var command = e.data.command;
|
|
|
|
// Debug Messages are always allowed
|
|
if (command == "Debug") {
|
|
messagemap[command](e.data.data);
|
|
return;
|
|
}
|
|
|
|
if (!run) return;
|
|
if (typeof messagemap[command] == 'function') {
|
|
try {
|
|
messagemap[command](e.data.data);
|
|
} catch (error) {
|
|
Debug("Master: Unhandled exception in command \"" + command + "\": " + error.message);
|
|
run = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function SetWorker(_worker) {
|
|
worker = _worker;
|
|
worker.onmessage = OnMessage;
|
|
worker.onerror = function(e) {
|
|
Debug("Error at " + e.filename + ":" + e.lineno + ": " + e.message);
|
|
Abort();
|
|
}
|
|
Register("Abort", function(){Debug("Master: Received abort signal from worker"); run=false;});
|
|
Register("Debug", function(d){Debug(d);});
|
|
}
|
|
|
|
module.exports.SetWorker = SetWorker;
|
|
module.exports.Register = Register;
|
|
module.exports.Debug = Debug;
|
|
module.exports.Warning = Warning;
|
|
module.exports.Error = DoError;
|
|
module.exports.Abort = Abort;
|
|
module.exports.Send = Send;
|
|
|
|
|
|
},{}],10:[function(require,module,exports){
|
|
// -------------------------------------------------
|
|
// -------------------- Master ---------------------
|
|
// -------------------------------------------------
|
|
|
|
var Terminal = require('./dev/terminal');
|
|
var TerminalInput = require('./dev/terminal-input');
|
|
var Framebuffer = require('./dev/framebuffer');
|
|
var Ethernet = require('./dev/ethernet');
|
|
var LoopSoundBuffer = require('./dev/sound');
|
|
var Filesystem = require('./dev/filesystem');
|
|
var utils = require('./utils');
|
|
var message = require('./messagehandler');
|
|
|
|
var TERMINAL = 0xDEADBEEF;
|
|
|
|
"use strict";
|
|
|
|
|
|
function jor1kGUI(parameters)
|
|
{
|
|
this.params = parameters;
|
|
this.message = message;
|
|
|
|
// --- parameters parsing ---
|
|
this.params.system = this.params.system || {};
|
|
|
|
this.params.path = this.params.path || "";
|
|
|
|
this.params.system.kernelURL = this.params.system.kernelURL || "vmlinux.bin.bz2";
|
|
this.params.system.memorysize = this.params.system.memorysize || 32;
|
|
this.params.system.arch = this.params.system.arch || "or1k";
|
|
this.params.system.cpu = this.params.system.cpu || "asm";
|
|
this.params.system.ncores = this.params.system.ncores || 1;
|
|
this.params.syncURL = this.params.syncURL || "";
|
|
|
|
this.params.fs = this.params.fs || {};
|
|
this.params.fs.basefsURL = this.params.fs.basefsURL || "basefs.json";
|
|
this.params.fs.earlyload = this.params.fs.earlyload || [];
|
|
this.params.fs.lazyloadimages = this.params.fs.lazyloadimages || [];
|
|
|
|
// add path to every URL
|
|
this.params.system.kernelURL = this.params.path + this.params.system.kernelURL;
|
|
this.params.fs.basefsURL = this.params.path + this.params.fs.basefsURL;
|
|
if (this.params.fs.extendedfsURL) {
|
|
this.params.fs.extendedfsURL = this.params.path + this.params.fs.extendedfsURL;
|
|
}
|
|
|
|
this.params.userid = this.params.userid || "";
|
|
|
|
// ----------------------
|
|
|
|
this.worker = (this.params.worker instanceof Worker) ?
|
|
this.params.worker : new Worker("jor1k-worker-min.js");
|
|
|
|
message.SetWorker(this.worker);
|
|
|
|
// ----
|
|
|
|
if (this.params.clipboardid) {
|
|
this.clipboard = document.getElementById(this.params.clipboardid);
|
|
}
|
|
|
|
if (this.params.statsid) {
|
|
this.stats = document.getElementById(this.params.statsid);
|
|
}
|
|
|
|
if (this.params.fbid) {
|
|
this.framebuffer = new Framebuffer(this.params.fbid, this.params.fps);
|
|
message.Register("GetFB", this.framebuffer.Update.bind(this.framebuffer));
|
|
}
|
|
|
|
this.terms = [];
|
|
if (this.params.term) {
|
|
this.terms = [this.params.term];
|
|
} else if (this.params.terms) {
|
|
this.terms = this.params.terms.slice(0, 2); // support up to 2 terminals
|
|
}
|
|
for (var i = 0; i < this.terms.length; i++) {
|
|
this.terms[i].Init(this, "tty" + i);
|
|
}
|
|
|
|
this.activeTTY = "tty0";
|
|
this.terminput = new TerminalInput(this.SendChars.bind(this));
|
|
|
|
this.fs = new Filesystem(this.params.syncURL, this.params.userid);
|
|
|
|
this.sound = new LoopSoundBuffer(22050);
|
|
message.Register("sound", this.sound.AddBuffer.bind(this.sound));
|
|
message.Register("sound.rate", this.sound.SetRate.bind(this.sound));
|
|
|
|
if (this.clipboard) {
|
|
this.clipboard.onpaste = function(event) {
|
|
this.clipboard.value = "";
|
|
setTimeout(this.SendClipboard.bind(this), 4);
|
|
}.bind(this);
|
|
|
|
|
|
this.SendClipboard = function() {
|
|
var chars = [];
|
|
var v = this.clipboard.value;
|
|
|
|
for(var i=0; i<v.length; i++) {
|
|
chars.push(v.charCodeAt(i));
|
|
}
|
|
|
|
this.SendChars(chars);
|
|
this.clipboard.value = "";
|
|
}.bind(this);
|
|
}
|
|
|
|
this.IgnoreKeys = function() {
|
|
return (
|
|
(this.lastMouseDownTarget != TERMINAL) &&
|
|
(this.framebuffer ? this.lastMouseDownTarget != this.framebuffer.fbcanvas : true) &&
|
|
(this.lastMouseDownTarget != this.clipboard)
|
|
);
|
|
}
|
|
|
|
var recordTarget = function(event) {
|
|
var termHitByEvent = false;
|
|
for (var i = 0; i < this.terms.length; i++) {
|
|
if (this.terms[i].WasHitByEvent(event)) {
|
|
termHitByEvent = true;
|
|
this.activeTTY = "tty" + i;
|
|
break;
|
|
}
|
|
}
|
|
if (termHitByEvent)
|
|
this.lastMouseDownTarget = TERMINAL;
|
|
else
|
|
this.lastMouseDownTarget = event.target;
|
|
}.bind(this);
|
|
|
|
if(document.addEventListener)
|
|
document.addEventListener('mousedown', recordTarget, false);
|
|
else
|
|
Window.onmousedown = recordTarget; // IE 10 support (untested)
|
|
|
|
|
|
document.onkeypress = function(event) {
|
|
if(this.IgnoreKeys()) return true;
|
|
if ((this.lastMouseDownTarget == TERMINAL) || (this.lastMouseDownTarget == this.clipboard)) {
|
|
return this.terminput.OnKeyPress(event);
|
|
}
|
|
message.Send("keypress", {keyCode:event.keyCode, charCode:event.charCode});
|
|
return false;
|
|
}.bind(this);
|
|
|
|
document.onkeydown = function(event) {
|
|
if(this.IgnoreKeys()) return true;
|
|
if ((this.lastMouseDownTarget == TERMINAL) || (this.lastMouseDownTarget == this.clipboard)) {
|
|
return this.terminput.OnKeyDown(event);
|
|
}
|
|
message.Send("keydown", {keyCode:event.keyCode, charCode:event.charCode});
|
|
return false;
|
|
}.bind(this);
|
|
|
|
document.onkeyup = function(event) {
|
|
if(this.IgnoreKeys()) return true;
|
|
if ((this.lastMouseDownTarget == TERMINAL) || (this.lastMouseDownTarget == this.clipboard)) {
|
|
return this.terminput.OnKeyUp(event);
|
|
}
|
|
message.Send("keyup", {keyCode:event.keyCode, charCode:event.charCode});
|
|
return false;
|
|
}.bind(this);
|
|
|
|
if (this.params.relayURL) {
|
|
this.ethernet = new Ethernet(this.params.relayURL);
|
|
this.ethernet.onmessage = function(e) {
|
|
message.Send("ethmac", e.data);
|
|
}.bind(this);
|
|
message.Register("ethmac", this.ethernet.SendFrame.bind(this.ethernet));
|
|
}
|
|
|
|
message.Register("GetIPS", this.ShowIPS.bind(this));
|
|
message.Register("execute", this.Execute.bind(this));
|
|
message.Register("WorkerReady", this.OnWorkerReady.bind(this));
|
|
}
|
|
|
|
jor1kGUI.prototype.OnWorkerReady = function() {
|
|
this.Reset();
|
|
window.setInterval(function() {
|
|
message.Send("GetIPS", 0);
|
|
}, 1000);
|
|
};
|
|
|
|
// this command is send back and forth to be responsive
|
|
jor1kGUI.prototype.Execute = function() {
|
|
if (this.stop) return;
|
|
if(this.userpaused) {
|
|
this.executepending = true;
|
|
} else {
|
|
this.executepending = false;
|
|
message.Send("execute", 0);
|
|
}
|
|
};
|
|
|
|
jor1kGUI.prototype.ShowIPS = function(ips) {
|
|
if (!this.stats) return;
|
|
if (this.userpaused) {
|
|
this.stats.innerHTML = "Paused";
|
|
} else {
|
|
this.stats.innerHTML = ips<1000000?
|
|
Math.floor(ips/1000) + " KIPS"
|
|
:
|
|
(Math.floor(ips/100000)/10.) + " MIPS";
|
|
}
|
|
};
|
|
|
|
|
|
jor1kGUI.prototype.ChangeCore = function(core) {
|
|
message.Send("ChangeCore", core);
|
|
};
|
|
|
|
|
|
jor1kGUI.prototype.Reset = function () {
|
|
this.stop = false; // VM Stopped/Aborted
|
|
this.userpaused = false;
|
|
this.executepending = false; // if we rec an execute message while paused
|
|
|
|
message.Send("Init", this.params.system);
|
|
message.Send("Reset");
|
|
message.Send("LoadAndStart", this.params.system.kernelURL);
|
|
message.Send("LoadFilesystem", this.params.fs);
|
|
|
|
if (this.terms.length > 0) {
|
|
this.terms.forEach(function (term) {
|
|
term.PauseBlink(false);
|
|
});
|
|
this.lastMouseDownTarget = TERMINAL;
|
|
// activeTTY remains the same, so the user can start typing into the terminal last used
|
|
// or the default terminal initialized in the constructor
|
|
}
|
|
}
|
|
|
|
jor1kGUI.prototype.Pause = function(pause) {
|
|
pause = !! pause; // coerce to boolean
|
|
if(pause == this.userpaused) return;
|
|
this.userpaused = pause;
|
|
if(! this.userpaused && this.executepending) {
|
|
this.executepending = false;
|
|
message.Send("execute", 0);
|
|
}
|
|
this.terms.forEach(function (term) {
|
|
term.PauseBlink(pause);
|
|
});
|
|
}
|
|
|
|
// sends the input characters for the terminal
|
|
jor1kGUI.prototype.SendChars = function(chars) {
|
|
if (this.lastMouseDownTarget == this.fbcanvas) return;
|
|
message.Send(this.activeTTY, chars);
|
|
message.Send("htif.term0.Transfer", chars);
|
|
}
|
|
|
|
// Returns the terminal attached to tty
|
|
// tty is the tty string, for example, tty0
|
|
jor1kGUI.prototype.GetTerm = function(tty) {
|
|
var index = parseInt(tty.slice(3));
|
|
return this.terms[index];
|
|
}
|
|
|
|
jor1kGUI.prototype.FocusTerm = function(tty) {
|
|
this.activeTTY = tty;
|
|
this.lastMouseDownTarget = TERMINAL;
|
|
}
|
|
|
|
module.exports = jor1kGUI;
|
|
|
|
},{"./dev/ethernet":3,"./dev/filesystem":4,"./dev/framebuffer":5,"./dev/sound":6,"./dev/terminal":8,"./dev/terminal-input":7,"./messagehandler":9,"./utils":11}],11:[function(require,module,exports){
|
|
// -------------------------------------------------
|
|
// --------------------- Utils ---------------------
|
|
// -------------------------------------------------
|
|
|
|
"use strict";
|
|
|
|
function UploadBinaryResource(url, filename, data, OnSuccess, OnError) {
|
|
|
|
var boundary = "xxxxxxxxx";
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('post', url, true);
|
|
xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary=" + boundary);
|
|
xhr.setRequestHeader("Content-Length", data.length);
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState != 4) {
|
|
return;
|
|
}
|
|
if ((xhr.status != 200) && (xhr.status != 0)) {
|
|
OnError("Error: Could not upload file " + filename);
|
|
return;
|
|
}
|
|
OnSuccess(this.responseText);
|
|
};
|
|
|
|
var bodyheader = "--" + boundary + "\r\n";
|
|
bodyheader += 'Content-Disposition: form-data; name="uploaded"; filename="' + filename + '"\r\n';
|
|
bodyheader += "Content-Type: application/octet-stream\r\n\r\n";
|
|
|
|
var bodyfooter = "\r\n";
|
|
bodyfooter += "--" + boundary + "--";
|
|
|
|
var newdata = new Uint8Array(data.length + bodyheader.length + bodyfooter.length);
|
|
var offset = 0;
|
|
for(var i=0; i<bodyheader.length; i++)
|
|
newdata[offset++] = bodyheader.charCodeAt(i);
|
|
|
|
for(var i=0; i<data.length; i++)
|
|
newdata[offset++] = data[i];
|
|
|
|
for(var i=0; i<bodyfooter.length; i++)
|
|
newdata[offset++] = bodyfooter.charCodeAt(i);
|
|
|
|
xhr.send(newdata.buffer);
|
|
}
|
|
|
|
module.exports.UploadBinaryResource = UploadBinaryResource;
|
|
|
|
},{}],"Jor1k":[function(require,module,exports){
|
|
var Jor1k = require('./system');
|
|
|
|
module.exports = Jor1k;
|
|
|
|
},{"./system":10}],"LinuxTerm":[function(require,module,exports){
|
|
var Terminal = require("../master/dev/terminal");
|
|
|
|
function LinuxTerm(termElementId) {
|
|
this.termElementId = termElementId;
|
|
}
|
|
|
|
LinuxTerm.prototype.Init = function(jor1kGUI, tty) {
|
|
this.term = new Terminal(26, 90, this.termElementId);
|
|
// this.term = new Terminal(26, 90, this.termElementId);
|
|
// this.term = new Terminal(22, 75, this.termElementId);
|
|
jor1kGUI.message.Register(tty, function(d) {
|
|
d.forEach(function(c) {
|
|
this.term.PutChar(c&0xFF);
|
|
}.bind(this));
|
|
}.bind(this));
|
|
|
|
this.terminalcanvas = document.getElementById(this.termElementId);
|
|
this.terminalcanvas.onmousedown = function(event) {
|
|
if (!jor1kGUI.framebuffer) return;
|
|
jor1kGUI.framebuffer.fbcanvas.style.border = "2px solid #000000";
|
|
}.bind(this);
|
|
}
|
|
|
|
LinuxTerm.prototype.WasHitByEvent = function(evt) {
|
|
return this.terminalcanvas.contains(evt.target);
|
|
}
|
|
|
|
LinuxTerm.prototype.PauseBlink = function(pause) {
|
|
this.term.PauseBlink(pause);
|
|
}
|
|
|
|
LinuxTerm.prototype.SetCharReceiveListener = function (callback) {
|
|
this.term.OnCharReceived = callback;
|
|
}
|
|
|
|
LinuxTerm.prototype.RemoveCharReceiveListener = function () {
|
|
this.term.OnCharReceived = function (){};
|
|
}
|
|
|
|
module.exports = LinuxTerm;
|
|
|
|
},{"../master/dev/terminal":8}]},{},[]);
|