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