Compare commits

...

10 commits

Author SHA1 Message Date
John "Elwin" Edwards
29f17aa874 Fix race condition related to watching DGL games.
It's possible for a dgamelaunch game to end and cause rlgwebd to stop watching
it before rlgwebd has started watching it.
2020-08-16 20:56:18 -04:00
John "Elwin" Edwards
c38fb5d107 rlgwebd: use some newer Javascript features. 2020-04-03 15:15:02 -04:00
John "Elwin" Edwards
9004afebdc Terminal emulation: implement the CSI b sequence.
CSI b repeats the previous character, if it was printable and not an
escape sequence.
2019-09-05 15:19:27 -04:00
John "Elwin" Edwards
f2256500e1 Changes for compatibility with recent versions of NodeJS.
The pty.js module is replaced with node-pty, now-mandatory callbacks
are added to various fs functions, and deprecated Buffer() calls are
replaced with Buffer.from() or Buffer.alloc().
2019-08-25 21:27:31 -04:00
John "Elwin" Edwards
4940bf86ae Move the NODE_PATH location.
Modules are now expected to be in /var/local/lib/node_modules.  This is
intended to make it easier to avoid running npm as root.
2017-12-26 13:23:55 -05:00
John "Elwin" Edwards
4059bf2983 Fix possibly insecure permissions on the control socket.
The server's control socket is now in a private directory.
2017-01-28 09:57:31 -05:00
John "Elwin" Edwards
c4d10ba33d rlgwebd-stop: avoid the deprecated domain module.
Instead of catching connection errors with domains, install an error
listener on the socket before connecting.
2017-01-27 19:18:31 -05:00
John "Elwin" Edwards
2f40fc5387 RLGWebD: replace deprecated fs.exists() with fs.access(). 2017-01-27 15:43:10 -05:00
John "Elwin" Edwards
c824ea924c Fix syntax error in Makefile. 2017-01-12 19:02:20 -05:00
John "Elwin" Edwards
5b790718d8 Use either HTTP or HTTPS.
If HTTPS is enabled, RLGWebD will not use insecure HTTP.
2017-01-08 16:17:11 -05:00
8 changed files with 247 additions and 163 deletions

View file

@ -23,7 +23,7 @@ install: all
mkdir -p ${CHROOT}/var/www
cp ${WEBASSETS} ${CHROOT}/var/www
cp rlgwebd.service /usr/lib/systemd/system
if test ! -f /etc/rlgwebd.conf; cp rlgwebd.conf /etc; fi
if test ! -f /etc/rlgwebd.conf; then cp rlgwebd.conf /etc; fi
# Libraries are not removed. Something else might be using them.
uninstall:

View file

@ -5,12 +5,11 @@ browser. It is intended to be compatible with dgamelaunch.
Node
---
RLGWebD currently works with Node v0.10.
RLGWebD is currently being updated to work with Node v10.x.
It requires the 'posix', 'pty.js', and 'websocket' modules. Currently,
it expects them to be installed in the global location, which is
"/usr/lib/node_modules". It is planned to eventually use a different
location so that npm will not need to run as root.
It requires the 'posix', 'node-pty', and 'websocket' modules. Currently,
it expects them to be installed in "/var/local/lib/node_modules". It
is not recommended to run npm as root when installing the modules.
init
---
@ -22,14 +21,11 @@ a proper initscript, but it could form the basis of one.
Configuration
---
You can set some options by changing some variables in the first few
lines of the rlgwebd script:
A configuration file is installed at /etc/rlgwebd.conf. It contains a
list of options.
Option Variable Default
Chroot path chrootDir /var/dgl
Username dropToUser rodney
Server port httpPort 8080
If the domain_name option and the SSL-related options are set, rlgwebd
will use HTTPS instead of insecure HTTP.
If you change the chroot location, change it in the first line of the
Makefile too.
@ -59,6 +55,7 @@ Running "make install" will:
Copy the C programs and the libraries they need into the chroot
Install the main RLGWebD script in /usr/local/bin
Place the systemd unit file in the proper directory
Copy a configuration file into /etc
If you don't use systemd, or want to change the installation locations,
you will have to edit the Makefile.

