Mercurial > hg > rlgwebd
diff rlgterm.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 | ee22eb9ab009 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rlgterm.js Sun May 06 08:45:40 2012 -0700 @@ -0,0 +1,485 @@ +/* rlgterm.js: Roguelike Gallery's driver for termemu.js */ + +// 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; +} + +/* Processes a message from the server, returning true or false if it was a + * data message with or without data, null if not data. */ +function processMsg(msg) { + var msglines = msg.split("\n"); + var havedata = null; + if (!msglines[0]) + return null; + if (msglines[0].charAt(0) == 'd') { + if (msglines[1]){ + writeData(msglines[1]); + havedata = true; + } + else { + havedata = false; + } + } + else if (msglines[0] == "E1") { + logout(); + } + else if (msglines[0].charAt(0) == "T") { + setTitle(msglines[1]); + } + else if (msglines[0] == "q1") { + logout(); + } + else { + debug(1, "Unrecognized server message " + msglines[0]); + } + return havedata; +} + +function getData() { + if (termemu.sessid == null) + return; + var datareq = new XMLHttpRequest(); + datareq.onreadystatechange = function () { + if (datareq.readyState == 4 && datareq.status == 200) { + var wasdata = processMsg(datareq.responseText); + if (wasdata != null) { + if (wasdata) + ajaxstate.gotdata(); + else + ajaxstate.gotnothing(); + } + return; + } + }; + datareq.open('POST', '/feed', true); + datareq.send("id=" + termemu.sessid); + return; +} + +function postResponseHandler() { + if (this.readyState == 4 && this.status == 200) { + // We might want to do something with wasdata someday. + var wasdata = processMsg(this.responseText); + 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("id=" + termemu.sessid + "&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"]; + /* Multi-char control sequences! Neat! */ + this[KeyboardEvent.DOM_VK_PAGE_UP] = ["1b5b357e", "1b5b357e"]; + this[KeyboardEvent.DOM_VK_PAGE_DOWN] = ["1b5b367e", "1b5b367e"]; + this.appCursor(false); + this.appKeypad(false); + }, + appCursor: function (on) { + 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) { + if (termemu.sessid == null) + return; + 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, NOT the numpad. */ + 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.sessid != null) + ev.preventDefault(); + var datareq = new XMLHttpRequest(); + datareq.onreadystatechange = postResponseHandler; + datareq.open('POST', '/feed', true); + datareq.send("id=" + termemu.sessid + "&keys=" + code); + return; +} + +var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c", + ';': "3a", '\'': "22", ',': "3c", '.': "3e", '/': "3f", '`': "7e" +} + +function vkey(c) { + if (termemu.sessid == null) + return; + 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("id=" + termemu.sessid + "&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 formlogin(ev) { + ev.preventDefault(); + if (termemu.sessid != null) + return; + var formname = document.getElementById("input_name").value; + var formpass = document.getElementById("input_pw").value; + var formgame = document.getElementById("input_game").value; + var formdata = "game=" + encodeURIComponent(formgame) + "&name=" + encodeURIComponent(formname) + "&pw=" + encodeURIComponent(formpass); + 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.sessid = datalines[1]; + setTitle("Playing as " + formname); + debug(1, "Logged in with id " + termemu.sessid); + document.getElementById("loginform").style.display = "none"; + getData(); + } + else { + debug(1, "Could not start game: " + datalines[1]); + document.getElementById("input_name").value = ""; + document.getElementById("input_pw").value = ""; + } + } + }; + req.open('POST', '/login', true); + req.send(formdata); + return; +} + +function logout() { + if (termemu.sessid == null) + return; + termemu.sessid = null; + setTitle("Game over."); + document.getElementById("loginform").style.display = "block"; + return; +} + +function stop() { + var req = new XMLHttpRequest(); + req.onreadystatechange = function () { + if (req.readyState == 4 && req.status == 200) { + processMsg(req.responseText); + return; + } + }; + req.open('POST', '/feed', true); + req.send("id=" + termemu.sessid + "&quit=quit"); + 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; +} + +function textsize(larger) { + var cssSize = termemu.view.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; +}