Mercurial > hg > rlgwebd
comparison 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 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:bd412f63ce0d |
|---|---|
| 1 /* rlgterm.js: Roguelike Gallery's driver for termemu.js */ | |
| 2 | |
| 3 // A state machine that keeps track of polling the server. | |
| 4 var ajaxstate = { | |
| 5 state: 0, | |
| 6 timerID: null, | |
| 7 clear: function () { | |
| 8 if (this.timerID != null) { | |
| 9 window.clearTimeout(this.timerID); | |
| 10 this.timerID = null; | |
| 11 } | |
| 12 }, | |
| 13 set: function (ms) { | |
| 14 this.clear(); | |
| 15 this.timerID = window.setTimeout(getData, ms); | |
| 16 }, | |
| 17 gotdata: function () { | |
| 18 this.set(100); | |
| 19 this.state = 0; | |
| 20 }, | |
| 21 gotnothing: function () { | |
| 22 if (this.state == 0) { | |
| 23 this.set(100); | |
| 24 this.state = 1; | |
| 25 } | |
| 26 else if (this.state == 1) { | |
| 27 this.set(300); | |
| 28 this.state = 2; | |
| 29 } | |
| 30 else if (this.state == 2) { | |
| 31 this.set(1000); | |
| 32 this.state = 3; | |
| 33 } | |
| 34 else { | |
| 35 this.set(5000); | |
| 36 this.state = 3; | |
| 37 } | |
| 38 }, | |
| 39 posted: function () { | |
| 40 this.set(100); | |
| 41 this.state = 0; | |
| 42 } | |
| 43 }; | |
| 44 | |
| 45 function writeData(hexstr) { | |
| 46 var codenum; | |
| 47 var codes = []; | |
| 48 var nc; | |
| 49 var u8wait = 0; /* Stores bits from previous bytes of multibyte sequences. */ | |
| 50 var expect = 0; /* The number of 10------ bytes expected. */ | |
| 51 /* UTF-8 translation. */ | |
| 52 for (var i = 0; i < hexstr.length; i += 2) { | |
| 53 nc = Number("0x" + hexstr.substr(i, 2)); | |
| 54 if (nc < 0x7F) { | |
| 55 /* 0------- */ | |
| 56 codes.push(nc); | |
| 57 /* Any incomplete sequence will be discarded. */ | |
| 58 u8wait = 0; | |
| 59 expect = 0; | |
| 60 } | |
| 61 else if (nc < 0xC0) { | |
| 62 /* 10------ : part of a multibyte sequence */ | |
| 63 if (expect > 0) { | |
| 64 u8wait <<= 6; | |
| 65 u8wait += (nc & 0x3F); | |
| 66 expect--; | |
| 67 if (expect == 0) { | |
| 68 codes.push(u8wait); | |
| 69 u8wait = 0; | |
| 70 } | |
| 71 } | |
| 72 else { | |
| 73 /* Assume an initial byte was missed. */ | |
| 74 u8wait = 0; | |
| 75 } | |
| 76 } | |
| 77 /* These will all discard any incomplete sequence. */ | |
| 78 else if (nc < 0xE0) { | |
| 79 /* 110----- : introduces 2-byte sequence */ | |
| 80 u8wait = (nc & 0x1F); | |
| 81 expect = 1; | |
| 82 } | |
| 83 else if (nc < 0xF0) { | |
| 84 /* 1110---- : introduces 3-byte sequence */ | |
| 85 u8wait = (nc & 0x0F); | |
| 86 expect = 2; | |
| 87 } | |
| 88 else if (nc < 0xF8) { | |
| 89 /* 11110--- : introduces 4-byte sequence */ | |
| 90 u8wait = (nc & 0x07); | |
| 91 expect = 3; | |
| 92 } | |
| 93 else if (nc < 0xFC) { | |
| 94 /* 111110-- : introduces 5-byte sequence */ | |
| 95 u8wait = (nc & 0x03); | |
| 96 expect = 4; | |
| 97 } | |
| 98 else if (nc < 0xFE) { | |
| 99 /* 1111110- : introduces 6-byte sequence */ | |
| 100 u8wait = (nc & 0x01); | |
| 101 expect = 5; | |
| 102 } | |
| 103 else { | |
| 104 /* 1111111- : should never appear */ | |
| 105 u8wait = 0; | |
| 106 expect = 0; | |
| 107 } | |
| 108 /* Supporting all 31 bits is probably overkill... */ | |
| 109 } | |
| 110 termemu.write(codes); | |
| 111 return; | |
| 112 } | |
| 113 | |
| 114 /* Processes a message from the server, returning true or false if it was a | |
| 115 * data message with or without data, null if not data. */ | |
| 116 function processMsg(msg) { | |
| 117 var msglines = msg.split("\n"); | |
| 118 var havedata = null; | |
| 119 if (!msglines[0]) | |
| 120 return null; | |
| 121 if (msglines[0].charAt(0) == 'd') { | |
| 122 if (msglines[1]){ | |
| 123 writeData(msglines[1]); | |
| 124 havedata = true; | |
| 125 } | |
| 126 else { | |
| 127 havedata = false; | |
| 128 } | |
| 129 } | |
| 130 else if (msglines[0] == "E1") { | |
| 131 logout(); | |
| 132 } | |
| 133 else if (msglines[0].charAt(0) == "T") { | |
| 134 setTitle(msglines[1]); | |
| 135 } | |
| 136 else if (msglines[0] == "q1") { | |
| 137 logout(); | |
| 138 } | |
| 139 else { | |
| 140 debug(1, "Unrecognized server message " + msglines[0]); | |
| 141 } | |
| 142 return havedata; | |
| 143 } | |
| 144 | |
| 145 function getData() { | |
| 146 if (termemu.sessid == null) | |
| 147 return; | |
| 148 var datareq = new XMLHttpRequest(); | |
| 149 datareq.onreadystatechange = function () { | |
| 150 if (datareq.readyState == 4 && datareq.status == 200) { | |
| 151 var wasdata = processMsg(datareq.responseText); | |
| 152 if (wasdata != null) { | |
| 153 if (wasdata) | |
| 154 ajaxstate.gotdata(); | |
| 155 else | |
| 156 ajaxstate.gotnothing(); | |
| 157 } | |
| 158 return; | |
| 159 } | |
| 160 }; | |
| 161 datareq.open('POST', '/feed', true); | |
| 162 datareq.send("id=" + termemu.sessid); | |
| 163 return; | |
| 164 } | |
| 165 | |
| 166 function postResponseHandler() { | |
| 167 if (this.readyState == 4 && this.status == 200) { | |
| 168 // We might want to do something with wasdata someday. | |
| 169 var wasdata = processMsg(this.responseText); | |
| 170 ajaxstate.posted(); | |
| 171 return; | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 function sendback(str) { | |
| 176 /* For responding to terminal queries. */ | |
| 177 var datareq = new XMLHttpRequest(); | |
| 178 datareq.onreadystatechange = postResponseHandler; | |
| 179 datareq.open('POST', '/feed', true); | |
| 180 datareq.send("id=" + termemu.sessid + "&keys=" + str); | |
| 181 return; | |
| 182 } | |
| 183 | |
| 184 /* ASCII values of keys 0-9. */ | |
| 185 var numShifts = [41, 33, 64, 35, 36, 37, 94, 38, 42, 40]; | |
| 186 | |
| 187 var keyHexCodes = { | |
| 188 init: function () { | |
| 189 this[KeyboardEvent.DOM_VK_RETURN] = ["0d", "0d"]; | |
| 190 this[KeyboardEvent.DOM_VK_SPACE] = ["20", "20"]; | |
| 191 this[KeyboardEvent.DOM_VK_TAB] = ["09", "09"]; | |
| 192 this[KeyboardEvent.DOM_VK_BACK_QUOTE] = ["60", "7e"]; | |
| 193 this[KeyboardEvent.DOM_VK_OPEN_BRACKET] = ["5b", "7b"]; | |
| 194 this[KeyboardEvent.DOM_VK_CLOSE_BRACKET] = ["5d", "7d"]; | |
| 195 this[KeyboardEvent.DOM_VK_BACK_SLASH] = ["5c", "7c"]; | |
| 196 this[KeyboardEvent.DOM_VK_SEMICOLON] = ["3b", "3a"]; | |
| 197 this[KeyboardEvent.DOM_VK_QUOTE] = ["27", "22"]; | |
| 198 this[KeyboardEvent.DOM_VK_COMMA] = ["2c", "3c"]; | |
| 199 this[KeyboardEvent.DOM_VK_PERIOD] = ["2e", "3e"]; | |
| 200 this[KeyboardEvent.DOM_VK_SLASH] = ["2f", "3f"]; | |
| 201 this[KeyboardEvent.DOM_VK_EQUALS] = ["3d", "2b"]; | |
| 202 this[KeyboardEvent.DOM_VK_SUBTRACT] = ["2d", "5f"]; | |
| 203 this[KeyboardEvent.DOM_VK_BACK_SPACE] = ["08", "08"]; | |
| 204 this[KeyboardEvent.DOM_VK_ESCAPE] = ["1b", "1b"]; | |
| 205 /* Multi-char control sequences! Neat! */ | |
| 206 this[KeyboardEvent.DOM_VK_PAGE_UP] = ["1b5b357e", "1b5b357e"]; | |
| 207 this[KeyboardEvent.DOM_VK_PAGE_DOWN] = ["1b5b367e", "1b5b367e"]; | |
| 208 this.appCursor(false); | |
| 209 this.appKeypad(false); | |
| 210 }, | |
| 211 appCursor: function (on) { | |
| 212 if (on) { | |
| 213 this[KeyboardEvent.DOM_VK_LEFT] = ["1b4f44", "1b4f44"]; | |
| 214 this[KeyboardEvent.DOM_VK_RIGHT] = ["1b4f43", "1b4f43"]; | |
| 215 this[KeyboardEvent.DOM_VK_UP] = ["1b4f41", "1b4f41"]; | |
| 216 this[KeyboardEvent.DOM_VK_DOWN] = ["1b4f42", "1b4f42"]; | |
| 217 this[KeyboardEvent.DOM_VK_END] = ["1b4f46", "1b4f46"]; | |
| 218 this[KeyboardEvent.DOM_VK_HOME] = ["1b4f48", "1b4f48"]; | |
| 219 } | |
| 220 else { | |
| 221 this[KeyboardEvent.DOM_VK_LEFT] = ["1b5b44", "1b5b44"]; | |
| 222 this[KeyboardEvent.DOM_VK_RIGHT] = ["1b5b43", "1b5b43"]; | |
| 223 this[KeyboardEvent.DOM_VK_UP] = ["1b5b41", "1b5b41"]; | |
| 224 this[KeyboardEvent.DOM_VK_DOWN] = ["1b5b42", "1b5b42"]; | |
| 225 this[KeyboardEvent.DOM_VK_END] = ["1b5b46", "1b5b46"]; | |
| 226 this[KeyboardEvent.DOM_VK_HOME] = ["1b5b48", "1b5b48"]; | |
| 227 } | |
| 228 }, | |
| 229 appKeypad: function (on) { | |
| 230 /* In theory, these should produce either numerals or the k[a-c][1-3] | |
| 231 * sequences. Since we can't count on the terminfo description actually | |
| 232 * containing those sequences, pretend they're just arrow keys etc. | |
| 233 */ | |
| 234 this[KeyboardEvent.DOM_VK_NUMPAD1] = ["1b4f46", "1b4f46"]; | |
| 235 this[KeyboardEvent.DOM_VK_NUMPAD2] = ["1b4f42", "1b4f42"]; | |
| 236 this[KeyboardEvent.DOM_VK_NUMPAD3] = ["1b5b367e", "1b5b367e"]; | |
| 237 this[KeyboardEvent.DOM_VK_NUMPAD4] = ["1b4f44", "1b4f44"]; | |
| 238 this[KeyboardEvent.DOM_VK_NUMPAD5] = ["1b5b45", "1b5b45"]; | |
| 239 this[KeyboardEvent.DOM_VK_NUMPAD6] = ["1b4f43", "1b4f43"]; | |
| 240 this[KeyboardEvent.DOM_VK_NUMPAD7] = ["1b4f48", "1b4f48"]; | |
| 241 this[KeyboardEvent.DOM_VK_NUMPAD8] = ["1b4f41", "1b4f41"]; | |
| 242 this[KeyboardEvent.DOM_VK_NUMPAD9] = ["1b5b357e", "1b5b357e"]; | |
| 243 return; | |
| 244 } | |
| 245 }; | |
| 246 | |
| 247 function sendkey(ev) { | |
| 248 if (termemu.sessid == null) | |
| 249 return; | |
| 250 var keynum = ev.keyCode; | |
| 251 var code; | |
| 252 if (keynum >= ev.DOM_VK_A && keynum <= ev.DOM_VK_Z) { | |
| 253 /* Letters. This assumes the codes are 65-90. */ | |
| 254 if (ev.ctrlKey) | |
| 255 keynum -= 64; | |
| 256 else if (!ev.shiftKey) | |
| 257 keynum += 32; | |
| 258 code = keynum.toString(16); | |
| 259 if (code.length < 2) | |
| 260 code = "0" + code; | |
| 261 } | |
| 262 else if (keynum >= ev.DOM_VK_0 && keynum <= ev.DOM_VK_9) { | |
| 263 /* The number row, NOT the numpad. */ | |
| 264 if (ev.shiftKey) { | |
| 265 code = numShifts[keynum - 48].toString(16); | |
| 266 } | |
| 267 else { | |
| 268 code = keynum.toString(16); | |
| 269 } | |
| 270 } | |
| 271 else if (keynum in keyHexCodes) { | |
| 272 if (ev.shiftKey) | |
| 273 code = keyHexCodes[keynum][1]; | |
| 274 else | |
| 275 code = keyHexCodes[keynum][0]; | |
| 276 } | |
| 277 else if (keynum == ev.DOM_VK_SHIFT || keynum == ev.DOM_VK_CONTROL || | |
| 278 keynum == ev.DOM_VK_ALT || keynum == ev.DOM_VK_CAPS_LOCK) { | |
| 279 return; | |
| 280 } | |
| 281 else { | |
| 282 debug(1, "Ignoring keycode " + keynum); | |
| 283 return; | |
| 284 } | |
| 285 if (termemu.sessid != null) | |
| 286 ev.preventDefault(); | |
| 287 var datareq = new XMLHttpRequest(); | |
| 288 datareq.onreadystatechange = postResponseHandler; | |
| 289 datareq.open('POST', '/feed', true); | |
| 290 datareq.send("id=" + termemu.sessid + "&keys=" + code); | |
| 291 return; | |
| 292 } | |
| 293 | |
| 294 var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c", | |
| 295 ';': "3a", '\'': "22", ',': "3c", '.': "3e", '/': "3f", '`': "7e" | |
| 296 } | |
| 297 | |
| 298 function vkey(c) { | |
| 299 if (termemu.sessid == null) | |
| 300 return; | |
| 301 var keystr; | |
| 302 if (c.match(/^[a-z]$/)) { | |
| 303 if (termemu.ctrlp()) { | |
| 304 var n = c.charCodeAt(0) - 96; | |
| 305 keystr = n.toString(16); | |
| 306 if (keystr.length < 2) | |
| 307 keystr = "0" + keystr; | |
| 308 } | |
| 309 else if (termemu.shiftp()) | |
| 310 keystr = c.toUpperCase().charCodeAt(0).toString(16); | |
| 311 else | |
| 312 keystr = c.charCodeAt(0).toString(16); | |
| 313 } | |
| 314 else if (c.match(/^[0-9]$/)) { | |
| 315 if (termemu.shiftp()) | |
| 316 keystr = numShifts[c.charCodeAt(0) - 48].toString(16); | |
| 317 else | |
| 318 keystr = c.charCodeAt(0).toString(16); | |
| 319 } | |
| 320 else if (c == '\n') | |
| 321 keystr = "0a"; | |
| 322 else if (c == '\t') | |
| 323 keystr = "09"; | |
| 324 else if (c == '\b') | |
| 325 keystr = "08"; | |
| 326 else if (c == ' ') | |
| 327 keystr = "20"; | |
| 328 else if (c in charshifts) { | |
| 329 if (termemu.shiftp()) | |
| 330 keystr = charshifts[c]; | |
| 331 else | |
| 332 keystr = c.charCodeAt(0).toString(16); | |
| 333 } | |
| 334 else | |
| 335 return; | |
| 336 //writeData("Sending " + keystr); | |
| 337 var datareq = new XMLHttpRequest(); | |
| 338 datareq.onreadystatechange = postResponseHandler; | |
| 339 datareq.open('POST', '/feed', true); | |
| 340 datareq.send("id=" + termemu.sessid + "&keys=" + keystr); | |
| 341 return; | |
| 342 } | |
| 343 | |
| 344 function setup() { | |
| 345 keyHexCodes.init(); | |
| 346 termemu.init("termwrap"); | |
| 347 setTitle("Not connected."); | |
| 348 return; | |
| 349 } | |
| 350 | |
| 351 function toggleshift() { | |
| 352 termemu.toggleshift(); | |
| 353 keydiv = document.getElementById("shiftkey"); | |
| 354 if (termemu.shiftp()) | |
| 355 keydiv.className = "keysel"; | |
| 356 else | |
| 357 keydiv.className = "key"; | |
| 358 return; | |
| 359 } | |
| 360 | |
| 361 function togglectrl() { | |
| 362 termemu.togglectrl(); | |
| 363 keydiv = document.getElementById("ctrlkey"); | |