View file

@ -1,8 +1,8 @@
#!/bin/sh
NODE_PATH=/usr/lib/node_modules
NODE_PATH=/var/local/lib/node_modules
LOGFILE=/var/log/rlgwebd.log
CTLSOCKET=/var/run/rlgwebd.sock
CTLSOCKET=/var/run/rlgwebd/rlgwebd.sock
RLGWEBDJS=/usr/local/bin/rlgwebd
export NODE_PATH

160
rlgwebd
View file

@ -1,35 +1,38 @@
#!/usr/bin/env node
var http = require('http');
var https = require('https');
var net = require('net');
var url = require('url');
var path = require('path');
var fs = require('fs');
var events = require('events');
var child_process = require('child_process');
const http = require('http');
const https = require('https');
const net = require('net');
const url = require('url');
const path = require('path');
const fs = require('fs');
const events = require('events');
const child_process = require('child_process');
// Dependencies
var posix = require("posix");
var pty = require("pty.js");
var WebSocketServer = require("websocket").server;
const posix = require("posix");
const pty = require("node-pty");
const WebSocketServer = require("websocket").server;
const errorcodes = [ "Generic Error", "Not logged in", "Invalid data",
"Login failed", "Already playing", "Game launch failed",
"Server shutting down", "Game not in progress" ];
/* Default options */
var rlgwebd_options = {
control_socket: "/var/run/rlgwebd.sock",
http_port: 8080,
https_port: 8081,
control_socket: "/var/run/rlgwebd/rlgwebd.sock",
port: 8080,
chrootDir: "/var/dgl/",
username: "rodney",
static_root: "/var/www/"
};
/* Read configuration from a file */
var config_file = "/etc/rlgwebd.conf";
const config_file = "/etc/rlgwebd.conf";
var config_lines = read_or_die(config_file, "Configuration file").toString().split('\n');
for (var i = 0; i < config_lines.length; i++) {
if (config_lines[i].length > 0 && config_lines[i][0] != '#') {
var config_fields = config_lines[i].split('=');
for (let conf_line of config_lines) {
if (conf_line.length > 0 && conf_line[0] != '#') {
var config_fields = conf_line.split('=');
if (config_fields.length < 2)
continue;
var option_name = config_fields[0].trim();
@ -44,13 +47,13 @@ if ("domain_name" in rlgwebd_options && "keyfile" in rlgwebd_options &&
"certfile" in rlgwebd_options)
rlgwebd_options["use_https"] = true;
var clearbufs = [
new Buffer([27, 91, 72, 27, 91, 50, 74]), // xterm: CSI H CSI 2J
new Buffer([27, 91, 72, 27, 91, 74]) // screen: CSI H CSI J
const clearbufs = [
Buffer.from([27, 91, 72, 27, 91, 50, 74]), // xterm: CSI H CSI 2J
Buffer.from([27, 91, 72, 27, 91, 74]) // screen: CSI H CSI J
];
/* Data on the games available. */
var games = {
const games = {
"rogue3": {
"name": "Rogue V3",
"uname": "rogue3",
@ -110,7 +113,7 @@ function BaseGame() {
this.watchers = [];
/* replaybuf holds the output since the last screen clear, so watchers can
* begin with a complete screen. replaylen is the number of bytes stored. */
this.replaybuf = new Buffer(1024);
this.replaybuf = Buffer.alloc(1024);
this.replaylen = 0;
/* Time of last activity. */
this.lasttime = new Date();
@ -126,12 +129,12 @@ BaseGame.prototype.tag = function () {
BaseGame.prototype.framepush = function(chunk) {
/* If this chunk resets the screen, discard what preceded it. */
if (isclear(chunk)) {
this.replaybuf = new Buffer(1024);
this.replaybuf = Buffer.alloc(1024);
this.replaylen = 0;
}
/* Make sure there's space. */
while (this.replaybuf.length < chunk.length + this.replaylen) {
var nbuf = new Buffer(this.replaybuf.length * 2);
var nbuf = Buffer.alloc(this.replaybuf.length * 2);
this.replaybuf.copy(nbuf, 0, 0, this.replaylen);
this.replaybuf = nbuf;
if (this.replaybuf.length > 65536) {
@ -211,7 +214,9 @@ function TermSession(gname, pname, wsReq) {
var progressdir = path.join("/dgldir/inprogress", this.gname);
this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec");
var lmsg = this.term.pid.toString() + '\n' + this.h + '\n' + this.w + '\n';
fs.writeFile(this.lock, lmsg, "utf8");
fs.writeFile(this.lock, lmsg, "utf8", function (err) {
if (err) tslog("Locking failed: %s", err);
});
var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname,
ts + ".ttyrec");
this.record = fs.createWriteStream(ttyrec, { mode: 0664 });
@ -239,8 +244,8 @@ TermSession.prototype = new BaseGame();
/* Currently this also sends to the player and any watchers. */
TermSession.prototype.write_ttyrec = function (datastr) {
this.lasttime = new Date();
var buf = new Buffer(datastr);
var chunk = new Buffer(buf.length + 12);
var buf = Buffer.from(datastr);
var chunk = Buffer.alloc(buf.length + 12);
/* TTYREC headers */
chunk.writeUInt32LE(Math.floor(this.lasttime.getTime() / 1000), 0);
chunk.writeUInt32LE(1000 * (this.lasttime.getTime() % 1000), 4);
@ -274,7 +279,7 @@ TermSession.prototype.input_msg = function (message) {
if (hexstr.length % 2 != 0) {
hexstr = hexstr.slice(0, -1);
}
var keybuf = new Buffer(hexstr, "hex");
var keybuf = Buffer.from(hexstr, "hex");
this.write(keybuf);
}
};
@ -287,7 +292,9 @@ TermSession.prototype.close = function () {
TermSession.prototype.destroy = function () {
var tag = this.tag();
fs.unlink(this.lock);
fs.unlink(this.lock, function (err) {
if (err) tslog("Lock removal failed: %s", err);
});
this.record.end();
var watchsocks = this.watchers;
this.watchers = [];
@ -356,7 +363,7 @@ DglSession.prototype.startchunk = function () {
if (this.reading)
return;
this.reading = true;
var header = new Buffer(12);
var header = Buffer.alloc(12);
fs.read(this.fd, header, 0, 12, this.rpos, this.datachunk.bind(this));
};
@ -377,7 +384,7 @@ DglSession.prototype.datachunk = function (err, n, buf) {
// Something is probably wrong...
tslog("DGL %s: looking for %d bytes", this.tag(), datalen);
}
var databuf = new Buffer(datalen);
var databuf = Buffer.alloc(datalen);
fs.read(this.fd, databuf, 0, datalen, this.rpos, this.handledata.bind(this));
};
@ -394,9 +401,9 @@ DglSession.prototype.handledata = function (err, n, buf) {
/* Process the data */
this.framepush(buf);
var wmsg = JSON.stringify({"t": "d", "d": buf.toString("hex")});
for (var i = 0; i < this.watchers.length; i++) {
if (this.watchers[i].connected)
this.watchers[i].sendUTF(wmsg);
for (let watcher of this.watchers) {
if (watcher.connected)
watcher.sendUTF(wmsg);
}
this.emit("data", buf);
/* Recurse. */
@ -411,7 +418,9 @@ DglSession.prototype.notifier = function (ev, finame) {
};
DglSession.prototype.close = function () {
this.recwatcher.close();
/* The watcher might not be open yet. */
if ("recwatcher" in this)
this.recwatcher.close();
/* Ensure all data is handled before quitting. */
this.startchunk();
var connlist = this.watchers;
@ -420,7 +429,9 @@ DglSession.prototype.close = function () {
if (connlist[i].connected)
connlist[i].close();
}
fs.close(this.fd);
fs.close(this.fd, function (err) {
if (err) tslog("PTY close failed: %s", err);
});
this.emit("close");
gamemux.emit('end', this.gname, this.pname);
tslog("DGL %s: closed", this.tag());
@ -487,8 +498,11 @@ function checksaved(user, game, callback, args) {
var savedirc = game.uname + "save";
var basename = String(pwent.uid) + "-" + user + game.suffix;
var savefile = path.join("/var/games/roguelike", savedirc, basename);
fs.exists(savefile, function (exist) {
args.unshift(exist);
fs.access(savefile, function (err) {
if (err)
args.unshift(false);
else
args.unshift(true);
callback.apply(null, args);
});
}
@ -567,8 +581,8 @@ function bufncmp(buf1, buf2, n) {
}
function isclear(buf) {
for (var i = 0; i < clearbufs.length; i++) {
if (bufncmp(buf, clearbufs[i], clearbufs[i].length))
for (let clearer of clearbufs) {
if (bufncmp(buf, clearer, clearer.length))
return true;
}
return false;
@ -688,10 +702,15 @@ function login(req, res, formdata) {
function regsetup(username) {
function regsetup_l2(err) {
for (var g in games) {
fs.mkdir(path.join("/dgldir/ttyrec", username, games[g].uname), 0755);
fs.mkdir(path.join("/dgldir/ttyrec", username, games[g].uname), 0755,
function (err) {
if (err) tslog("ttyrec mkdir failed: %s", err);
});
}
}
fs.mkdir(path.join("/dgldir/userdata", username), 0755);
fs.mkdir(path.join("/dgldir/userdata", username), 0755, function (err) {
if (err) tslog("Userdata mkdir failed: %s", err);
});
fs.mkdir(path.join("/dgldir/ttyrec/", username), 0755, regsetup_l2);
}
@ -779,7 +798,9 @@ function stopgame(res, formdata) {
if (err.code == "ESRCH") {
var nodere = RegExp("^" + pname + ":node:");
if (fname.match(nodere)) {
fs.unlink(fullfile);
fs.unlink(fullfile, function (err) {
if (err) tslog("Stale lock removal failed: %s", err);
});
}
}
}
@ -832,6 +853,8 @@ function startProgressWatcher() {
}
function serveStatic(req, res, fname) {
if (fname[0] !== "/")
fname = "/" + fname;
var nname = path.normalize(fname);
if (nname == "" || nname == "/")
nname = "index.html";
@ -839,9 +862,9 @@ function serveStatic(req, res, fname) {
path.join(nname, "index.html"); /* it was a directory */
var realname = path.join(rlgwebd_options.static_root, nname);
var extension = path.extname(realname);
fs.exists(realname, function (exists) {
fs.access(realname, function (access_err) {
var resheaders = {};
if (!exists || !extension || extension == ".html")
if (access_err || !extension || extension == ".html")
resheaders["Content-Type"] = "text/html; charset=utf-8";
else if (extension == ".png")
resheaders["Content-Type"] = "image/png";
@ -853,7 +876,7 @@ function serveStatic(req, res, fname) {
resheaders["Content-Type"] = "image/svg+xml";
else
resheaders["Content-Type"] = "application/octet-stream";
if (exists) {
if (!access_err) {
fs.readFile(realname, function (error, data) {
if (error) {
res.writeHead(500, {});
@ -1024,10 +1047,6 @@ function setuinfo(req, res, postdata) {
}
}
var errorcodes = [ "Generic Error", "Not logged in", "Invalid data",
"Login failed", "Already playing", "Game launch failed",
"Server shutting down", "Game not in progress" ];
function sendError(res, ecode, msg, box) {
res.writeHead(200, { "Content-Type": "application/json" });
var edict = {"t": "E"};
@ -1264,6 +1283,21 @@ if (rlgwebd_options.use_https) {
tls_options.ca = read_or_die(rlgwebd_options.cafile, "CA file");
};
/* Make sure the socket directory is secure. */
var socket_dir = path.dirname(rlgwebd_options.control_socket);
try {
fs.mkdirSync(socket_dir, 0o700);
}
catch (err) {
if (err.code == "EEXIST") {
fs.chownSync(socket_dir, 0, 0);
fs.chmodSync(socket_dir, 0o700);
}
else {
throw err;
}
}
/* Open the control socket before chrooting where it can't be found */
var ctlServer = net.createServer(function (sock) {
sock.on('data', consoleHandler);
@ -1288,20 +1322,22 @@ ctlServer.listen(rlgwebd_options.control_socket, function () {
tslog("Could not drop permissions: %s", err);
process.exit(1);
}
httpServer = http.createServer(webHandler);
httpServer.listen(rlgwebd_options.http_port);
tslog('rlgwebd running on port %d', rlgwebd_options.http_port);
wsServer = new WebSocketServer({"httpServer": httpServer});
wsServer.on("request", wsHandler);
tslog('WebSockets are online');
if (rlgwebd_options.use_https) {
var httpsServer = https.createServer(tls_options, webHandler);
httpsServer.listen(rlgwebd_options.https_port);
tslog('TLS running on port %d', rlgwebd_options.https_port);
var wssServer = new WebSocketServer({"httpServer": httpsServer});
wssServer.on("request", wsHandler);
httpServer = https.createServer(tls_options, webHandler);
httpServer.listen(rlgwebd_options.port);
tslog('rlgwebd running on port %d (TLS)', rlgwebd_options.port);
wsServer = new WebSocketServer({"httpServer": httpServer});
wsServer.on("request", wsHandler);
tslog('Secure WebSockets are online');
}
else {
httpServer = http.createServer(webHandler);
httpServer.listen(rlgwebd_options.port);
tslog('rlgwebd running on port %d', rlgwebd_options.port);
wsServer = new WebSocketServer({"httpServer": httpServer});
wsServer.on("request", wsHandler);
tslog('WebSockets are online');
}
progressWatcher = startProgressWatcher();
setInterval(pushStatus, 40000);
});

View file

@ -1,22 +1,19 @@
#!/usr/bin/env node
var net = require('net');
var domain = require('domain');
var sockpath = "/var/run/rlgwebd.sock";
var sockpath = "/var/run/rlgwebd/rlgwebd.sock";
var dom = domain.create();
var sock = new net.Socket();
dom.on('error', function (err) {
sock.on('error', function (err) {
console.log("Cannot connect to " + sockpath + ", rlgwebd already stopped.");
process.exit(0);
});
dom.run(function () {
var sock = net.connect(sockpath, function () {
sock.on('close', function () {
if (process.argv[2] == "debug")
console.log("Control socket closed");
});
sock.write("quit\n");
sock.connect(sockpath, function () {
sock.on('close', function (had_error) {
if (process.argv[2] == "debug")
console.log("Control socket closed");
});
sock.write("quit\n");
});

View file

@ -3,11 +3,9 @@
# These values are set by default:
# Location of the socket for start/stop commands
#control_socket = /var/run/rlgwebd.sock
#control_socket = /var/run/rlgwebd/rlgwebd.sock
# Port number to bind
#http_port = 8080
# Port number for HTTPS
#https_port = 8081
#port = 8080
# Path to the dgamelaunch installation to chroot into
# If you change this, change the Makefile too
#chrootDir = /var/dgl/

View file

@ -4,7 +4,7 @@ After=network.target syslog.target
[Service]
Type=simple
Environment=NODE_PATH=/usr/lib/node_modules
Environment=NODE_PATH=/var/local/lib/node_modules
ExecStart=/usr/local/bin/rlgwebd
ExecStop=/usr/local/bin/rlgwebd-stop
Restart=on-failure

View file

@ -71,6 +71,7 @@ var termemu = {
scrB: 0, // init() will set this properly
c: null, // Contains cursor position and text attributes
offedge: false, // Going off the edge doesn't mean adding a new line
lastcode: 0, // Last printed character
clearAttrs: function () {
/* Make sure to reset ALL attribute properties and NOTHING else. */
this.c.bold = false;
@ -460,6 +461,7 @@ var termemu = {
this.screen.replaceChild(this.makeRow(), this.screen.childNodes[i]);
}
this.flipCursor(); // make it appear in the new row
this.lastcode = 0;
return;
},
write: function (codes) {
@ -531,6 +533,10 @@ var termemu = {
debug(1, "Unrecognized sequence ESC " + codes[i].toString(16));
this.comseq = [];
}
if (this.comseq.length == 0) {
// A complete sequence was processed, clear lastcode.
this.lastcode = 0;
}
}
else if (this.comseq.length == 2 && this.comseq[0] == 27) {
/* An ESC C N sequence. Not implemented. Doesn't check validity
@ -555,6 +561,7 @@ var termemu = {
String.fromCharCode(this.comseq[1]) + " 0x" +
codes[i].toString(16));
this.comseq = [];
this.lastcode = 0;
}
else if (this.comseq[0] == 157) {
/* Commands beginning with OSC */
@ -566,6 +573,7 @@ var termemu = {
debug(0, "Got " + (this.comseq.length - 1) + "-byte OSC sequence");
this.oscProcess();
this.comseq = [];
this.lastcode = 0;
}
else
this.comseq.push(codes[i]);
@ -582,15 +590,17 @@ var termemu = {
/* Chars in csiPre can only occur right after the CSI */
debug(1, "Invalid CSI sequence: misplaced prefix");
this.comseq = [];
this.lastcode = 0;
}
else
this.comseq.push(codes[i]);
}
else if (csiPost.indexOf(this.comseq[this.comseq.length - 1]) >= 0 &&
!csiFinal(codes[i])) {
/* Chars is csiPost must come right before the final char */
/* Chars in csiPost must come right before the final char */
debug(1, "Invalid CSI sequence: misplaced postfix");
this.comseq = [];
this.lastcode = 0;
}
else if ((codes[i] >= 48 && codes[i] <= 57) || codes[i] == 59 ||
csiPost.indexOf(codes[i]) >= 0) {
@ -605,91 +615,102 @@ var termemu = {
else {
debug(1, "Invalid CSI sequence: unknown code " + codes[i].toString(16));
this.comseq = [];
this.lastcode = 0;
}
}
else {
debug(1, "Unknown sequence with " + this.comseq[0].toString(16));
this.comseq = [];
}
continue;
}
/* Treat it as a single character. */
if (codes[i] == 5) {
sendback("06");
}
else if (codes[i] == 7) {
/* bell */
bell(true);
}
else if (codes[i] == 8) {
/* backspace */
if (this.offedge)
this.offedge = false;
else if (this.c.x > 0)
this.cmove(null, this.c.x - 1);
}
else if (codes[i] == 9) {
/* tab */
var xnew;
if (this.c.x < this.w - 1) {
xnew = 8 * (Math.floor(this.c.x / 8) + 1);
if (xnew >= this.w)
xnew = this.w - 1;
this.cmove(null, xnew);
}
else {
this.offedge = true;
this.lastcode = 0;
}
}
else if (codes[i] >= 10 && codes[i] <= 12) {
/* newline, vertical tab, form feed */
if (this.offedge)
this.newline(true);
else
this.newline(false);
}
else if (codes[i] == 13) {
/* carriage return \r */
this.cmove(null, 0);
}
else if (codes[i] == 14) {
/* shift out */
// Currently assuming that G1 is DEC Special & Line Drawing
this.c.cset = "0";
debug(0, "Using DEC graphics charset.");
}
else if (codes[i] == 15) {
/* shift in */
// Currently assuming that G0 is ASCII
this.c.cset = "B";
debug(0, "Using ASCII charset.");
}
else if (codes[i] == 27) {
/* escape */
this.comseq.push(codes[i]);
}
else if (codes[i] < 32 || (codes[i] >= 127 && codes[i] < 160)) {
/* Some kind of control character. */
debug(1, "Unprintable character 0x" + codes[i].toString(16));
}
else {
else if ((codes[i] >= 32 && codes[i] < 127) || codes[i] >= 160) {
/* If it's ASCII, it's printable; take a risk on anything higher */
if ((this.c.cset == "0") && (codes[i] in decChars)) {
// DEC special character set
this.placechar(String.fromCharCode(decChars[codes[i]]));
this.lastcode = decChars[codes[i]];
}
else {
this.placechar(String.fromCharCode(codes[i]));
this.lastcode = codes[i];
}
this.placechar(String.fromCharCode(this.lastcode));
}
else {
/* Treat it as a single control character. */
this.singleCtl(codes[i]);
}
}
return;
},
singleCtl: function (ctlcode) {
if (ctlcode == 5) {
sendback("06");
}
else if (ctlcode == 7) {
/* bell */
bell(true);
}
else if (ctlcode == 8) {
/* backspace */
if (this.offedge)
this.offedge = false;
else if (this.c.x > 0)
this.cmove(null, this.c.x - 1);
}
else if (ctlcode == 9) {
/* tab */
var xnew;
if (this.c.x < this.w - 1) {
xnew = 8 * (Math.floor(this.c.x / 8) + 1);
if (xnew >= this.w)
xnew = this.w - 1;
this.cmove(null, xnew);
}
else {
this.offedge = true;
}
}
else if (ctlcode >= 10 && ctlcode <= 12) {
/* newline, vertical tab, form feed */
if (this.offedge)
this.newline(true);
else
this.newline(false);
}
else if (ctlcode == 13) {
/* carriage return \r */
this.cmove(null, 0);
}
else if (ctlcode == 14) {
/* shift out */
// Currently assuming that G1 is DEC Special & Line Drawing
this.c.cset = "0";
debug(0, "Using DEC graphics charset.");
}
else if (ctlcode == 15) {
/* shift in */
// Currently assuming that G0 is ASCII
this.c.cset = "B";
debug(0, "Using ASCII charset.");
}
else if (ctlcode == 27) {
/* escape */
this.comseq.push(27);
}
else {
debug(1, "Unprintable character 0x" + ctlcode.toString(16));
}
if (ctlcode != 27) {
// Sequences should preserve lastcode until they are completed
this.lastcode = 0;
}
},
csiProcess: function () {
/* Processes the CSI sequence in this.comseq */
var c = this.comseq[this.comseq.length - 1];
if (this.comseq[0] != 155 || !csiFinal(c))
return;
var printed = false;
var comstr = "";
for (var i = 1; i < this.comseq.length; i++)
comstr += String.fromCharCode(this.comseq[i]);
@ -698,6 +719,7 @@ var termemu = {
var matchCSI = comstr.match(reCSI);
if (!matchCSI) {
debug(1, "Unrecognized CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
var prefix = null;
@ -725,6 +747,7 @@ var termemu = {
/* @ - insert spaces at cursor */
if (prefix || postfix) {
debug(1, "Invalid CSI @ sequence: " + comstr);
this.lastcode = 0;
return;
}
/* The cursor stays still, but characters move out from under it. */
@ -745,6 +768,7 @@ var termemu = {
/* E - next line, F - previous line, G - to column */
if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
/* These may be out of range, but cmove will take care of that. */
@ -769,6 +793,7 @@ var termemu = {
var y = 0;
if (prefix || postfix) {
debug(1, "Invalid CSI H sequence: " + comstr);
this.lastcode = 0;
return;
}
if (params[0])
@ -787,6 +812,7 @@ var termemu = {
var x = this.c.x;
if (prefix || postfix) {
debug(1, "Invalid CSI I sequence: " + comstr);
this.lastcode = 0;
return;
}
while (count > 0) {
@ -808,6 +834,7 @@ var termemu = {
debug(1, "Warning: CSI ?J not implemented");
else if (prefix || postfix) {
debug(1, "Invalid CSI J sequence: " + comstr);
this.lastcode = 0;
return;
}
if (!params[0]) {
@ -828,6 +855,7 @@ var termemu = {
}
else {
debug(1, "Unimplemented parameter in CSI J sequence: " + comstr);
this.lastcode = 0;
return;
}
for (var nrow = start; nrow <= end; nrow++) {
@ -855,6 +883,7 @@ var termemu = {
debug(1, "Warning: CSI ?K not implemented");
else if (prefix || postfix) {
debug(1, "Invalid CSI K sequence: " + comstr);
this.lastcode = 0;
return;
}
/* 0 (default): right, 1: left, 2: all. Include cursor position. */
@ -885,11 +914,14 @@ var termemu = {
* M - delete current lines */
if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
/* CSI LM have no effect outside of the scrolling region */
if (this.c.y < this.scrT || this.c.y > this.scrB)
if (this.c.y < this.scrT || this.c.y > this.scrB) {
this.lastcode = 0;
return;
}
this.flipCursor();
while (count > 0) {
var blankrow = this.makeRow();
@ -915,6 +947,7 @@ var termemu = {
/* P - delete at active position, causing cells on the right to shift. */
if (prefix || postfix) {
debug(1, "Invalid CSI P sequence: " + comstr);
this.lastcode = 0;
return;
}
var cursrow = this.screen.childNodes[this.c.y];
@ -930,6 +963,7 @@ var termemu = {
/* S - scroll up, T - scroll down */
if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
if (c == 83)
@ -941,6 +975,7 @@ var termemu = {
/* X - erase characters */
if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
var row = this.screen.childNodes[this.c.y];
@ -954,6 +989,7 @@ var termemu = {
var x = this.c.x;
if (prefix || postfix) {
debug(1, "Invalid CSI Z sequence: " + comstr);
this.lastcode = 0;
return;
}
while (count > 0) {
@ -970,14 +1006,26 @@ var termemu = {
/* ` - go to col */
if (prefix || postfix) {
debug(1, "Invalid CSI ` sequence: " + comstr);
this.lastcode = 0;
return;
}
this.cmove(null, count - 1);
}
else if (c == 98) {
/* b - repeat previous character */
if (this.lastcode !== 0) {
while (count > 0) {
this.placechar(String.fromCharCode(this.lastcode));
count--;
}
printed = true;
}
}
else if (c == 99) {
/* c - query terminal attributes */
if (prefix !== null) {
debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
/* "CSI ? 1 ; 2 c" - VT100 */
@ -987,6 +1035,7 @@ var termemu = {
/* d - go to row */
if (prefix || postfix) {
debug(1, "Invalid CSI d sequence: " + comstr);
this.lastcode = 0;
return;
}
this.cmove(count - 1, null);
@ -997,6 +1046,7 @@ var termemu = {
var y = 0;
if (prefix || postfix) {
debug(1, "Invalid CSI f sequence: " + comstr);
this.lastcode = 0;
return;
}
if (params[0])
@ -1009,6 +1059,7 @@ var termemu = {
/* h - set modes */
if (prefix != '?') {
debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
for (var i = 0; i < params.length; i++) {
@ -1047,6 +1098,7 @@ var termemu = {
}
else {
debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
}
@ -1080,6 +1132,7 @@ var termemu = {
/* m - character attributes */
if (prefix !== null) {
debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return;
}
if (params.length == 0)
@ -1133,15 +1186,18 @@ var termemu = {
t = params[0] - 1;
if (params[1] && params[1] <= this.h)
b = params[1] - 1;
if (b <= t)
return;
this.scrT = t;
this.scrB = b;
this.cmove(0, 0);
if (b > t) {
this.scrT = t;
this.scrB = b;
this.cmove(0, 0);
}
}
else {
debug(1, "Unimplemented CSI sequence: " + comstr);
}
if (!printed) {
this.lastcode = 0;
}
return;
},
oscProcess: function () {