Mercurial > hg > rlgwebd
view shterm.js @ 60:31bb3cf4f25f
Make sure watchers start with completely drawn screens.
TermSessions now buffer all data since the last screen clear, so new
Watchers can start with complete screens.
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Tue, 19 Jun 2012 19:11:59 -0700 |
parents | 7a50b4412fea |
children | d7eb63cd7a16 |
line wrap: on
line source
/* shterm.js: browser-side JavaScript to handle I/O for termemu.js when it * is running a shell via the webtty.js server. */ var isalive = false; // Whether the session is currently active. var nsend = 0; // The number of the next packet to send. var nrecv = 0; // The next packet expected. var msgQ = []; // Queue for out-of-order messages. // 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 processMsg(response) { if (response.t != "d" || typeof(response.d) != "string") return; if (response.n === nrecv) { writeData(response.d); nrecv++; var next; /* msgQ must be shifted every time nrecv is incremented, but the process * stops whenever an empty space, corresponding to an unarrived message, * is encountered. */ while ((next = msgQ.shift()) !== undefined) { writeData(next.d); nrecv++; } } else if (response.n > nrecv) { /* The current message comes after one still missing. Queue this one * for later use. */ debug(1, "Got packet " + response.n + ", expected " + nrecv); msgQ[response.n - nrecv - 1] = response; } else { /* This message's number was encountered previously. */ debug(1, "Discarding packet " + response.n + ", expected " + nrecv); } } function getData() { if (!isalive) 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) { isalive = false; debug(1, "Server error: " + response.s); } } else if (response.t == "n") ajaxstate.gotnothing(); else if (response.t == "d") { processMsg(response); 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) { isalive = false; debug(1, "Server error: " + response.s); } return; } else if (response.t != "d") return; /* It is a data message */ if (response.d) { processMsg(response); //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 (isalive) 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 (isalive) 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); isalive = true; nsend = 0; nrecv = 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. */ isalive = 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; }