0e6c2f5d95
Split of wsproxy from noVNC and rename it websockify.
920 lines
22 KiB
JavaScript
920 lines
22 KiB
JavaScript
// VT100.js -- a text terminal emulator in JavaScript with a ncurses-like
|
|
// interface and a POSIX-like interface. (The POSIX-like calls are
|
|
// implemented on top of the ncurses-like calls, not the other way round.)
|
|
//
|
|
// Released under the GNU LGPL v2.1, by Frank Bi <bi@zompower.tk>
|
|
//
|
|
// 2007-08-12 - refresh():
|
|
// - factor out colour code to html_colours_()
|
|
// - fix handling of A_REVERSE | A_DIM
|
|
// - simplify initial <br /> output code
|
|
// - fix underlining colour
|
|
// - fix attron() not to turn off attributes
|
|
// - decouple A_STANDOUT and A_BOLD
|
|
// 2007-08-11 - getch() now calls refresh()
|
|
// 2007-08-06 - Safari compat fix -- turn '\r' into '\n' for onkeypress
|
|
// 2007-08-05 - Opera compat fixes for onkeypress
|
|
// 2007-07-30 - IE compat fixes:
|
|
// - change key handling code
|
|
// - add <br />...<br /> so that 1st and last lines align
|
|
// 2007-07-28 - change wrapping behaviour -- writing at the right edge no
|
|
// longer causes the cursor to immediately wrap around
|
|
// - add <b>...</b> to output to make A_STANDOUT stand out more
|
|
// - add handling of backspace, tab, return keys
|
|
// - fix doc. of VT100() constructor
|
|
// - change from GPL to LGPL
|
|
// 2007-07-09 - initial release
|
|
//
|
|
// class VT100
|
|
// A_NORMAL, A_UNDERLINE, A_REVERSE, A_BLINK, A_DIM, A_BOLD, A_STANDOUT
|
|
// =class constants=
|
|
// Attribute constants.
|
|
// VT100(wd, ht, scr_id) =constructor=
|
|
// Creates a virtual terminal with width `wd', and
|
|
// height `ht'. The terminal will be displayed between
|
|
// <pre>...</pre> tags which have element ID `scr_id'.
|
|
// addch(ch [, attr])
|
|
// Writes out the character `ch'. If `attr' is given,
|
|
// it specifies the attributes for the character,
|
|
// otherwise the current attributes are used.
|
|
// addstr(stuff) Writes out the string `stuff' using the current
|
|
// attributes.
|
|
// attroff(mode) Turns off any current options given in mode.
|
|
// attron(mode) Turns on any options given in mode.
|
|
// attrset(mode) Sets the current options to mode.
|
|
// bkgdset(attr) Sets the background attributes to attr.
|
|
// clear() Clears the terminal using the background attributes,
|
|
// and homes the cursor.
|
|
// clrtobol() Clears the portion of the terminal from the cursor
|
|
// to the bottom.
|
|
// clrtoeol() Clears the portion of the current line after the
|
|
// cursor.
|
|
// curs_set(vis [, grab])
|
|
// If `vis' is 0, makes the cursor invisible; otherwise
|
|
// make it visible. If `grab' is given and true, starts
|
|
// capturing keyboard events (for `getch()'); if given
|
|
// and false, stops capturing events.
|
|
// echo() Causes key strokes to be automatically echoed on the
|
|
// terminal.
|
|
// erase() Same as `clear()'.
|
|
// getch(isr) Arranges to call `isr' when a key stroke is
|
|
// received. The received character and the terminal
|
|
// object are passed as arguments to `isr'.
|
|
// getmaxyx() Returns an associative array with the maximum row
|
|
// (`y') and column (`x') numbers for the terminal.
|
|
// getyx() Returns an associative array with the current row
|
|
// (`y') and column (`x') of the cursor.
|
|
// move(r, c) Moves the cursor to row `r', column `c'.
|
|
// noecho() Stops automatically echoing key strokes.
|
|
// refresh() Updates the display.
|
|
// scroll() Scrolls the terminal up one line.
|
|
// standend() Same as `attrset(VT100.A_NORMAL)'.
|
|
// standout() Same as `attron(VT100.A_STANDOUT)'.
|
|
// write(stuff) Writes `stuff' to the terminal and immediately
|
|
// updates the display; (some) escape sequences are
|
|
// interpreted and acted on.
|
|
|
|
// constructor
|
|
function VT100(wd, ht, scr_id)
|
|
{
|
|
var r;
|
|
var c;
|
|
var scr = document.getElementById(scr_id);
|
|
this.wd_ = wd;
|
|
this.ht_ = ht;
|
|
this.scrolled_ = 0;
|
|
this.bkgd_ = {
|
|
mode: VT100.A_NORMAL,
|
|
fg: VT100.COLOR_WHITE,
|
|
bg: VT100.COLOR_BLACK
|
|
};
|
|
this.c_attr_ = {
|
|
mode: VT100.A_NORMAL,
|
|
fg: VT100.COLOR_WHITE,
|
|
bg: VT100.COLOR_BLACK
|
|
};
|
|
this.text_ = new Array(ht);
|
|
this.attr_ = new Array(ht);
|
|
for (r = 0; r < ht; ++r) {
|
|
this.text_[r] = new Array(wd);
|
|
this.attr_[r] = new Array(wd);
|
|
}
|
|
this.scr_ = scr;
|
|
this.cursor_vis_ = true;
|
|
this.grab_events_ = false;
|
|
this.getch_isr_ = undefined;
|
|
this.key_buf_ = [];
|
|
this.echo_ = true;
|
|
this.esc_state_ = 0;
|
|
// Internal debug setting.
|
|
this.debug_ = 0;
|
|
this.clear();
|
|
this.refresh();
|
|
}
|
|
|
|
// public constants -- colours and colour pairs
|
|
VT100.COLOR_BLACK = 0;
|
|
VT100.COLOR_BLUE = 1;
|
|
VT100.COLOR_GREEN = 2;
|
|
VT100.COLOR_CYAN = 3;
|
|
VT100.COLOR_RED = 4;
|
|
VT100.COLOR_MAGENTA = 5;
|
|
VT100.COLOR_YELLOW = 6;
|
|
VT100.COLOR_WHITE = 7;
|
|
VT100.COLOR_PAIRS = 256;
|
|
VT100.COLORS = 8;
|
|
// public constants -- attributes
|
|
VT100.A_NORMAL = 0;
|
|
VT100.A_UNDERLINE = 1;
|
|
VT100.A_REVERSE = 2;
|
|
VT100.A_BLINK = 4;
|
|
VT100.A_DIM = 8;
|
|
VT100.A_BOLD = 16;
|
|
VT100.A_STANDOUT = 32;
|
|
VT100.A_PROTECT = VT100.A_INVIS = 0; // ?
|
|
// other public constants
|
|
VT100.TABSIZE = 8;
|
|
// private constants
|
|
VT100.ATTR_FLAGS_ = VT100.A_UNDERLINE | VT100.A_REVERSE | VT100.A_BLINK |
|
|
VT100.A_DIM | VT100.A_BOLD | VT100.A_STANDOUT |
|
|
VT100.A_PROTECT | VT100.A_INVIS;
|
|
VT100.COLOR_SHIFT_ = 6;
|
|
VT100.browser_ie_ = (navigator.appName.indexOf("Microsoft") != -1);
|
|
VT100.browser_opera_ = (navigator.appName.indexOf("Opera") != -1);
|
|
// class variables
|
|
VT100.the_vt_ = undefined;
|
|
|
|
// class methods
|
|
|
|
// this is actually an event handler
|
|
VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event)
|
|
{
|
|
var vt = VT100.the_vt_, ch;
|
|
if (vt === undefined)
|
|
return true;
|
|
if (VT100.browser_ie_ || VT100.browser_opera_) {
|
|
ch = event.keyCode;
|
|
if (ch == 13)
|
|
ch = 10;
|
|
else if (ch > 255 || (ch < 32 && ch != 8))
|
|
return true;
|
|
ch = String.fromCharCode(ch);
|
|
} else {
|
|
ch = event.charCode;
|
|
//dump("ch: " + ch + "\n");
|
|
//dump("ctrl?: " + event.ctrlKey + "\n");
|
|
vt.debug("onkeypress:: keyCode: " + event.keyCode + ", ch: " + event.charCode);
|
|
if (ch) {
|
|
if (ch > 255)
|
|
return true;
|
|
if (event.ctrlKey && event.shiftKey) {
|
|
// Don't send the copy/paste commands.
|
|
var charStr = String.fromCharCode(ch);
|
|
if (charStr == 'C' || charStr == 'V') {
|
|
return false;
|
|
}
|
|
}
|
|
if (event.ctrlKey) {
|
|
ch = String.fromCharCode(ch - 96);
|
|
} else {
|
|
ch = String.fromCharCode(ch);
|
|
if (ch == '\r')
|
|
ch = '\n';
|
|
}
|
|
} else {
|
|
switch (event.keyCode) {
|
|
case event.DOM_VK_BACK_SPACE:
|
|
ch = '\b';
|
|
break;
|
|
case event.DOM_VK_TAB:
|
|
ch = '\t';
|
|
// Stop tab from moving to another element.
|
|
event.preventDefault();
|
|
break;
|
|
case event.DOM_VK_RETURN:
|
|
case event.DOM_VK_ENTER:
|
|
ch = '\n';
|
|
break;
|
|
case event.DOM_VK_UP:
|
|
ch = '\x1b[A';
|
|
break;
|
|
case event.DOM_VK_DOWN:
|
|
ch = '\x1b[B';
|
|
break;
|
|
case event.DOM_VK_RIGHT:
|
|
ch = '\x1b[C';
|
|
break;
|
|
case event.DOM_VK_LEFT:
|
|
ch = '\x1b[D';
|
|
break;
|
|
case event.DOM_VK_DELETE:
|
|
ch = '\x1b[3~';
|
|
break;
|
|
case event.DOM_VK_HOME:
|
|
ch = '\x1b[H';
|
|
break;
|
|
case event.DOM_VK_ESCAPE:
|
|
ch = '\x1bc';
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
vt.key_buf_.push(ch);
|
|
setTimeout(VT100.go_getch_, 0);
|
|
return false;
|
|
}
|
|
|
|
// this is actually an event handler
|
|
VT100.handle_onkeydown_ = function VT100_handle_onkeydown()
|
|
{
|
|
var vt = VT100.the_vt_, ch;
|
|
switch (event.keyCode) {
|
|
case 8:
|
|
ch = '\b'; break;
|
|
default:
|
|
return true;
|
|
}
|
|
vt.key_buf_.push(ch);
|
|
setTimeout(VT100.go_getch_, 0);
|
|
return false;
|
|
}
|
|
|
|
VT100.go_getch_ = function VT100_go_getch()
|
|
{
|
|
var vt = VT100.the_vt_;
|
|
if (vt === undefined)
|
|
return;
|
|
var isr = vt.getch_isr_;
|
|
vt.getch_isr_ = undefined;
|
|
if (isr === undefined)
|
|
return;
|
|
var ch = vt.key_buf_.shift();
|
|
if (ch === undefined) {
|
|
vt.getch_isr_ = isr;
|
|
return;
|
|
}
|
|
if (vt.echo_)
|
|
vt.addch(ch);
|
|
isr(ch, vt);
|
|
}
|
|
|
|
// object methods
|
|
|
|
VT100.prototype.may_scroll_ = function()
|
|
{
|
|
var ht = this.ht_, cr = this.row_;
|
|
while (cr >= ht) {
|
|
this.scroll();
|
|
--cr;
|
|
}
|
|
this.row_ = cr;
|
|
}
|
|
|
|
VT100.prototype.html_colours_ = function(attr)
|
|
{
|
|
var fg, bg, co0, co1;
|
|
fg = attr.fg;
|
|
bg = attr.bg;
|
|
switch (attr.mode & (VT100.A_REVERSE | VT100.A_DIM | VT100.A_BOLD)) {
|
|
case 0:
|
|
case VT100.A_DIM | VT100.A_BOLD:
|
|
co0 = '00'; co1 = 'c0';
|
|
break;
|
|
case VT100.A_BOLD:
|
|
co0 = '00'; co1 = 'ff';
|
|
break;
|
|
case VT100.A_DIM:
|
|
if (fg == VT100.COLOR_BLACK)
|
|
co0 = '40';
|
|
else
|
|
co0 = '00';
|
|
co1 = '40';
|
|
break;
|
|
case VT100.A_REVERSE:
|
|
case VT100.A_REVERSE | VT100.A_DIM | VT100.A_BOLD:
|
|
co0 = 'c0'; co1 = '40';
|
|
break;
|
|
case VT100.A_REVERSE | VT100.A_BOLD:
|
|
co0 = 'c0'; co1 = '00';
|
|
break;
|
|
default:
|
|
if (fg == VT100.COLOR_BLACK)
|
|
co0 = '80';
|
|
else
|
|
co0 = 'c0';
|
|
co1 = 'c0';
|
|
}
|
|
return {
|
|
f: '#' + (fg & 4 ? co1 : co0) +
|
|
(fg & 2 ? co1 : co0) +
|
|
(fg & 1 ? co1 : co0),
|
|
b: '#' + (bg & 4 ? co1 : co0) +
|
|
(bg & 2 ? co1 : co0) +
|
|
(bg & 1 ? co1 : co0)
|
|
};
|
|
}
|
|
|
|
VT100.prototype.addch = function(ch, attr)
|
|
{
|
|
var cc = this.col_;
|
|
this.debug("addch:: ch: " + ch + ", attr: " + attr);
|
|
switch (ch) {
|
|
case '\b':
|
|
if (cc != 0)
|
|
--cc;
|
|
break;
|
|
case '\n':
|
|
++this.row_;
|
|
cc = 0;
|
|
this.clrtoeol();
|
|
this.may_scroll_();
|
|
break;
|
|
case '\r':
|
|
this.may_scroll_();
|
|
cc = 0;
|
|
break;
|
|
case '\t':
|
|
this.may_scroll_();
|
|
cc += VT100.TABSIZE - cc % VT100.TABSIZE;
|
|
if (cc >= this.wd_) {
|
|
++this.row_;
|
|
cc -= this.wd_;
|
|
}
|
|
break;
|
|
default:
|
|
if (attr === undefined)
|
|
attr = this._cloneAttr(this.c_attr_);
|
|
if (cc >= this.wd_) {
|
|
++this.row_;
|
|
cc = 0;
|
|
}
|
|
this.may_scroll_();
|
|
this.text_[this.row_][cc] = ch;
|
|
this.attr_[this.row_][cc] = attr;
|
|
++cc;
|
|
}
|
|
this.col_ = cc;
|
|
}
|
|
|
|
VT100.prototype.addstr = function(stuff)
|
|
{
|
|
for (var i = 0; i < stuff.length; ++i)
|
|
this.addch(stuff.charAt(i));
|
|
}
|
|
|
|
VT100.prototype._cloneAttr = function VT100_cloneAttr(a)
|
|
{
|
|
return {
|
|
mode: a.mode,
|
|
fg: a.fg,
|
|
bg: a.bg
|
|
};
|
|
}
|
|
|
|
VT100.prototype.attroff = function(a)
|
|
{
|
|
//dump("attroff: " + a + "\n");
|
|
a &= VT100.ATTR_FLAGS_;
|
|
this.c_attr_.mode &= ~a;
|
|
}
|
|
|
|
VT100.prototype.attron = function(a)
|
|
{
|
|
//dump("attron: " + a + "\n");
|
|
a &= VT100.ATTR_FLAGS_;
|
|
this.c_attr_.mode |= a;
|
|
}
|
|
|
|
VT100.prototype.attrset = function(a)
|
|
{
|
|
//dump("attrset: " + a + "\n");
|
|
this.c_attr_.mode = a;
|
|
}
|
|
|
|
VT100.prototype.fgset = function(fg)
|
|
{
|
|
//dump("fgset: " + fg + "\n");
|
|
this.c_attr_.fg = fg;
|
|
}
|
|
|
|
VT100.prototype.bgset = function(bg)
|
|
{
|
|
//dump("bgset: " + bg + "\n");
|
|
if (bg !== 0) {
|
|
this.warn("bgset: " + bg + "\n");
|
|
}
|
|
this.c_attr_.bg = bg;
|
|
}
|
|
|
|
VT100.prototype.bkgdset = function(a)
|
|
{
|
|
this.bkgd_ = a;
|
|
}
|
|
|
|
VT100.prototype.clear = function()
|
|
{
|
|
this.debug("clear");
|
|
this.row_ = this.col_ = 0;
|
|
this.scrolled_ = 0;
|
|
for (r = 0; r < this.ht_; ++r) {
|
|
for (c = 0; c < this.wd_; ++c) {
|
|
this.text_[r][c] = ' ';
|
|
this.attr_[r][c] = this._cloneAttr(this.bkgd_);
|
|
}
|
|
}
|
|
}
|
|
|
|
VT100.prototype.clrtobot = function()
|
|
{
|
|
this.debug("clrtobot, row: " + this.row_);
|
|
var ht = this.ht_;
|
|
var wd = this.wd_;
|
|
this.clrtoeol();
|
|
for (var r = this.row_ + 1; r < ht; ++r) {
|
|
for (var c = 0; c < wd; ++c) {
|
|
this.text_[r][c] = ' ';
|
|
this.attr_[r][c] = this.bkgd_;
|
|
}
|
|
}
|
|
}
|
|
|
|
VT100.prototype.clrtoeol = function()
|
|
{
|
|
this.debug("clrtoeol, col: " + this.col_);
|
|
var r = this.row_;
|
|
if (r >= this.ht_)
|
|
return;
|
|
for (var c = this.col_; c < this.wd_; ++c) {
|
|
this.text_[r][c] = ' ';
|
|
this.attr_[r][c] = this.bkgd_;
|
|
}
|
|
}
|
|
|
|
VT100.prototype.clearpos = function(row, col)
|
|
{
|
|
this.debug("clearpos (" + row + ", " + col + ")");
|
|
if (row < 0 || row >= this.ht_)
|
|
return;
|
|
if (col < 0 || col >= this.wd_)
|
|
return;
|
|
this.text_[row][col] = ' ';
|
|
this.attr_[row][col] = this.bkgd_;
|
|
}
|
|
|
|
VT100.prototype.curs_set = function(vis, grab, eventist)
|
|
{
|
|
this.debug("curs_set:: vis: " + vis + ", grab: " + grab);
|
|
if (vis !== undefined)
|
|
this.cursor_vis_ = (vis > 0);
|
|
if (eventist === undefined)
|
|
eventist = window;
|
|
if (grab === true || grab === false) {
|
|
if (grab === this.grab_events_)
|
|
return;
|
|
if (grab) {
|
|
this.grab_events_ = true;
|
|
VT100.the_vt_ = this;
|
|
eventist.addEventListener("keypress", VT100.handle_onkeypress_, false);
|
|
if (VT100.browser_ie_)
|
|
document.onkeydown = VT100.handle_onkeydown_;
|
|
} else {
|
|
eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false);
|
|
if (VT100.browser_ie_)
|
|
document.onkeydown = VT100.handle_onkeydown_;
|
|
this.grab_events_ = false;
|
|
VT100.the_vt_ = undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
VT100.prototype.echo = function()
|
|
{
|
|
this.debug("echo on");
|
|
this.echo_ = true;
|
|
}
|
|
|
|
VT100.prototype.erase = VT100.prototype.clear;
|
|
|
|
VT100.prototype.getch = function(isr)
|
|
{
|
|
this.debug("getch");
|
|
this.refresh();
|
|
this.getch_isr_ = isr;
|
|
setTimeout(VT100.go_getch_, 0);
|
|
}
|
|
|
|
VT100.prototype.getmaxyx = function()
|
|
{
|
|
return { y: this.ht_ - 1, x: this.wd_ - 1 };
|
|
}
|
|
|
|
VT100.prototype.getyx = function()
|
|
{
|
|
return { y: this.row_, x: this.col_ };
|
|
}
|
|
|
|
VT100.prototype.move = function(r, c)
|
|
{
|
|
this.debug("move: (" + r + ", " + c + ")");
|
|
if (r < 0)
|
|
r = 0;
|
|
else if (r >= this.ht_)
|
|
r = this.ht_ - 1;
|
|
if (c < 0)
|
|
c = 0;
|
|
else if (c >= this.wd_)
|
|
c = this.wd_ - 1;
|
|
this.row_ = r;
|
|
this.col_ = c;
|
|
}
|
|
|
|
VT100.prototype.noecho = function()
|
|
{
|
|
this.debug("echo off");
|
|
this.echo_ = false;
|
|
}
|
|
|
|
VT100.prototype.refresh = function()
|
|
{
|
|
this.debug("refresh");
|
|
var r, c, stuff = "", start_tag = "", end_tag = "", at = -1, n_at, ch,
|
|
pair, cr, cc, ht, wd, cv, added_end_tag;
|
|
ht = this.ht_;
|
|
wd = this.wd_;
|
|
cr = this.row_;
|
|
cc = this.col_;
|
|
cv = this.cursor_vis_;
|
|
var innerHTML = this.scr_.innerHTML;
|
|
if (cc >= wd)
|
|
cc = wd - 1;
|
|
for (r = 0; r < ht; ++r) {
|
|
if (r > 0) {
|
|
stuff += '\n';
|
|
}
|
|
for (c = 0; c < wd; ++c) {
|
|
added_end_tag = false;
|
|
n_at = this.attr_[r][c];
|
|
if (cv && r == cr && c == cc) {
|
|
// Draw the cursor here.
|
|
n_at = this._cloneAttr(n_at);
|
|
n_at.mode ^= VT100.A_REVERSE;
|
|
}
|
|
// If the attributes changed, make a new span.
|
|
if (n_at.mode != at.mode || n_at.fg != at.fg || n_at.bg != at.bg) {
|
|
if (c > 0) {
|
|
stuff += end_tag;
|
|
}
|
|
start_tag = "";
|
|
end_tag = "";
|
|
if (n_at.mode & VT100.A_BLINK) {
|
|
start_tag = "<blink>";
|
|
end_tag = "</blink>" + end_tag;
|
|
}
|
|
if (n_at.mode & VT100.A_STANDOUT)
|
|
n_at.mode |= VT100.A_BOLD;
|
|
pair = this.html_colours_(n_at);
|
|
start_tag += '<span style="color:' + pair.f +
|
|
';background-color:' + pair.b;
|
|
if (n_at.mode & VT100.A_UNDERLINE)
|
|
start_tag += ';text-decoration:underline';
|
|
start_tag += ';">';
|
|
stuff += start_tag;
|
|
end_tag = "</span>" + end_tag;
|
|
at = n_at;
|
|
added_end_tag = true;
|
|
} else if (c == 0) {
|
|
stuff += start_tag;
|
|
}
|
|
ch = this.text_[r][c];
|
|
switch (ch) {
|
|
case '&':
|
|
stuff += '&'; break;
|
|
case '<':
|
|
stuff += '<'; break;
|
|
case '>':
|
|
stuff += '>'; break;
|
|
case ' ':
|
|
//stuff += ' '; break;
|
|
stuff += ' '; break;
|
|
default:
|
|
stuff += ch;
|
|
}
|
|
}
|
|
if (!added_end_tag)
|
|
stuff += end_tag;
|
|
}
|
|
this.scr_.innerHTML = "<b>" + stuff + "</b>\n";
|
|
}
|
|
|
|
VT100.prototype.scroll = function()
|
|
{
|
|
this.scrolled_ += 1;
|
|
this.debug("scrolled: " + this.scrolled_);
|
|
var n_text = this.text_[0], n_attr = this.attr_[0],
|
|
ht = this.ht_, wd = this.wd_;
|
|
for (var r = 1; r < ht; ++r) {
|
|
this.text_[r - 1] = this.text_[r];
|
|
this.attr_[r - 1] = this.attr_[r];
|
|
}
|
|
this.text_[ht - 1] = n_text;
|
|
this.attr_[ht - 1] = n_attr;
|
|
for (var c = 0; c < wd; ++c) {
|
|
n_text[c] = ' ';
|
|
n_attr[c] = this.bkgd_;
|
|
}
|
|
}
|
|
|
|
VT100.prototype.standend = function()
|
|
{
|
|
//this.debug("standend");
|
|
this.attrset(0);
|
|
}
|
|
|
|
VT100.prototype.standout = function()
|
|
{
|
|
//this.debug("standout");
|
|
this.attron(VT100.A_STANDOUT);
|
|
}
|
|
|
|
VT100.prototype.write = function(stuff)
|
|
{
|
|
var ch, x, r, c, i, j, yx, myx;
|
|
for (i = 0; i < stuff.length; ++i) {
|
|
ch = stuff.charAt(i);
|
|
if (ch == '\x0D') {
|
|
this.debug("write:: ch: " + ch.charCodeAt(0) + ", '\\x0D'");
|
|
} else {
|
|
this.debug("write:: ch: " + ch.charCodeAt(0) + ", '" + (ch == '\x1b' ? "ESC" : ch) + "'");
|
|
}
|
|
//dump("ch: " + ch.charCodeAt(0) + ", '" + (ch == '\x1b' ? "ESC" : ch) + "'\n");
|
|
switch (ch) {
|
|
case '\x00':
|
|
case '\x7f':
|
|
case '\x07': /* bell, ignore it */
|
|
this.debug("write:: ignoring bell character: " + ch);
|
|
continue;
|
|
case '\a':
|
|
case '\b':
|
|
case '\t':
|
|
case '\r':
|
|
this.addch(ch);
|
|
continue;
|
|
case '\n':
|
|
case '\v':
|
|
case '\f': // what a mess
|
|
yx = this.getyx();
|
|
myx = this.getmaxyx();
|
|
if (yx.y >= myx.y) {
|
|
this.scroll();
|
|
this.move(myx.y, 0);
|
|
} else
|
|
this.move(yx.y + 1, 0);
|
|
continue;
|
|
case '\x18':
|
|
case '\x1a':
|
|
this.esc_state_ = 0;
|
|
this.debug("write:: set escape state: 0");
|
|
continue;
|
|
case '\x1b':
|
|
this.esc_state_ = 1;
|
|
this.debug("write:: set escape state: 1");
|
|
continue;
|
|
case '\x9b':
|
|
this.esc_state_ = 2;
|
|
this.debug("write:: set escape state: 2");
|
|
continue;
|
|
}
|
|
// not a recognized control character
|
|
switch (this.esc_state_) {
|
|
case 0: // not in escape sequence
|
|
this.addch(ch);
|
|
break;
|
|
case 1: // just saw ESC
|
|
switch (ch) {
|
|
case '[':
|
|
this.esc_state_ = 2;
|
|
this.debug("write:: set escape state: 2");
|
|
break;
|
|
case '=':
|
|
/* Set keypade mode (ignored) */
|
|
this.debug("write:: set keypade mode: ignored");
|
|
this.esc_state_ = 0;
|
|
break;
|
|
case '>':
|
|
/* Reset keypade mode (ignored) */
|
|
this.debug("write:: reset keypade mode: ignored");
|
|
this.esc_state_ = 0;
|
|
break;
|
|
case 'H':
|
|
/* Set tab at cursor column (ignored) */
|
|
this.debug("write:: set tab cursor column: ignored");
|
|
this.esc_state_ = 0;
|
|
break;
|
|
}
|
|
break;
|
|
case 2: // just saw CSI
|
|
switch (ch) {
|
|
case 'K':
|
|
/* Erase in Line */
|
|
this.esc_state_ = 0;
|
|
this.clrtoeol();
|
|
continue;
|
|
case 'H':
|
|
/* Move to (0,0). */
|
|
this.esc_state_ = 0;
|
|
this.move(0, 0);
|
|
continue;
|
|
case 'J':
|
|
/* Clear to the bottom. */
|
|
this.esc_state_ = 0;
|
|
this.clrtobot();
|
|
continue;
|
|
case '?':
|
|
/* Special VT100 mode handling. */
|
|
this.esc_state_ = 5;
|
|
this.debug("write:: special vt100 mode");
|
|
continue;
|
|
}
|
|
// Drop through to next case.
|
|
this.csi_parms_ = [0];
|
|
this.debug("write:: set escape state: 3");
|
|
this.esc_state_ = 3;
|
|
case 3: // saw CSI and parameters
|
|
switch (ch) {
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
x = this.csi_parms_.pop();
|
|
this.csi_parms_.push(x * 10 + ch * 1);
|
|
this.debug("csi_parms_: " + this.csi_parms_);
|
|
continue;
|
|
case ';':
|
|
if (this.csi_parms_.length < 17)
|
|
this.csi_parms_.push(0);
|
|
continue;
|
|
}
|
|
this.esc_state_ = 0;
|
|
switch (ch) {
|
|
case 'A':
|
|
// Cursor Up <ESC>[{COUNT}A
|
|
this.move(this.row_ - Math.max(1, this.csi_parms_[0]),
|
|
this.col_);
|
|
break;
|
|
case 'B':
|
|
// Cursor Down <ESC>[{COUNT}B
|
|
this.move(this.row_ + Math.max(1, this.csi_parms_[0]),
|
|
this.col_);
|
|
break;
|
|
case 'C':
|
|
// Cursor Forward <ESC>[{COUNT}C
|
|
this.move(this.row_,
|
|
this.col_ + Math.max(1, this.csi_parms_[0]));
|
|
break;
|
|
case 'c':
|
|
this.warn("write:: got TERM query");
|
|
break;
|
|
case 'D':
|
|
// Cursor Backward <ESC>[{COUNT}D
|
|
this.move(this.row_,
|
|
this.col_ - Math.max(1, this.csi_parms_[0]));
|
|
break;
|
|
case 'f':
|
|
case 'H':
|
|
// Cursor Home <ESC>[{ROW};{COLUMN}H
|
|
this.csi_parms_.push(0);
|
|
this.move(this.csi_parms_[0] - 1,
|
|
this.csi_parms_[1] - 1);
|
|
break;
|
|
case 'J':
|
|
switch (this.csi_parms_[0]) {
|
|
case 0:
|
|
this.clrtobot();
|
|
break;
|
|
case 2:
|
|
this.clear();
|
|
this.move(0, 0);
|
|
}
|
|
break;
|
|
case 'm':
|
|
for (j=0; j<this.csi_parms_.length; ++j) {
|
|
x = this.csi_parms_[j];
|
|
switch (x) {
|
|
case 0:
|
|
this.standend();
|
|
this.fgset(this.bkgd_.fg);
|
|
this.bgset(this.bkgd_.bg);
|
|
break;
|
|
case 1:
|
|
this.attron(VT100.A_BOLD);
|
|
break;
|
|
case 30:
|
|
this.fgset(VT100.COLOR_BLACK);
|
|
break;
|
|
case 31:
|
|
this.fgset(VT100.COLOR_RED);
|
|
break;
|
|
case 32:
|
|
this.fgset(VT100.COLOR_GREEN);
|
|
break;
|
|
case 33:
|
|
this.fgset(VT100.COLOR_YELLOW);
|
|
break;
|
|
case 34:
|
|
this.fgset(VT100.COLOR_BLUE);
|
|
break;
|
|
case 35:
|
|
this.fgset(VT100.COLOR_MAGENTA);
|
|
break;
|
|
case 36:
|
|
this.fgset(VT100.COLOR_CYAN);
|
|
break;
|
|
case 37:
|
|
this.fgset(VT100.COLOR_WHITE);
|
|
break;
|
|
case 40:
|
|
this.bgset(VT100.COLOR_BLACK);
|
|
break;
|
|
case 41:
|
|
this.bgset(VT100.COLOR_RED);
|
|
break;
|
|
case 42:
|
|
this.bgset(VT100.COLOR_GREEN);
|
|
break;
|
|
case 44:
|
|
this.bgset(VT100.COLOR_YELLOW);
|
|
break;
|
|
case 44:
|
|
this.bgset(VT100.COLOR_BLUE);
|
|
break;
|
|
case 45:
|
|
this.bgset(VT100.COLOR_MAGENTA);
|
|
break;
|
|
case 46:
|
|
this.bgset(VT100.COLOR_CYAN);
|
|
break;
|
|
case 47:
|
|
this.bgset(VT100.COLOR_WHITE);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'r':
|
|
// 1,24r - set scrolling region (ignored)
|
|
break;
|
|
case '[':
|
|
this.debug("write:: set escape state: 4");
|
|
this.esc_state_ = 4;
|
|
break;
|
|
case 'g':
|
|
// 0g: clear tab at cursor (ignored)
|
|
// 3g: clear all tabs (ignored)
|
|
break;
|
|
default:
|
|
this.warn("write:: unknown command: " + ch);
|
|
this.csi_parms_ = [];
|
|
break;
|
|
}
|
|
break;
|
|
case 4: // saw CSI [
|
|
this.esc_state_ = 0; // gobble char.
|
|
break;
|
|
case 5: // Special mode handling, saw <ESC>[?
|
|
// Expect a number - the reset type
|
|
this.csi_parms_ = [ch];
|
|
this.esc_state_ = 6;
|
|
break;
|
|
case 6: // Reset mode handling, saw <ESC>[?1
|
|
// Expect a letter - the mode target, example:
|
|
// <ESC>[?1l : cursor key mode = cursor
|
|
// <ESC>[?1h : save current screen, create new empty
|
|
// screen and position at 0,0
|
|
// <ESC>[?5l : White on blk
|
|
// XXX: Ignored for now.
|
|
//dump("Saw reset mode: <ESC>[?" + this.csi_parms_[0] + ch + "\n");
|
|
this.esc_state_ = 0;
|
|
this.debug("write:: set escape state: 0");
|
|
break;
|
|
}
|
|
}
|
|
this.refresh();
|
|
}
|
|
|
|
VT100.prototype.debug = function(message) {
|
|
if (this.debug_) {
|
|
dump(message + "\n");
|
|
}
|
|
}
|
|
|
|
VT100.prototype.warn = function(message) {
|
|
dump(message + "\n");
|
|
}
|