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"); | |