node-pty 0.11.0-beta9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/binding.gyp +68 -29
- package/lib/conpty_console_list_agent.js.map +1 -1
- package/lib/terminal.js +10 -4
- package/lib/terminal.js.map +1 -1
- package/lib/terminal.test.js +3 -0
- package/lib/terminal.test.js.map +1 -1
- package/lib/unixTerminal.js +30 -4
- package/lib/unixTerminal.js.map +1 -1
- package/lib/unixTerminal.test.js +147 -18
- package/lib/unixTerminal.test.js.map +1 -1
- package/lib/windowsPtyAgent.js +8 -0
- package/lib/windowsPtyAgent.js.map +1 -1
- package/lib/windowsTerminal.js +21 -3
- package/lib/windowsTerminal.js.map +1 -1
- package/lib/windowsTerminal.test.js +4 -5
- package/lib/windowsTerminal.test.js.map +1 -1
- package/lib/worker/conoutSocketWorker.js +3 -0
- package/lib/worker/conoutSocketWorker.js.map +1 -1
- package/package.json +3 -4
- package/scripts/post-install.js +1 -0
- package/src/conpty_console_list_agent.ts +1 -1
- package/src/interfaces.ts +8 -2
- package/src/native.d.ts +3 -1
- package/src/terminal.test.ts +3 -0
- package/src/terminal.ts +21 -20
- package/src/tsconfig.json +1 -3
- package/src/unix/pty.cc +297 -205
- package/src/unix/spawn-helper.cc +23 -0
- package/src/unixTerminal.test.ts +117 -25
- package/src/unixTerminal.ts +31 -11
- package/src/win/conpty.cc +65 -22
- package/src/win/path_util.cc +1 -1
- package/src/windowsConoutConnection.ts +1 -1
- package/src/windowsPtyAgent.ts +11 -5
- package/src/windowsTerminal.test.ts +5 -6
- package/src/windowsTerminal.ts +23 -4
- package/src/worker/conoutSocketWorker.ts +4 -0
- package/typings/node-pty.d.ts +11 -20
- package/deps/winpty/.gitignore +0 -15
- package/deps/winpty/misc/.gitignore +0 -2
- package/scripts/install.js +0 -18
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#include <errno.h>
|
|
2
|
+
#include <fcntl.h>
|
|
3
|
+
#include <string.h>
|
|
4
|
+
#include <unistd.h>
|
|
5
|
+
|
|
6
|
+
int main (int argc, char** argv) {
|
|
7
|
+
char *slave_path = ttyname(STDIN_FILENO);
|
|
8
|
+
// open implicit attaches a process to a terminal device if:
|
|
9
|
+
// - process has no controlling terminal yet
|
|
10
|
+
// - O_NOCTTY is not set
|
|
11
|
+
close(open(slave_path, O_RDWR));
|
|
12
|
+
|
|
13
|
+
char *cwd = argv[1];
|
|
14
|
+
char *file = argv[2];
|
|
15
|
+
argv = &argv[2];
|
|
16
|
+
|
|
17
|
+
if (strlen(cwd) && chdir(cwd) == -1) {
|
|
18
|
+
_exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
execvp(file, argv);
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
package/src/unixTerminal.test.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { UnixTerminal } from './unixTerminal';
|
|
|
7
7
|
import * as assert from 'assert';
|
|
8
8
|
import * as cp from 'child_process';
|
|
9
9
|
import * as path from 'path';
|
|
10
|
+
import * as tty from 'tty';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import { constants } from 'os';
|
|
10
13
|
import { pollUntil } from './testUtils.test';
|
|
11
14
|
|
|
12
15
|
const FIXTURES_PATH = path.normalize(path.join(__dirname, '..', 'fixtures', 'utf8-character.txt'));
|
|
@@ -16,7 +19,7 @@ if (process.platform !== 'win32') {
|
|
|
16
19
|
describe('Constructor', () => {
|
|
17
20
|
it('should set a valid pts name', () => {
|
|
18
21
|
const term = new UnixTerminal('/bin/bash', [], {});
|
|
19
|
-
let regExp: RegExp;
|
|
22
|
+
let regExp: RegExp | undefined;
|
|
20
23
|
if (process.platform === 'linux') {
|
|
21
24
|
// https://linux.die.net/man/4/pts
|
|
22
25
|
regExp = /^\/dev\/pts\/\d+$/;
|
|
@@ -26,8 +29,9 @@ if (process.platform !== 'win32') {
|
|
|
26
29
|
regExp = /^\/dev\/tty[p-sP-S][a-z0-9]+$/;
|
|
27
30
|
}
|
|
28
31
|
if (regExp) {
|
|
29
|
-
assert.ok(regExp.test(
|
|
32
|
+
assert.ok(regExp.test(term.ptsName), '"' + term.ptsName + '" should match ' + regExp.toString());
|
|
30
33
|
}
|
|
34
|
+
assert.ok(tty.isatty(term.fd));
|
|
31
35
|
});
|
|
32
36
|
});
|
|
33
37
|
|
|
@@ -35,8 +39,8 @@ if (process.platform !== 'win32') {
|
|
|
35
39
|
it('should default to utf8', (done) => {
|
|
36
40
|
const term = new UnixTerminal('/bin/bash', [ '-c', `cat "${FIXTURES_PATH}"` ]);
|
|
37
41
|
term.on('data', (data) => {
|
|
38
|
-
assert.
|
|
39
|
-
assert.
|
|
42
|
+
assert.strictEqual(typeof data, 'string');
|
|
43
|
+
assert.strictEqual(data, '\u00E6');
|
|
40
44
|
done();
|
|
41
45
|
});
|
|
42
46
|
});
|
|
@@ -45,25 +49,25 @@ if (process.platform !== 'win32') {
|
|
|
45
49
|
encoding: null
|
|
46
50
|
});
|
|
47
51
|
term.on('data', (data) => {
|
|
48
|
-
assert.
|
|
52
|
+
assert.strictEqual(typeof data, 'object');
|
|
49
53
|
assert.ok(data instanceof Buffer);
|
|
50
|
-
assert.
|
|
51
|
-
assert.
|
|
54
|
+
assert.strictEqual(0xC3, data[0]);
|
|
55
|
+
assert.strictEqual(0xA6, data[1]);
|
|
52
56
|
done();
|
|
53
57
|
});
|
|
54
58
|
});
|
|
55
59
|
it('should support other encodings', (done) => {
|
|
56
60
|
const text = 'test æ!';
|
|
57
|
-
const term = new UnixTerminal(
|
|
61
|
+
const term = new UnixTerminal(undefined, ['-c', 'echo "' + text + '"'], {
|
|
58
62
|
encoding: 'base64'
|
|
59
63
|
});
|
|
60
64
|
let buffer = '';
|
|
61
|
-
term.
|
|
62
|
-
assert.
|
|
65
|
+
term.onData((data) => {
|
|
66
|
+
assert.strictEqual(typeof data, 'string');
|
|
63
67
|
buffer += data;
|
|
64
68
|
});
|
|
65
|
-
term.
|
|
66
|
-
assert.
|
|
69
|
+
term.onExit(() => {
|
|
70
|
+
assert.strictEqual(Buffer.alloc(8, buffer, 'base64').toString().replace('\r', '').replace('\n', ''), text);
|
|
67
71
|
done();
|
|
68
72
|
});
|
|
69
73
|
});
|
|
@@ -74,8 +78,8 @@ if (process.platform !== 'win32') {
|
|
|
74
78
|
|
|
75
79
|
afterEach(() => {
|
|
76
80
|
if (term) {
|
|
77
|
-
term.slave
|
|
78
|
-
term.master
|
|
81
|
+
term.slave!.destroy();
|
|
82
|
+
term.master!.destroy();
|
|
79
83
|
}
|
|
80
84
|
});
|
|
81
85
|
|
|
@@ -83,12 +87,12 @@ if (process.platform !== 'win32') {
|
|
|
83
87
|
term = UnixTerminal.open({});
|
|
84
88
|
|
|
85
89
|
let slavebuf = '';
|
|
86
|
-
term.slave
|
|
90
|
+
term.slave!.on('data', (data) => {
|
|
87
91
|
slavebuf += data;
|
|
88
92
|
});
|
|
89
93
|
|
|
90
94
|
let masterbuf = '';
|
|
91
|
-
term.master
|
|
95
|
+
term.master!.on('data', (data) => {
|
|
92
96
|
masterbuf += data;
|
|
93
97
|
});
|
|
94
98
|
|
|
@@ -100,8 +104,19 @@ if (process.platform !== 'win32') {
|
|
|
100
104
|
return false;
|
|
101
105
|
}, 200, 10);
|
|
102
106
|
|
|
103
|
-
term.slave
|
|
104
|
-
term.master
|
|
107
|
+
term.slave!.write('slave\n');
|
|
108
|
+
term.master!.write('master\n');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('close', () => {
|
|
112
|
+
const term = new UnixTerminal('node');
|
|
113
|
+
it('should exit when terminal is destroyed programmatically', (done) => {
|
|
114
|
+
term.on('exit', (code, signal) => {
|
|
115
|
+
assert.strictEqual(code, 0);
|
|
116
|
+
assert.strictEqual(signal, constants.signals.SIGHUP);
|
|
117
|
+
done();
|
|
118
|
+
});
|
|
119
|
+
term.destroy();
|
|
105
120
|
});
|
|
106
121
|
});
|
|
107
122
|
describe('signals in parent and child', () => {
|
|
@@ -140,8 +155,8 @@ if (process.platform !== 'win32') {
|
|
|
140
155
|
});
|
|
141
156
|
p.on('close', () => {
|
|
142
157
|
// handlers in parent and child should have been triggered
|
|
143
|
-
assert.
|
|
144
|
-
assert.
|
|
158
|
+
assert.strictEqual(buffer.indexOf('SIGINT in child') !== -1, true);
|
|
159
|
+
assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);
|
|
145
160
|
done();
|
|
146
161
|
});
|
|
147
162
|
});
|
|
@@ -181,8 +196,8 @@ if (process.platform !== 'win32') {
|
|
|
181
196
|
});
|
|
182
197
|
p.on('close', () => {
|
|
183
198
|
// handlers in parent and child should have been triggered
|
|
184
|
-
assert.
|
|
185
|
-
assert.
|
|
199
|
+
assert.strictEqual(buffer.indexOf('should not be printed') !== -1, false);
|
|
200
|
+
assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);
|
|
186
201
|
done();
|
|
187
202
|
});
|
|
188
203
|
});
|
|
@@ -201,7 +216,7 @@ if (process.platform !== 'win32') {
|
|
|
201
216
|
});
|
|
202
217
|
term.on('exit', () => {
|
|
203
218
|
// no timeout in buffer
|
|
204
|
-
assert.
|
|
219
|
+
assert.strictEqual(buffer, '');
|
|
205
220
|
done();
|
|
206
221
|
});
|
|
207
222
|
});
|
|
@@ -233,11 +248,88 @@ if (process.platform !== 'win32') {
|
|
|
233
248
|
});
|
|
234
249
|
term.on('exit', () => {
|
|
235
250
|
// should have called both handlers and only once
|
|
236
|
-
assert.
|
|
237
|
-
assert.
|
|
251
|
+
assert.strictEqual(pHandlerCalled, 1);
|
|
252
|
+
assert.strictEqual(buffer, 'SIGUSR1 in child\r\n');
|
|
238
253
|
done();
|
|
239
254
|
});
|
|
240
255
|
});
|
|
241
256
|
});
|
|
257
|
+
describe('spawn', () => {
|
|
258
|
+
if (process.platform === 'darwin') {
|
|
259
|
+
it('should return the name of the process', (done) => {
|
|
260
|
+
const term = new UnixTerminal('/bin/echo');
|
|
261
|
+
assert.strictEqual(term.process, '/bin/echo');
|
|
262
|
+
term.on('exit', () => done());
|
|
263
|
+
term.destroy();
|
|
264
|
+
});
|
|
265
|
+
it('should close on exec', (done) => {
|
|
266
|
+
const data = `
|
|
267
|
+
var pty = require('./lib/index');
|
|
268
|
+
var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log("hello from terminal"), 300);']);
|
|
269
|
+
ptyProcess.on('data', function (data) {
|
|
270
|
+
console.log(data);
|
|
271
|
+
});
|
|
272
|
+
setTimeout(() => null, 500);
|
|
273
|
+
console.log('ready', ptyProcess.pid);
|
|
274
|
+
`;
|
|
275
|
+
const buffer: string[] = [];
|
|
276
|
+
const readFd = fs.openSync(FIXTURES_PATH, 'r');
|
|
277
|
+
const p = cp.spawn('node', ['-e', data], {
|
|
278
|
+
stdio: ['ignore', 'pipe', 'pipe', readFd]
|
|
279
|
+
});
|
|
280
|
+
let sub = '';
|
|
281
|
+
p.stdout!.on('data', (data) => {
|
|
282
|
+
if (!data.toString().indexOf('ready')) {
|
|
283
|
+
sub = data.toString().split(' ')[1].slice(0, -1);
|
|
284
|
+
try {
|
|
285
|
+
fs.statSync(`/proc/${sub}/fd/${readFd}`);
|
|
286
|
+
done('not reachable');
|
|
287
|
+
} catch (error) {
|
|
288
|
+
assert.notStrictEqual(error.message.indexOf('ENOENT'), -1);
|
|
289
|
+
}
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
process.kill(parseInt(sub), 'SIGINT'); // SIGINT to child
|
|
292
|
+
p.kill('SIGINT'); // SIGINT to parent
|
|
293
|
+
}, 200);
|
|
294
|
+
} else {
|
|
295
|
+
buffer.push(data.toString().replace(/^\s+|\s+$/g, ''));
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
p.on('close', () => {
|
|
299
|
+
done();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
it('should handle exec() errors', (done) => {
|
|
304
|
+
const term = new UnixTerminal('/bin/bogus.exe', []);
|
|
305
|
+
term.on('exit', (code, signal) => {
|
|
306
|
+
assert.strictEqual(code, 1);
|
|
307
|
+
done();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
it('should handle chdir() errors', (done) => {
|
|
311
|
+
const term = new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' });
|
|
312
|
+
term.on('exit', (code, signal) => {
|
|
313
|
+
assert.strictEqual(code, 1);
|
|
314
|
+
done();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
it('should not leak child process', (done) => {
|
|
318
|
+
const count = cp.execSync('ps -ax | grep node | wc -l');
|
|
319
|
+
const term = new UnixTerminal('node', [ '-e', `
|
|
320
|
+
console.log('ready');
|
|
321
|
+
setTimeout(()=>console.log('timeout'), 200);`
|
|
322
|
+
]);
|
|
323
|
+
term.on('data', async (data) => {
|
|
324
|
+
if (data === 'ready\r\n') {
|
|
325
|
+
process.kill(term.pid, 'SIGINT');
|
|
326
|
+
await setTimeout(() => null, 1000);
|
|
327
|
+
const newCount = cp.execSync('ps -ax | grep node | wc -l');
|
|
328
|
+
assert.strictEqual(count.toString(), newCount.toString());
|
|
329
|
+
done();
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
242
334
|
});
|
|
243
335
|
}
|
package/src/unixTerminal.ts
CHANGED
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
5
5
|
*/
|
|
6
6
|
import * as net from 'net';
|
|
7
|
+
import * as path from 'path';
|
|
7
8
|
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
|
|
8
9
|
import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';
|
|
9
10
|
import { ArgvOrCommandLine } from './types';
|
|
10
11
|
import { assign } from './utils';
|
|
11
12
|
|
|
12
13
|
let pty: IUnixNative;
|
|
14
|
+
let helperPath: string;
|
|
13
15
|
try {
|
|
14
16
|
pty = require('../build/Release/pty.node');
|
|
17
|
+
helperPath = '../build/Release/spawn-helper';
|
|
15
18
|
} catch (outerError) {
|
|
16
19
|
try {
|
|
17
20
|
pty = require('../build/Debug/pty.node');
|
|
21
|
+
helperPath = '../build/Debug/spawn-helper';
|
|
18
22
|
} catch (innerError) {
|
|
19
23
|
console.error('innerError', innerError);
|
|
20
24
|
// Re-throw the exception from the Release require if the Debug require fails as well
|
|
@@ -22,6 +26,10 @@ try {
|
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
helperPath = path.resolve(__dirname, helperPath);
|
|
30
|
+
helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
|
31
|
+
helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
|
32
|
+
|
|
25
33
|
const DEFAULT_FILE = 'sh';
|
|
26
34
|
const DEFAULT_NAME = 'xterm';
|
|
27
35
|
const DESTROY_SOCKET_TIMEOUT_MS = 200;
|
|
@@ -36,13 +44,13 @@ export class UnixTerminal extends Terminal {
|
|
|
36
44
|
protected _readable: boolean;
|
|
37
45
|
protected _writable: boolean;
|
|
38
46
|
|
|
39
|
-
private _boundClose: boolean;
|
|
40
|
-
private _emittedClose: boolean;
|
|
41
|
-
private _master: net.Socket;
|
|
42
|
-
private _slave: net.Socket;
|
|
47
|
+
private _boundClose: boolean = false;
|
|
48
|
+
private _emittedClose: boolean = false;
|
|
49
|
+
private _master: net.Socket | undefined;
|
|
50
|
+
private _slave: net.Socket | undefined;
|
|
43
51
|
|
|
44
|
-
public get master(): net.Socket { return this._master; }
|
|
45
|
-
public get slave(): net.Socket { return this._slave; }
|
|
52
|
+
public get master(): net.Socket | undefined { return this._master; }
|
|
53
|
+
public get slave(): net.Socket | undefined { return this._slave; }
|
|
46
54
|
|
|
47
55
|
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) {
|
|
48
56
|
super(opt);
|
|
@@ -59,8 +67,8 @@ export class UnixTerminal extends Terminal {
|
|
|
59
67
|
|
|
60
68
|
this._cols = opt.cols || DEFAULT_COLS;
|
|
61
69
|
this._rows = opt.rows || DEFAULT_ROWS;
|
|
62
|
-
const uid = opt.uid
|
|
63
|
-
const gid = opt.gid
|
|
70
|
+
const uid = opt.uid ?? -1;
|
|
71
|
+
const gid = opt.gid ?? -1;
|
|
64
72
|
const env: IProcessEnv = assign({}, opt.env);
|
|
65
73
|
|
|
66
74
|
if (opt.env === process.env) {
|
|
@@ -86,7 +94,7 @@ export class UnixTerminal extends Terminal {
|
|
|
86
94
|
// From macOS High Sierra 10.13.2 sometimes the socket never gets
|
|
87
95
|
// closed. A timeout is applied here to avoid the terminal never being
|
|
88
96
|
// destroyed when this occurs.
|
|
89
|
-
let timeout = setTimeout(() => {
|
|
97
|
+
let timeout: NodeJS.Timeout | null = setTimeout(() => {
|
|
90
98
|
timeout = null;
|
|
91
99
|
// Destroying the socket now will cause the close event to fire
|
|
92
100
|
this._socket.destroy();
|
|
@@ -103,7 +111,7 @@ export class UnixTerminal extends Terminal {
|
|
|
103
111
|
};
|
|
104
112
|
|
|
105
113
|
// fork
|
|
106
|
-
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), onexit);
|
|
114
|
+
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);
|
|
107
115
|
|
|
108
116
|
this._socket = new PipeSocket(term.fd);
|
|
109
117
|
if (encoding !== null) {
|
|
@@ -169,6 +177,10 @@ export class UnixTerminal extends Terminal {
|
|
|
169
177
|
this._socket.write(data);
|
|
170
178
|
}
|
|
171
179
|
|
|
180
|
+
/* Accessors */
|
|
181
|
+
get fd(): number { return this._fd; }
|
|
182
|
+
get ptsName(): string { return this._pty; }
|
|
183
|
+
|
|
172
184
|
/**
|
|
173
185
|
* openpty
|
|
174
186
|
*/
|
|
@@ -204,7 +216,7 @@ export class UnixTerminal extends Terminal {
|
|
|
204
216
|
self._slave.resume();
|
|
205
217
|
|
|
206
218
|
self._socket = self._master;
|
|
207
|
-
self._pid =
|
|
219
|
+
self._pid = -1;
|
|
208
220
|
self._fd = term.master;
|
|
209
221
|
self._pty = term.pty;
|
|
210
222
|
|
|
@@ -250,6 +262,10 @@ export class UnixTerminal extends Terminal {
|
|
|
250
262
|
* Gets the name of the process.
|
|
251
263
|
*/
|
|
252
264
|
public get process(): string {
|
|
265
|
+
if (process.platform === 'darwin') {
|
|
266
|
+
return pty.process(this._pid) || this._file;
|
|
267
|
+
}
|
|
268
|
+
|
|
253
269
|
return pty.process(this._fd, this._pty) || this._file;
|
|
254
270
|
}
|
|
255
271
|
|
|
@@ -266,6 +282,10 @@ export class UnixTerminal extends Terminal {
|
|
|
266
282
|
this._rows = rows;
|
|
267
283
|
}
|
|
268
284
|
|
|
285
|
+
public clear(): void {
|
|
286
|
+
|
|
287
|
+
}
|
|
288
|
+
|
|
269
289
|
private _sanitizeEnv(env: IProcessEnv): void {
|
|
270
290
|
// Make sure we didn't start our server from inside tmux.
|
|
271
291
|
delete env['TMUX'];
|
package/src/win/conpty.cc
CHANGED
|
@@ -34,6 +34,7 @@ extern "C" void init(v8::Local<v8::Object>);
|
|
|
34
34
|
typedef VOID* HPCON;
|
|
35
35
|
typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE hOut, DWORD dwFlags, HPCON* phpcon);
|
|
36
36
|
typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
|
|
37
|
+
typedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);
|
|
37
38
|
typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
|
|
38
39
|
|
|
39
40
|
#endif
|
|
@@ -289,6 +290,13 @@ static NAN_METHOD(PtyConnect) {
|
|
|
289
290
|
const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
|
|
290
291
|
const v8::Local<v8::Function> exitCallback = v8::Local<v8::Function>::Cast(info[4]);
|
|
291
292
|
|
|
293
|
+
// Fetch pty handle from ID and start process
|
|
294
|
+
pty_baton* handle = get_pty_baton(id);
|
|
295
|
+
if (!handle) {
|
|
296
|
+
Nan::ThrowError("Invalid pty handle");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
292
300
|
// Prepare command line
|
|
293
301
|
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
|
|
294
302
|
HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());
|
|
@@ -311,11 +319,8 @@ static NAN_METHOD(PtyConnect) {
|
|
|
311
319
|
auto envV = vectorFromString(env);
|
|
312
320
|
LPWSTR envArg = envV.empty() ? nullptr : envV.data();
|
|
313
321
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
BOOL success = ConnectNamedPipe(handle->hIn, nullptr);
|
|
318
|
-
success = ConnectNamedPipe(handle->hOut, nullptr);
|
|
322
|
+
ConnectNamedPipe(handle->hIn, nullptr);
|
|
323
|
+
ConnectNamedPipe(handle->hOut, nullptr);
|
|
319
324
|
|
|
320
325
|
// Attach the pseudoconsole to the client application we're creating
|
|
321
326
|
STARTUPINFOEXW siEx{0};
|
|
@@ -396,15 +401,46 @@ static NAN_METHOD(PtyResize) {
|
|
|
396
401
|
|
|
397
402
|
const pty_baton* handle = get_pty_baton(id);
|
|
398
403
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
404
|
+
if (handle != nullptr) {
|
|
405
|
+
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
406
|
+
bool fLoadedDll = hLibrary != nullptr;
|
|
407
|
+
if (fLoadedDll)
|
|
408
|
+
{
|
|
409
|
+
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
|
|
410
|
+
if (pfnResizePseudoConsole)
|
|
411
|
+
{
|
|
412
|
+
COORD size = {cols, rows};
|
|
413
|
+
pfnResizePseudoConsole(handle->hpc, size);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return info.GetReturnValue().SetUndefined();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
static NAN_METHOD(PtyClear) {
|
|
422
|
+
Nan::HandleScope scope;
|
|
423
|
+
|
|
424
|
+
if (info.Length() != 1 ||
|
|
425
|
+
!info[0]->IsNumber()) {
|
|
426
|
+
Nan::ThrowError("Usage: pty.clear(id)");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
431
|
+
|
|
432
|
+
const pty_baton* handle = get_pty_baton(id);
|
|
433
|
+
|
|
434
|
+
if (handle != nullptr) {
|
|
435
|
+
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
436
|
+
bool fLoadedDll = hLibrary != nullptr;
|
|
437
|
+
if (fLoadedDll)
|
|
405
438
|
{
|
|
406
|
-
|
|
407
|
-
|
|
439
|
+
PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClearPseudoConsole");
|
|
440
|
+
if (pfnClearPseudoConsole)
|
|
441
|
+
{
|
|
442
|
+
pfnClearPseudoConsole(handle->hpc);
|
|
443
|
+
}
|
|
408
444
|
}
|
|
409
445
|
}
|
|
410
446
|
|
|
@@ -424,18 +460,24 @@ static NAN_METHOD(PtyKill) {
|
|
|
424
460
|
|
|
425
461
|
const pty_baton* handle = get_pty_baton(id);
|
|
426
462
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
|
|
432
|
-
if (pfnClosePseudoConsole)
|
|
463
|
+
if (handle != nullptr) {
|
|
464
|
+
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
465
|
+
bool fLoadedDll = hLibrary != nullptr;
|
|
466
|
+
if (fLoadedDll)
|
|
433
467
|
{
|
|
434
|
-
pfnClosePseudoConsole(
|
|
468
|
+
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
|
|
469
|
+
if (pfnClosePseudoConsole)
|
|
470
|
+
{
|
|
471
|
+
pfnClosePseudoConsole(handle->hpc);
|
|
472
|
+
}
|
|
435
473
|
}
|
|
436
|
-
}
|
|
437
474
|
|
|
438
|
-
|
|
475
|
+
DisconnectNamedPipe(handle->hIn);
|
|
476
|
+
DisconnectNamedPipe(handle->hOut);
|
|
477
|
+
CloseHandle(handle->hIn);
|
|
478
|
+
CloseHandle(handle->hOut);
|
|
479
|
+
CloseHandle(handle->hShell);
|
|
480
|
+
}
|
|
439
481
|
|
|
440
482
|
return info.GetReturnValue().SetUndefined();
|
|
441
483
|
}
|
|
@@ -449,6 +491,7 @@ extern "C" void init(v8::Local<v8::Object> target) {
|
|
|
449
491
|
Nan::SetMethod(target, "startProcess", PtyStartProcess);
|
|
450
492
|
Nan::SetMethod(target, "connect", PtyConnect);
|
|
451
493
|
Nan::SetMethod(target, "resize", PtyResize);
|
|
494
|
+
Nan::SetMethod(target, "clear", PtyClear);
|
|
452
495
|
Nan::SetMethod(target, "kill", PtyKill);
|
|
453
496
|
};
|
|
454
497
|
|
package/src/win/path_util.cc
CHANGED
|
@@ -52,7 +52,7 @@ std::wstring get_shell_path(std::wstring filename) {
|
|
|
52
52
|
|
|
53
53
|
const wchar_t *filename_ = filename.c_str();
|
|
54
54
|
|
|
55
|
-
for (
|
|
55
|
+
for (size_t i = 0; i < paths.size(); ++i) {
|
|
56
56
|
std::wstring path = paths[i];
|
|
57
57
|
wchar_t searchPath[MAX_PATH];
|
|
58
58
|
::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);
|
|
@@ -30,7 +30,7 @@ const FLUSH_DATA_INTERVAL = 1000;
|
|
|
30
30
|
*/
|
|
31
31
|
export class ConoutConnection implements IDisposable {
|
|
32
32
|
private _worker: Worker;
|
|
33
|
-
private _drainTimeout: NodeJS.Timeout;
|
|
33
|
+
private _drainTimeout: NodeJS.Timeout | undefined;
|
|
34
34
|
private _isDisposed: boolean = false;
|
|
35
35
|
|
|
36
36
|
private _onReady = new EventEmitter2<void>();
|
package/src/windowsPtyAgent.ts
CHANGED
|
@@ -29,10 +29,10 @@ const FLUSH_DATA_INTERVAL = 1000;
|
|
|
29
29
|
export class WindowsPtyAgent {
|
|
30
30
|
private _inSocket: Socket;
|
|
31
31
|
private _outSocket: Socket;
|
|
32
|
-
private _pid: number;
|
|
33
|
-
private _innerPid: number;
|
|
34
|
-
private _innerPidHandle: number;
|
|
35
|
-
private _closeTimeout: NodeJS.Timer;
|
|
32
|
+
private _pid: number = 0;
|
|
33
|
+
private _innerPid: number = 0;
|
|
34
|
+
private _innerPidHandle: number = 0;
|
|
35
|
+
private _closeTimeout: NodeJS.Timer | undefined;
|
|
36
36
|
private _exitCode: number | undefined;
|
|
37
37
|
private _conoutSocketWorker: ConoutConnection;
|
|
38
38
|
|
|
@@ -152,6 +152,12 @@ export class WindowsPtyAgent {
|
|
|
152
152
|
this._ptyNative.resize(this._pid, cols, rows);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
public clear(): void {
|
|
156
|
+
if (this._useConpty) {
|
|
157
|
+
(this._ptyNative as IConptyNative).clear(this._pty);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
155
161
|
public kill(): void {
|
|
156
162
|
this._inSocket.readable = false;
|
|
157
163
|
this._outSocket.readable = false;
|
|
@@ -202,7 +208,7 @@ export class WindowsPtyAgent {
|
|
|
202
208
|
});
|
|
203
209
|
}
|
|
204
210
|
|
|
205
|
-
public get exitCode(): number {
|
|
211
|
+
public get exitCode(): number | undefined {
|
|
206
212
|
if (this._useConpty) {
|
|
207
213
|
return this._exitCode;
|
|
208
214
|
}
|
|
@@ -65,7 +65,7 @@ function pollForProcessTreeSize(pid: number, size: number, intervalMs: number =
|
|
|
65
65
|
})[0]);
|
|
66
66
|
const list: IWindowsProcessTreeResult[] = [];
|
|
67
67
|
while (openList.length) {
|
|
68
|
-
const current = openList.shift()
|
|
68
|
+
const current = openList.shift()!;
|
|
69
69
|
ps.filter(p => p.ppid === current.pid).map(p => {
|
|
70
70
|
return { name: p.name, pid: p.pid };
|
|
71
71
|
}).forEach(p => openList.push(p));
|
|
@@ -81,7 +81,6 @@ function pollForProcessTreeSize(pid: number, size: number, intervalMs: number =
|
|
|
81
81
|
if (tries * intervalMs >= timeoutMs) {
|
|
82
82
|
clearInterval(interval);
|
|
83
83
|
assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);
|
|
84
|
-
resolve();
|
|
85
84
|
}
|
|
86
85
|
});
|
|
87
86
|
}, intervalMs);
|
|
@@ -105,10 +104,10 @@ if (process.platform === 'win32') {
|
|
|
105
104
|
term.write('notepad.exe\r');
|
|
106
105
|
term.write('node.exe\r');
|
|
107
106
|
pollForProcessTreeSize(term.pid, 4, 500, 5000).then(list => {
|
|
108
|
-
assert.
|
|
109
|
-
assert.
|
|
110
|
-
assert.
|
|
111
|
-
assert.
|
|
107
|
+
assert.strictEqual(list[0].name.toLowerCase(), 'cmd.exe');
|
|
108
|
+
assert.strictEqual(list[1].name.toLowerCase(), 'powershell.exe');
|
|
109
|
+
assert.strictEqual(list[2].name.toLowerCase(), 'notepad.exe');
|
|
110
|
+
assert.strictEqual(list[3].name.toLowerCase(), 'node.exe');
|
|
112
111
|
term.kill();
|
|
113
112
|
const desiredState: IProcessState = {};
|
|
114
113
|
desiredState[list[0].pid] = false;
|
package/src/windowsTerminal.ts
CHANGED
|
@@ -147,21 +147,27 @@ export class WindowsTerminal extends Terminal {
|
|
|
147
147
|
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
|
|
148
148
|
throw new Error('resizing must be done using positive cols and rows');
|
|
149
149
|
}
|
|
150
|
-
this.
|
|
150
|
+
this._deferNoArgs(() => {
|
|
151
151
|
this._agent.resize(cols, rows);
|
|
152
152
|
this._cols = cols;
|
|
153
153
|
this._rows = rows;
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
public clear(): void {
|
|
158
|
+
this._deferNoArgs(() => {
|
|
159
|
+
this._agent.clear();
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
public destroy(): void {
|
|
158
|
-
this.
|
|
164
|
+
this._deferNoArgs(() => {
|
|
159
165
|
this.kill();
|
|
160
166
|
});
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
public kill(signal?: string): void {
|
|
164
|
-
this.
|
|
170
|
+
this._deferNoArgs(() => {
|
|
165
171
|
if (signal) {
|
|
166
172
|
throw new Error('Signals not supported on windows.');
|
|
167
173
|
}
|
|
@@ -170,7 +176,20 @@ export class WindowsTerminal extends Terminal {
|
|
|
170
176
|
});
|
|
171
177
|
}
|
|
172
178
|
|
|
173
|
-
private
|
|
179
|
+
private _deferNoArgs<A>(deferredFn: () => void): void {
|
|
180
|
+
// If the terminal is ready, execute.
|
|
181
|
+
if (this._isReady) {
|
|
182
|
+
deferredFn.call(this);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Queue until terminal is ready.
|
|
187
|
+
this._deferreds.push({
|
|
188
|
+
run: () => deferredFn.call(this)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private _defer<A>(deferredFn: (arg: A) => void, arg: A): void {
|
|
174
193
|
// If the terminal is ready, execute.
|
|
175
194
|
if (this._isReady) {
|
|
176
195
|
deferredFn.call(this, arg);
|
|
@@ -14,5 +14,9 @@ conoutSocket.connect(conoutPipeName, () => {
|
|
|
14
14
|
conoutSocket.pipe(workerSocket);
|
|
15
15
|
});
|
|
16
16
|
server.listen(getWorkerPipeName(conoutPipeName));
|
|
17
|
+
|
|
18
|
+
if (!parentPort) {
|
|
19
|
+
throw new Error('worker_threads parentPort is null');
|
|
20
|
+
}
|
|
17
21
|
parentPort.postMessage(ConoutWorkerMessage.READY);
|
|
18
22
|
});
|