2016-09-21 06:27:11 -08:00
|
|
|
const {exec} = require('child_process');
|
|
|
|
|
const {EventEmitter} = require('events');
|
2016-10-02 08:54:27 -08:00
|
|
|
const {StringDecoder} = require('string_decoder');
|
2016-09-21 06:27:11 -08:00
|
|
|
|
|
|
|
|
const {app} = require('electron');
|
2016-07-01 15:50:28 -08:00
|
|
|
const defaultShell = require('default-shell');
|
2016-09-21 06:27:11 -08:00
|
|
|
|
|
|
|
|
const {getDecoratedEnv} = require('./plugins');
|
|
|
|
|
const {productName, version} = require('./package');
|
2016-08-01 23:49:25 -08:00
|
|
|
const config = require('./config');
|
2016-06-30 22:01:04 -08:00
|
|
|
|
2016-07-13 13:06:05 -08:00
|
|
|
let spawn;
|
|
|
|
|
try {
|
|
|
|
|
spawn = require('child_pty').spawn;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(
|
|
|
|
|
'A native module failed to load. Typically this means ' +
|
|
|
|
|
'you installed the modules incorrectly.\n Use `scripts/install.sh` ' +
|
|
|
|
|
'to trigger the installation.\n ' +
|
|
|
|
|
'More information: https://github.com/zeit/hyperterm/issues/72'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-08 13:26:44 -08:00
|
|
|
const TITLE_POLL_INTERVAL = 500;
|
2016-06-30 22:01:04 -08:00
|
|
|
|
2016-08-01 23:49:25 -08:00
|
|
|
const envFromConfig = config.getConfig().env || {};
|
|
|
|
|
|
2016-06-30 22:01:04 -08:00
|
|
|
module.exports = class Session extends EventEmitter {
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
constructor({rows, cols: columns, cwd, shell, shellArgs}) {
|
2016-06-30 22:01:04 -08:00
|
|
|
super();
|
2016-07-24 10:03:24 -08:00
|
|
|
const baseEnv = Object.assign({}, process.env, {
|
2016-07-26 10:26:06 -08:00
|
|
|
LANG: app.getLocale().replace('-', '_') + '.UTF-8',
|
2016-07-24 10:03:24 -08:00
|
|
|
TERM: 'xterm-256color',
|
|
|
|
|
TERM_PROGRAM: productName,
|
|
|
|
|
TERM_PROGRAM_VERSION: version
|
2016-08-01 23:49:25 -08:00
|
|
|
}, envFromConfig);
|
2016-07-24 10:03:24 -08:00
|
|
|
|
2016-10-02 08:54:27 -08:00
|
|
|
const decoder = new StringDecoder('utf8');
|
|
|
|
|
|
2016-08-13 11:30:17 -08:00
|
|
|
const defaultShellArgs = ['--login'];
|
|
|
|
|
|
|
|
|
|
this.pty = spawn(shell || defaultShell, shellArgs || defaultShellArgs, {
|
2016-07-03 12:35:45 -08:00
|
|
|
columns,
|
2016-06-30 22:01:04 -08:00
|
|
|
rows,
|
2016-07-16 14:41:13 -08:00
|
|
|
cwd,
|
2016-07-24 10:03:24 -08:00
|
|
|
env: getDecoratedEnv(baseEnv)
|
2016-06-30 22:01:04 -08:00
|
|
|
});
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
this.pty.stdout.on('data', data => {
|
2016-08-19 12:19:04 -08:00
|
|
|
if (this.ended) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-10-02 08:54:27 -08:00
|
|
|
this.emit('data', decoder.write(data));
|
2016-06-30 22:01:04 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.pty.on('exit', () => {
|
|
|
|
|
if (!this.ended) {
|
|
|
|
|
this.ended = true;
|
|
|
|
|
this.emit('exit');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-23 16:57:47 -08:00
|
|
|
this.shell = shell || defaultShell;
|
2016-06-30 22:01:04 -08:00
|
|
|
this.getTitle();
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
focus() {
|
2016-07-14 15:16:44 -08:00
|
|
|
this.subscribed = true;
|
|
|
|
|
this.getTitle();
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
blur() {
|
2016-07-14 15:16:44 -08:00
|
|
|
this.subscribed = false;
|
2016-06-30 22:01:04 -08:00
|
|
|
clearTimeout(this.titlePoll);
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
getTitle() {
|
|
|
|
|
if (process.platform === 'win32') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.fetching) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-07-14 15:16:44 -08:00
|
|
|
this.fetching = true;
|
2016-06-30 22:01:04 -08:00
|
|
|
|
|
|
|
|
let tty = this.pty.stdout.ttyname;
|
|
|
|
|
tty = tty.replace(/^\/dev\/tty/, '');
|
|
|
|
|
|
|
|
|
|
// try to exclude grep from the results
|
|
|
|
|
// by grepping for `[s]001` instead of `s001`
|
|
|
|
|
tty = `[${tty[0]}]${tty.substr(1)}`;
|
|
|
|
|
|
|
|
|
|
// TODO: limit the concurrency of how many processes we run?
|
|
|
|
|
// TODO: only tested on mac
|
2016-07-08 13:26:44 -08:00
|
|
|
exec(`ps uxac | grep ${tty} | head -n 1`, (err, out) => {
|
2016-07-14 15:16:44 -08:00
|
|
|
this.fetching = false;
|
2016-09-21 06:27:11 -08:00
|
|
|
if (this.ended) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (err) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-06-30 22:01:04 -08:00
|
|
|
let title = out.split(' ').pop();
|
|
|
|
|
if (title) {
|
|
|
|
|
title = title.replace(/^\(/, '');
|
|
|
|
|
title = title.replace(/\)?\n$/, '');
|
|
|
|
|
if (title !== this.lastTitle) {
|
|
|
|
|
this.emit('title', title);
|
|
|
|
|
this.lastTitle = title;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-14 15:16:44 -08:00
|
|
|
if (this.subscribed) {
|
|
|
|
|
this.titlePoll = setTimeout(() => this.getTitle(), TITLE_POLL_INTERVAL);
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
exit() {
|
2016-06-30 22:01:04 -08:00
|
|
|
this.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
write(data) {
|
2016-06-30 22:01:04 -08:00
|
|
|
this.pty.stdin.write(data);
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
resize({cols: columns, rows}) {
|
2016-06-30 22:01:04 -08:00
|
|
|
try {
|
2016-09-21 06:27:11 -08:00
|
|
|
this.pty.stdout.resize({columns, rows});
|
2016-06-30 22:01:04 -08:00
|
|
|
} catch (err) {
|
2016-06-30 22:25:19 -08:00
|
|
|
console.error(err.stack);
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
destroy() {
|
2016-07-03 12:35:45 -08:00
|
|
|
try {
|
|
|
|
|
this.pty.kill('SIGHUP');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('exit error', err.stack);
|
|
|
|
|
}
|
2016-06-30 22:01:04 -08:00
|
|
|
this.emit('exit');
|
|
|
|
|
this.ended = true;
|
2016-07-14 15:16:44 -08:00
|
|
|
this.blur();
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
};
|