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");
365 if (datalines[0] == 'l1') {
366 /* Success */
367 termemu.alive = true;
368 setTitle("Logged in");
369 debug(1, "Logged in with id " + datalines[1]);
370 getData();
371 return;
372 }
373 return;
374 }
375 };
376 req.open('POST', '/login', true);
377 req.send("login=login");
378 return;
379 }
380
381 function stop() {
382 var req = new XMLHttpRequest();
383 req.onreadystatechange = function () {
384 if (req.readyState == 4 && req.status == 200) {
385 var datalines = req.responseText.split("\n");
386 /* Figure out whether or not it worked. */
387 termemu.alive = false;
388 return;
389 }
390 };
391 req.open('POST', '/feed', true);
392 req.send("quit=quit");
393 return;
394 }
395
396 function setTitle(tstr) {
397 var titlespan = document.getElementById("ttitle");
398 var tnode = document.createTextNode(tstr);
399 if (titlespan.childNodes.length == 0)
400 titlespan.appendChild(tnode);
401 else
402 titlespan.replaceChild(tnode, titlespan.childNodes[0]);
403 return;
404 }
405
406 function debug(level, msg) {
407 if (level < debugSuppress)
408 return;
409 var msgdiv = document.createElement("div");
410 var msgtext = document.createTextNode(msg);
411 msgdiv.appendChild(msgtext);
412 document.getElementById("debug").appendChild(msgdiv);
413 return;
414 }
415
416 /* This should be a termemu method. */
417 function textsize(larger) {
418 var cssSize = document.getElementById("term").style.fontSize;
419 if (!cssSize)
420 return;
421 var match = cssSize.match(/\d*/);
422 if (!match)
423 return;
424 var csize = Number(match[0]);
425 var nsize;
426 if (larger) {
427 if (csize >= 48)
428 nsize = 48;
429 else if (csize >= 20)
430 nsize = csize + 4;
431 else if (csize >= 12)
432 nsize = csize + 2;
433 else if (csize >= 8)
434 nsize = csize + 1;
435 else
436 nsize = 8;
437 }
438 else {
439 if (csize <= 8)
440 nsize = 8;
441 else if (csize <= 12)
442 nsize = csize - 1;
443 else if (csize <= 20)
444 nsize = csize - 2;
445 else if (csize <= 48)
446 nsize = csize - 4;
447 else
448 nsize = 48;
449 }
450 document.getElementById("term").style.fontSize = nsize.toString() + "px";
451 termemu.resize();
452 debug(1, "Changing font size to " + nsize.toString());
453 return;
454 }
455
456 function bell(on) {
457 var imgnode = document.getElementById("bell");
458 if (on) {
459 imgnode.style.visibility = "visible";
460 window.setTimeout(bell, 1500, false);
461 }
462 else
463 imgnode.style.visibility = "hidden";
464 return;
465 }
466
467 function dkey(codestr) {
468 var dstr = "Keystring: ";
469 for (var i = 0; i < codestr.length; i += 2) {
470 code = Number("0x" + codestr.substr(i, 2));
471 if (code < 32 || (code >= 127 && code < 160))
472 dstr += "\\x" + code.toString(16);
473 else
474 dstr += String.fromCharCode(code);
475 }
476 debug(1, dstr);
477 return;
478 }