pty.js-dl
Version:
Pseudo terminals for node.
349 lines (277 loc) • 6.42 kB
JavaScript
/**
* pty.js
* Copyright (c) 2012, Christopher Jeffrey (MIT License)
* Binding to the pseudo terminals.
*/
var net = require('net');
var pty = require('../build/default/pty.node');
/**
* Terminal
*/
// Example:
// var term = new Terminal('bash', [], {
// name: 'xterm-color',
// cols: 80,
// rows: 30,
// cwd: process.env.HOME,
// env: process.env
// });
function Terminal(file, args, opt) {
if (!(this instanceof Terminal)) {
return new Terminal(file, args, opt);
}
var self = this
, env
, cwd
, name
, cols
, rows
, term;
// backward compatibility
if (typeof args === 'string') {
opt = {
name: arguments[1],
cols: arguments[2],
rows: arguments[3],
cwd: process.env.HOME
};
args = [];
}
// arguments
args = args || [];
file = file || 'sh';
opt = opt || {};
cols = opt.cols || 80;
rows = opt.rows || 30;
env = clone(opt.env || process.env);
cwd = opt.cwd || process.cwd();
name = opt.name || env.TERM || 'xterm';
env.TERM = name;
env = environ(env);
// fork
term = pty.fork(file, args, env, cwd, cols, rows);
this.socket = new net.Socket(term.fd);
this.socket.setEncoding('utf8');
this.socket.resume();
// setup
this.socket.on('error', function(err) {
// close
self._close();
// EIO, happens when someone closes our child
// process: the only process in the terminal.
// node < 0.6.14: errno 5
// node >= 0.6.14: read EIO
if (err.code) {
if (~err.code.indexOf('errno 5')
|| ~err.code.indexOf('EIO')) return;
}
// throw anything else
if (self.listeners('error').length < 2) {
throw err;
}
});
this.pid = term.pid;
this.fd = term.fd;
this.pty = term.pty;
this.file = file;
this.name = name;
this.cols = cols;
this.rows = rows;
this.readable = true;
this.writable = true;
Terminal.total++;
this.socket.on('close', function() {
Terminal.total--;
self._close();
self.emit('exit', null);
});
env = null;
}
Terminal.fork =
Terminal.spawn =
Terminal.createTerminal = function(file, args, opt) {
return new Terminal(file, args, opt);
};
/**
* openpty
*/
Terminal.open = function(opt) {
var self = Object.create(Terminal.prototype)
, opt = opt || {};
if (arguments.length > 1) {
opt = {
cols: arguments[1],
rows: arguments[2]
};
}
var cols = opt.cols || 80
, rows = opt.rows || 30
, term;
// open
term = pty.open(cols, rows);
self.master = new net.Socket(term.master);
self.master.setEncoding('utf8');
self.master.resume();
self.slave = new net.Socket(term.slave);
self.slave.setEncoding('utf8');
self.slave.resume();
self.socket = self.master;
self.pid = null;
self.fd = term.master;
self.pty = term.pty;
self.file = process.argv[0] || 'node';
self.name = process.env.TERM || '';
self.cols = cols;
self.rows = rows;
self.readable = true;
self.writable = true;
self.socket.on('error', function(err) {
self._close();
if (self.listeners('error').length < 2) {
throw err;
}
});
Terminal.total++;
self.socket.on('close', function() {
Terminal.total--;
self._close();
});
return self;
};
/**
* Total
*/
// Keep track of the total
// number of terminals for
// the process.
Terminal.total = 0;
/**
* Events
*/
// Don't inherit from net.Socket in
// order to avoid collisions.
Terminal.prototype.write = function(data) {
return this.socket.write(data);
};
Terminal.prototype.end = function(data) {
return this.socket.end(data);
};
Terminal.prototype.pipe = function(dest, options) {
return this.socket.pipe(dest, options);
};
Terminal.prototype.pause = function() {
this.socket.pause();
};
Terminal.prototype.resume = function() {
this.socket.resume();
};
Terminal.prototype.setEncoding = function(enc) {
if (this.socket._decoder) {
delete this.socket._decoder;
}
if (enc) {
this.socket.setEncoding(enc);
}
};
Terminal.prototype.addListener =
Terminal.prototype.on = function(type, func) {
this.socket.on(type, func);
return this;
};
Terminal.prototype.emit = function() {
return this.socket.emit.apply(this.socket, arguments);
};
Terminal.prototype.listeners = function(type) {
return this.socket.listeners(type);
};
Terminal.prototype.removeListener = function(type, func) {
this.socket.removeListener(type, func);
return this;
};
Terminal.prototype.removeAllListeners = function(type) {
this.socket.removeAllListeners(type);
return this;
};
Terminal.prototype.once = function(type, func) {
this.socket.once(type, func);
return this;
};
Terminal.prototype.__defineGetter__('stdin', function() {
return this;
});
Terminal.prototype.__defineGetter__('stdout', function() {
return this;
});
Terminal.prototype.__defineGetter__('stderr', function() {
throw new Error('No stderr.');
});
/**
* TTY
*/
Terminal.prototype.resize = function(cols, rows) {
cols = cols || 80;
rows = rows || 30;
this.cols = cols;
this.rows = rows;
pty.resize(this.fd, cols, rows);
};
Terminal.prototype.destroy = function() {
var self = this;
// close
this._close();
// Need to close the read stream so
// node stops reading a dead file descriptor.
// Then we can safely SIGTERM the shell.
this.socket.once('close', function() {
self.kill('SIGTERM');
});
this.socket.destroy();
};
Terminal.prototype.kill = function(sig) {
try {
process.kill(this.pid, sig || 'SIGTERM');
} catch(e) {
;
}
};
Terminal.prototype.__defineGetter__('process', function() {
return pty.process(this.fd, this.pty) || this.file;
});
Terminal.prototype._close = function() {
this.socket.writable = false;
this.socket.readable = false;
this.write = function() {};
this.end = function() {};
this.writable = false;
this.readable = false;
};
/**
* Helpers
*/
function clone(a) {
var keys = Object.keys(a || {})
, l = keys.length
, i = 0
, b = {};
for (; i < l; i++) {
b[keys[i]] = a[keys[i]];
}
return b;
}
function environ(env) {
var keys = Object.keys(env || {})
, l = keys.length
, i = 0
, pairs = [];
for (; i < l; i++) {
pairs.push(keys[i] + '=' + env[keys[i]]);
}
return pairs;
}
/**
* Expose
*/
exports = Terminal;
exports.Terminal = Terminal;
exports.native = pty;
module.exports = exports;