view shterm.js @ 192:addc4e3456c4

Begin adding systemd compatibility. The Makefile can be used to build and install rlgwebd. On systems with systemd, the unit file controls starting and stopping the service, and the shell script is not needed. The unit file uses KillMode=none because socat does not actually stop rlgwebd, it only asks it to stop and exits without waiting for a response. Until a better stopping method is introduced, this setting prevents systemd from killing all the rlgwebd processes as soon as socat exits.
author John "Elwin" Edwards
date Fri, 01 Jan 2016 16:11:34 -0500
parents c4a32007d2dc
children
line wrap: on
line source

/* shterm.js: browser-side JavaScript to handle I/O for termemu.js when it 
 * is running a shell via the webtty.js server.
 */

var isalive = false; // Whether the session is currently active.
var conn = null; // WebSocket

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 sendback(str) {
  /* For responding to terminal queries. */
  if (conn) {
    var msgObj = {"t": "d", "d": str};
    conn.send(JSON.stringify(msgObj));
  }
  else {
    debug(1, "No connection: cannot send " + str);
  }
  return;
}

function sendkey(ev) {
  if (!isalive)
    return;
  var keynum = ev.keyCode;
  var code;
  if (keynum >= 65 && keynum <= 90) {
    /* Letters. */
    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 >= 48 && keynum <= 57) {
    /* 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 >= 16 && keynum <= 20) {
    return;
  }
  else {
    debug(1, "Ignoring keycode " + keynum);
    return;
  }
  ev.preventDefault();
  var msgObj = {"t": "d", "d": code};
  conn.send(JSON.stringify(msgObj));
  //dkey(code);
  return;
}

var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c",
  ';': "3a", '\'': "22", ',': "3c", '.': "3e", '/': "3f", '`': "7e"
}

var kpkeys = { "KP1": "1b4f46", "KP2": "1b4f42", "KP3": "1b5b367e", 
               "KP4": "1b4f44", "KP5": "1b5b45", "KP6": "1b4f43", 
               "KP7": "1b4f48", "KP8": "1b4f41", "KP9": "1b5b357e" };

function vkey(c) {
  if (!isalive)
    return;
  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 if (c in kpkeys) {
    keystr = kpkeys[c];
  }
  else
    return;
  //writeData("Sending " + keystr);
  var msgObj = {"t": "d", "d": keystr};
  conn.send(JSON.stringify(msgObj));
  return;
}

function setup() {
  keyHexCodes.init();
  termemu.init("termwrap", 24, 80);
  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(h, w) {
  if (conn)
    return;
  if (!(window.WebSocket)) {
    debug(1, "Cannot connect: WebSockets not supported");
    return;
  }
  var sockurl = "ws://" + window.location.host + "/sock?w=" + w + "&h=" + h;
  conn = new WebSocket(sockurl);
  conn.onopen = function (event) {
    isalive = true;
    setTitle("Logged in");
    debug(1, "Logged in via WebSocket");
  }
  conn.onmessage = function (event) {
    var msgObj = JSON.parse(event.data);
    if (msgObj.t == 'l') {
      termemu.resize(msgObj.h, msgObj.w);
    }
    else if (msgObj.t == 'd') {
      debug(0, msgObj.d);
      writeData(msgObj.d);
    }
    else if (msgObj.t == 'q') {
      debug(0, "Quit message!");
      conn.close();
    }
  }
  conn.onclose = function (event) {
    conn = null;
    isalive = false;
    debug(1, "WebSocket connection closed.");
    setTitle("Not connected.");
  }
}

function stop() {
  if (conn) {
    conn.close();
  }
  else {
    debug(1, "Cannot stop: connection already closed");
  }
  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.fixsize();
  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;
}