rlgwebd/shterm.js

420 lines
9.9 KiB
JavaScript
Raw Normal View History

2012-05-06 09:04:14 -07:00
/* shterm.js: browser-side JavaScript to handle I/O for termemu.js when it
* is running a shell via the webtty.js server.
*/
// A state machine that keeps track of polling the server.
var ajaxstate = {
state: 0,
timerID: null,
clear: function () {
if (this.timerID != null) {
window.clearTimeout(this.timerID);
this.timerID = null;
}
},
set: function (ms) {
this.clear();
this.timerID = window.setTimeout(getData, ms);
},
gotdata: function () {
this.set(100);
this.state = 0;
},
gotnothing: function () {
if (this.state == 0) {
this.set(100);
this.state = 1;
}
else if (this.state == 1) {
this.set(300);
this.state = 2;
}
else if (this.state == 2) {
this.set(1000);
this.state = 3;
}
else {
this.set(5000);
this.state = 3;
}
},
posted: function () {
this.set(100);
this.state = 0;
}
};
function writeData(hexstr) {
var codenum;
var codes = [];
var nc;
var u8wait = 0; /* Stores bits from previous bytes of multibyte sequences. */
var expect = 0; /* The number of 10------ bytes expected. */
/* UTF-8 translation. */
for (var i = 0; i < hexstr.length; i += 2) {
nc = Number("0x" + hexstr.substr(i, 2));
if (nc < 0x7F) {
/* 0------- */
codes.push(nc);
/* Any incomplete sequence will be discarded. */
u8wait = 0;
expect = 0;
}
else if (nc < 0xC0) {
/* 10------ : part of a multibyte sequence */
if (expect > 0) {
u8wait <<= 6;
u8wait += (nc & 0x3F);
expect--;
if (expect == 0) {
codes.push(u8wait);
u8wait = 0;
}
}
else {
/* Assume an initial byte was missed. */
u8wait = 0;
}
}
/* These will all discard any incomplete sequence. */
else if (nc < 0xE0) {
/* 110----- : introduces 2-byte sequence */
u8wait = (nc & 0x1F);
expect = 1;
}
else if (nc < 0xF0) {
/* 1110---- : introduces 3-byte sequence */
u8wait = (nc & 0x0F);
expect = 2;
}
else if (nc < 0xF8) {
/* 11110--- : introduces 4-byte sequence */
u8wait = (nc & 0x07);
expect = 3;
}
else if (nc < 0xFC) {
/* 111110-- : introduces 5-byte sequence */
u8wait = (nc & 0x03);
expect = 4;
}
else if (nc < 0xFE) {
/* 1111110- : introduces 6-byte sequence */
u8wait = (nc & 0x01);
expect = 5;
}
else {
/* 1111111- : should never appear */
u8wait = 0;
expect = 0;
}
/* Supporting all 31 bits is probably overkill... */
}
termemu.write(codes);
return;
}
function getData() {
if (!termemu.alive)
return;
var datareq = new XMLHttpRequest();
datareq.onreadystatechange = function () {
if (datareq.readyState == 4 && datareq.status == 200) {
var response = JSON.parse(this.responseText);
if (!response.t)
return;
else if (response.t == "E") {
if (response.c == 1) {
termemu.alive = false;
debug(1, "Server error: " + response.s);
}
}
else if (response.t == "n")
ajaxstate.gotnothing();
else if (response.t == "d") {
writeData(response.d);
ajaxstate.gotdata();
}
return;
}
};
datareq.open('GET', '/feed', true);
datareq.send(null);
return;
}
function postResponseHandler() {
if (this.readyState == 4 && this.status == 200) {
var response = JSON.parse(this.responseText);
if (!response.t || response.t == "n")
return;
else if (response.t == "E") {
if (response.c == 1) {
termemu.alive = false;
debug(1, "Server error: " + response.s);
}
return;
}
else if (response.t != "d")
return;
/* It is a data message */
if (response.d) {
writeData(response.d);
}
ajaxstate.posted();
return;
}
}
function sendback(str) {
/* For responding to terminal queries. */
var datareq = new XMLHttpRequest();
datareq.onreadystatechange = postResponseHandler;
datareq.open('POST', '/feed', true);
datareq.send("keys=" + str);
return;
}
function sendkey(ev) {
var keynum = ev.keyCode;
var code;
if (keynum >= ev.DOM_VK_A && keynum <= ev.DOM_VK_Z) {
/* Letters. This assumes the codes are 65-90. */
if (ev.ctrlKey)
keynum -= 64;
else if (!ev.shiftKey)
keynum += 32;
code = keynum.toString(16);
if (code.length < 2)
code = "0" + code;
}
else if (keynum >= ev.DOM_VK_0 && keynum <= ev.DOM_VK_9) {
/* The number row. */
if (ev.shiftKey) {
code = numShifts[keynum - 48].toString(16);
}
else {
code = keynum.toString(16);
}
}
else if (keynum in keyHexCodes) {
if (ev.shiftKey)
code = keyHexCodes[keynum][1];
else
code = keyHexCodes[keynum][0];
}
else if (keynum == ev.DOM_VK_SHIFT || keynum == ev.DOM_VK_CONTROL ||
keynum == ev.DOM_VK_ALT || keynum == ev.DOM_VK_CAPS_LOCK) {
return;
}
else {
debug(1, "Ignoring keycode " + keynum);
return;
}
if (termemu.alive)
ev.preventDefault();
var datareq = new XMLHttpRequest();
datareq.onreadystatechange = postResponseHandler;
datareq.open('POST', '/feed', true);
datareq.send("keys=" + code);
//dkey(code);
return;
}
var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c",
';': "3a", '\'': "22", ',': "3c", '.': "3e", '/': "3f", '`': "7e"
}
function vkey(c) {
var keystr;
if (c.match(/^[a-z]$/)) {
if (termemu.ctrlp()) {
var n = c.charCodeAt(0) - 96;
keystr = n.toString(16);
if (keystr.length < 2)
keystr = "0" + keystr;
}
else if (termemu.shiftp())
keystr = c.toUpperCase().charCodeAt(0).toString(16);
else
keystr = c.charCodeAt(0).toString(16);
}
else if (c.match(/^[0-9]$/)) {
if (termemu.shiftp())
keystr = numShifts[c.charCodeAt(0) - 48].toString(16);
else
keystr = c.charCodeAt(0).toString(16);
}
else if (c == '\n')
keystr = "0a";
else if (c == '\t')
keystr = "09";
else if (c == '\b')
keystr = "08";
else if (c == ' ')
keystr = "20";
else if (c in charshifts) {
if (termemu.shiftp())
keystr = charshifts[c];
else
keystr = c.charCodeAt(0).toString(16);
}
else
return;
//writeData("Sending " + keystr);
var datareq = new XMLHttpRequest();
datareq.onreadystatechange = postResponseHandler;
datareq.open('POST', '/feed', true);
datareq.send("keys=" + keystr);
return;
}
function setup() {
keyHexCodes.init();
2012-05-09 13:38:05 -07:00
termemu.init("termwrap", 24, 80);
setTitle("Not connected.");
return;
}
function toggleshift() {
termemu.toggleshift();
keydiv = document.getElementById("shiftkey");
if (termemu.shiftp())
keydiv.className = "keysel";
else
keydiv.className = "key";
return;
}
function togglectrl() {
termemu.togglectrl();
keydiv = document.getElementById("ctrlkey");
if (termemu.ctrlp())
keydiv.className = "keysel";
else
keydiv.className = "key";
return;
}
2012-05-09 13:38:05 -07:00
function login(h, w) {
if (termemu.alive)
return;
var req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.readyState == 4 && req.status == 200) {
var logindict = JSON.parse(req.responseText);
if (logindict.login) {
/* Success */
termemu.resize(logindict.h, logindict.w);
termemu.alive = true;
setTitle("Logged in");
debug(1, "Logged in with id " + logindict.id);
getData();
return;
}
return;
}
};
req.open('POST', '/login', true);
2012-05-09 13:38:05 -07:00
req.send("login=login&h=" + String(h) + "&w=" + String(w));
return;
}
function stop() {
var req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.readyState == 4 && req.status == 200) {
/* Figure out whether or not it worked. */
/* FIXME the server might respond with output. */
termemu.alive = false;
return;
}
};
req.open('POST', '/feed', true);
req.send("quit=quit");
return;
}
function setTitle(tstr) {
var titlespan = document.getElementById("ttitle");
var tnode = document.createTextNode(tstr);
if (titlespan.childNodes.length == 0)
titlespan.appendChild(tnode);
else
titlespan.replaceChild(tnode, titlespan.childNodes[0]);
return;
}
function debug(level, msg) {
if (level < debugSuppress)
return;
var msgdiv = document.createElement("div");
var msgtext = document.createTextNode(msg);
msgdiv.appendChild(msgtext);
document.getElementById("debug").appendChild(msgdiv);
return;
}
/* This should be a termemu method. */
function textsize(larger) {
var cssSize = document.getElementById("term").style.fontSize;
if (!cssSize)
return;
var match = cssSize.match(/\d*/);
if (!match)
return;
var csize = Number(match[0]);
var nsize;
if (larger) {
if (csize >= 48)
nsize = 48;
else if (csize >= 20)
nsize = csize + 4;
else if (csize >= 12)
nsize = csize + 2;
else if (csize >= 8)
nsize = csize + 1;
else
nsize = 8;
}
else {
if (csize <= 8)
nsize = 8;
else if (csize <= 12)
nsize = csize - 1;
else if (csize <= 20)
nsize = csize - 2;
else if (csize <= 48)
nsize = csize - 4;
else
nsize = 48;
}
document.getElementById("term").style.fontSize = nsize.toString() + "px";
2012-05-09 13:38:05 -07:00
termemu.fixsize();
debug(1, "Changing font size to " + nsize.toString());
return;
}
function bell(on) {
var imgnode = document.getElementById("bell");
if (on) {
imgnode.style.visibility = "visible";
window.setTimeout(bell, 1500, false);
}
else
imgnode.style.visibility = "hidden";
return;
}
function dkey(codestr) {
var dstr = "Keystring: ";
for (var i = 0; i < codestr.length; i += 2) {
code = Number("0x" + codestr.substr(i, 2));
if (code < 32 || (code >= 127 && code < 160))
dstr += "\\x" + code.toString(16);
else
dstr += String.fromCharCode(code);
}
debug(1, dstr);
return;
}