Switch the client-to-server messages from the HTML forms format to JSON (for the webtty app). Message numbers are sent but not yet used.
430 lines
10 KiB
JavaScript
430 lines
10 KiB
JavaScript
/* shterm.js: browser-side JavaScript to handle I/O for termemu.js when it
|
|
* is running a shell via the webtty.js server.
|
|
*/
|
|
|
|
/* The number of the next packet to send. */
|
|
var nsend = 0;
|
|
|
|
// 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);
|
|
debug(1, "Got packet " + response.n);
|
|
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);
|
|
debug(1, "Got packet " + response.n);
|
|
}
|
|
ajaxstate.posted();
|
|
return;
|
|
}
|
|
}
|
|
|
|
function sendback(str) {
|
|
/* For responding to terminal queries. */
|
|
var formdata = {"t": "d", "n": nsend++, "d": str};
|
|
var datareq = new XMLHttpRequest();
|
|
datareq.onreadystatechange = postResponseHandler;
|
|
datareq.open('POST', '/feed', true);
|
|
datareq.send(JSON.stringify(formdata));
|
|
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 formdata = {"t": "d", "n": nsend++, "d": code};
|
|
var datareq = new XMLHttpRequest();
|
|
datareq.onreadystatechange = postResponseHandler;
|
|
datareq.open('POST', '/feed', true);
|
|
datareq.send(JSON.stringify(formdata));
|
|
//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 formdata = {"t": "d", "n": nsend++, "d": code};
|
|
var datareq = new XMLHttpRequest();
|
|
datareq.onreadystatechange = postResponseHandler;
|
|
datareq.open('POST', '/feed', true);
|
|
datareq.send(JSON.stringify(formdata));
|
|
return;
|
|
}
|
|
|
|
function setup() {
|
|
keyHexCodes.init();
|
|
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;
|
|
}
|
|
|
|
function login(h, w) {
|
|
if (termemu.alive)
|
|
return;
|
|
params = {"login": true, "h": h, "w": w};
|
|
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;
|
|
nsend = 0;
|
|
setTitle("Logged in");
|
|
debug(1, "Logged in with id " + logindict.id);
|
|
getData();
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
req.open('POST', '/login', true);
|
|
req.send(JSON.stringify(params));
|
|
//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(JSON.stringify({"t": "q", "n": nsend++}));
|
|
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";
|
|
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;
|
|
}
|