diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shterm.js	Sun May 06 08:45:40 2012 -0700
@@ -0,0 +1,478 @@
+// 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;
+}
+
+function getData() {
+  if (!termemu.alive)
+    return;
+  var datareq = new XMLHttpRequest();
+  datareq.onreadystatechange = function () {
+    if (datareq.readyState == 4 && datareq.status == 200) {
+      var datalines = datareq.responseText.split("\n");
+      if (!datalines[0]) {
+        return;
+      }
+      else if (datalines[0] == "E1") {
+        termemu.alive = false;
+        return;
+      }
+      else if (datalines[0].charAt(0) != 'd') {
+        return;
+      }
+      if (datalines[1]) {
+        writeData(datalines[1]);
+        ajaxstate.gotdata();
+      }
+      else {
+        ajaxstate.gotnothing();
+      }
+      return;
+    }
+  };
+  datareq.open('GET', '/feed', true);
+  datareq.send(null);
+  return;
+}
+
+function postResponseHandler() {
+  if (this.readyState == 4 && this.status == 200) {
+    var datalines = this.responseText.split("\n");
+    if (!datalines[0])
+      return;
+    else if (datalines[0] == "E1") {
+      termemu.alive = false;
+      return;
+    }
+    else if (datalines[0].charAt(0) != "d")
+      return;
+    /* It is a data message */
+    if (datalines[1]) {
+      writeData(datalines[1]);
+    }
+    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("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"];
+    this[KeyboardEvent.DOM_VK_PAGE_UP] =   ["1b5b357e", "1b5b357e"];
+    this[KeyboardEvent.DOM_VK_PAGE_DOWN] = ["1b5b367e", "1b5b367e"];
+    this.appCursor(false);
+    this.appKeypad(false);
+  },
+  /* Multi-char control sequences!  Neat! */
+  appCursor: function (on) {
+    /* Aren't special keys vile? */
+    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) {
+  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. */
+    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.alive)
+    ev.preventDefault();
+  var datareq = new XMLHttpRequest();
+  datareq.onreadystatechange = postResponseHandler;
+  datareq.open('POST', '/feed', true);
+  datareq.send("keys=" + code);
+  //dkey(code);
+  return;
+}
+
+var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c",
+  ';': "3a", '\'': "22", ',': "3c", '.': "3e", '/': "3f", '`': "7e"
+}
+
+function vkey(c) {
+  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("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 login() {
+  if (termemu.alive)
+    return;
+  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.alive = true;
+	setTitle("Logged in");
+        debug(1, "Logged in with id " + datalines[1]);
+        getData();
+        return;
+      }
+      return;
+    }
+  };
+  req.open('POST', '/login', true);
+  req.send("login=login");
+  return;
+}
+
+function stop() {
+  var req = new XMLHttpRequest();
+  req.onreadystatechange = function () {
+    if (req.readyState == 4 && req.status == 200) {
+      var datalines = req.responseText.split("\n");
+      /* Figure out whether or not it worked. */
+      termemu.alive = false;
+      return;
+    }
+  };
+  req.open('POST', '/feed', true);
+  req.send("quit=quit");
+  return;
+}
+
+function setTitle(tstr) {
+  var titlespan = document.getElementById("ttitle");
+  var tnode = document.createTextNode(tstr);
+  if (titlespan.childNodes.length == 0)
+    titlespan.appendChild(tnode);
+  else
+    titlespan.replaceChild(tnode, titlespan.childNodes[0]);
+  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;
+}
+
+/* This should be a termemu method. */
+function textsize(larger) {
+  var cssSize = document.getElementById("term").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;
+}
+
+function dkey(codestr) {
+  var dstr = "Keystring: ";
+  for (var i = 0; i < codestr.length; i += 2) {
+    code = Number("0x" + codestr.substr(i, 2));
+    if (code < 32 || (code >= 127 && code < 160))
+      dstr += "\\x" + code.toString(16);
+    else
+      dstr += String.fromCharCode(code);
+  }
+  debug(1, dstr);
+  return;
+}