Mercurial > hg > rlgwebd
view shterm.js @ 0:bd412f63ce0d
Put this project under version control, finally.
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Sun, 06 May 2012 08:45:40 -0700 |
parents | |
children | 9bef0941c6dd |
line wrap: on
line source
// 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 datalines = datareq.responseText.split("\n"); if (!datalines[0]) { return; } else if (datalines[0] == "E1") { termemu.alive = false; return; } else if (datalines[0].charAt(0) != 'd') { return; } if (datalines[1]) { writeData(datalines[1]); ajaxstate.gotdata(); } else { ajaxstate.gotnothing(); } return; } }; datareq.open('GET', '/feed', true); datareq.send(null); return; } function postResponseHandler() { if (this.readyState == 4 && this.status == 200) { var datalines = this.responseText.split("\n"); if (!datalines[0]) return; else if (datalines[0] == "E1") { termemu.alive = false; return; } else if (datalines[0].charAt(0) != "d") return; /* It is a data message */ if (datalines[1]) { writeData(datalines[1]); } 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; } /* ASCII values of keys 0-9. */ var numShifts = [41, 33, 64, 35, 36, 37, 94, 38, 42, 40]; var keyHexCodes = { init: function () { this[KeyboardEvent.DOM_VK_RETURN] = ["0d", "0d"]; this[KeyboardEvent.DOM_VK_SPACE] = ["20", "20"]; this[KeyboardEvent.DOM_VK_TAB] = ["09", "09"]; this[KeyboardEvent.DOM_VK_BACK_QUOTE] = ["60", "7e"]; this[KeyboardEvent.DOM_VK_OPEN_BRACKET] = ["5b", "7b"]; this[KeyboardEvent.DOM_VK_CLOSE_BRACKET] = ["5d", "7d"]; this[KeyboardEvent.DOM_VK_BACK_SLASH] = ["5c", "7c"]; this[KeyboardEvent.DOM_VK_SEMICOLON] = ["3b", "3a"]; this[KeyboardEvent.DOM_VK_QUOTE] = ["27", "22"]; this[KeyboardEvent.DOM_VK_COMMA] = ["2c", "3c"]; this[KeyboardEvent.DOM_VK_PERIOD] = ["2e", "3e"]; this[KeyboardEvent.DOM_VK_SLASH] = ["2f", "3f"]; this[KeyboardEvent.DOM_VK_EQUALS] = ["3d", "2b"]; this[KeyboardEvent.DOM_VK_SUBTRACT] = ["2d", "5f"]; this[KeyboardEvent.DOM_VK_BACK_SPACE] = ["08", "08"]; this[KeyboardEvent.DOM_VK_ESCAPE] = ["1b", "1b"]; this[KeyboardEvent.DOM_VK_PAGE_UP] = ["1b5b357e", "1b5b357e"]; this[KeyboardEvent.DOM_VK_PAGE_DOWN] = ["1b5b367e", "1b5b367e"]; this.appCursor(false); this.appKeypad(false); }, /* Multi-char control sequences! Neat! */ appCursor: function (on) { /* Aren't special keys vile? */ if (on) { this[KeyboardEvent.DOM_VK_LEFT] = ["1b4f44", "1b4f44"]; this[KeyboardEvent.DOM_VK_RIGHT] = ["1b4f43", "1b4f43"]; this[KeyboardEvent.DOM_VK_UP] = ["1b4f41", "1b4f41"]; this[KeyboardEvent.DOM_VK_DOWN] = ["1b4f42", "1b4f42"]; this[KeyboardEvent.DOM_VK_END] = ["1b4f46", "1b4f46"]; this[KeyboardEvent.DOM_VK_HOME] = ["1b4f48", "1b4f48"]; } else { this[KeyboardEvent.DOM_VK_LEFT] = ["1b5b44", "1b5b44"]; this[KeyboardEvent.DOM_VK_RIGHT] = ["1b5b43", "1b5b43"]; this[KeyboardEvent.DOM_VK_UP] = ["1b5b41", "1b5b41"]; this[KeyboardEvent.DOM_VK_DOWN] = ["1b5b42", "1b5b42"]; this[KeyboardEvent.DOM_VK_END] = ["1b5b46", "1b5b46"]; this[KeyboardEvent.DOM_VK_HOME] = ["1b5b48", "1b5b48"]; } }, appKeypad: function (on) { /* In theory, these should produce either numerals or the k[a-c][1-3] * sequences. Since we can't count on the terminfo description actually * containing those sequences, pretend they're just arrow keys etc. */ this[KeyboardEvent.DOM_VK_NUMPAD1] = ["1b4f46", "1b4f46"]; this[KeyboardEvent.DOM_VK_NUMPAD2] = ["1b4f42", "1b4f42"]; this[KeyboardEvent.DOM_VK_NUMPAD3] = ["1b5b367e", "1b5b367e"]; this[KeyboardEvent.DOM_VK_NUMPAD4] = ["1b4f44", "1b4f44"]; this[KeyboardEvent.DOM_VK_NUMPAD5] = ["1b5b45", "1b5b45"]; this[KeyboardEvent.DOM_VK_NUMPAD6] = ["1b4f43", "1b4f43"]; this[KeyboardEvent.DOM_VK_NUMPAD7] = ["1b4f48", "1b4f48"]; this[KeyboardEvent.DOM_VK_NUMPAD8] = ["1b4f41", "1b4f41"]; this[KeyboardEvent.DOM_VK_NUMPAD9] = ["1b5b357e", "1b5b357e"]; 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(); termemu.init("termwrap"); 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() { if (termemu.alive) return; var req = new XMLHttpRequest(); req.onreadystatechange = function () { if (req.readyState == 4 && req.status == 200) { var datalines = req.responseText.split("\n"); if (datalines[0] == 'l1') { /* Success */ termemu.alive = true; setTitle("Logged in"); debug(1, "Logged in with id " + datalines[1]); getData(); return; } return; } }; req.open('POST', '/login', true); req.send("login=login"); return; } function stop() { var req = new XMLHttpRequest(); req.onreadystatechange = function () { if (req.readyState == 4 && req.status == 200) { var datalines = req.responseText.split("\n"); /* Figure out whether or not it worked. */ 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"; termemu.resize(); 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; }