node-pty 0.11.0-beta9 → 1.1.0-beta1
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 +70 -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 +13 -6
- 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 +98 -97
- 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 +6 -5
- 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 +85 -38
- package/src/win/conpty_console_list.cc +3 -3
- package/src/win/path_util.cc +22 -3
- package/src/win/path_util.h +1 -0
- package/src/win/winpty.cc +118 -54
- package/src/windowsConoutConnection.ts +1 -1
- package/src/windowsPtyAgent.ts +16 -11
- package/src/windowsTerminal.test.ts +100 -99
- 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
|
|
@@ -178,8 +179,8 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
const std::wstring filename(path_util::to_wstring(Nan::Utf8String(info[0])));
|
|
181
|
-
const SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
182
|
-
const SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
182
|
+
const SHORT cols = static_cast<SHORT>(info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust());
|
|
183
|
+
const SHORT rows = static_cast<SHORT>(info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust());
|
|
183
184
|
const bool debug = Nan::To<bool>(info[3]).FromJust();
|
|
184
185
|
const std::wstring pipeName(path_util::to_wstring(Nan::Utf8String(info[4])));
|
|
185
186
|
const bool inheritCursor = Nan::To<bool>(info[5]).FromJust();
|
|
@@ -193,12 +194,10 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
193
194
|
shellpath = filename;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
std::string shellpath_(shellpath.begin(), shellpath.end());
|
|
197
|
-
|
|
198
197
|
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
|
|
199
|
-
std::
|
|
200
|
-
why << "File not found: " <<
|
|
201
|
-
Nan::ThrowError(why.str().c_str());
|
|
198
|
+
std::wstringstream why;
|
|
199
|
+
why << "File not found: " << shellpath;
|
|
200
|
+
Nan::ThrowError(path_util::from_wstring(why.str().c_str()));
|
|
202
201
|
return;
|
|
203
202
|
}
|
|
204
203
|
|
|
@@ -222,14 +221,20 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
222
221
|
return;
|
|
223
222
|
}
|
|
224
223
|
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
std::string inNameStr(path_util::from_wstring(inName.c_str()));
|
|
225
|
+
if (inNameStr.empty()) {
|
|
226
|
+
Nan::ThrowError("Failed to initialize conpty conin");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
std::string outNameStr(path_util::from_wstring(outName.c_str()));
|
|
230
|
+
if (outNameStr.empty()) {
|
|
231
|
+
Nan::ThrowError("Failed to initialize conpty conout");
|
|
232
|
+
return;
|
|
232
233
|
}
|
|
234
|
+
|
|
235
|
+
Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
|
|
236
|
+
Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(inNameStr).ToLocalChecked());
|
|
237
|
+
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(outNameStr).ToLocalChecked());
|
|
233
238
|
info.GetReturnValue().Set(marshal);
|
|
234
239
|
}
|
|
235
240
|
|
|
@@ -289,6 +294,13 @@ static NAN_METHOD(PtyConnect) {
|
|
|
289
294
|
const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
|
|
290
295
|
const v8::Local<v8::Function> exitCallback = v8::Local<v8::Function>::Cast(info[4]);
|
|
291
296
|
|
|
297
|
+
// Fetch pty handle from ID and start process
|
|
298
|
+
pty_baton* handle = get_pty_baton(id);
|
|
299
|
+
if (!handle) {
|
|
300
|
+
Nan::ThrowError("Invalid pty handle");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
292
304
|
// Prepare command line
|
|
293
305
|
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
|
|
294
306
|
HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());
|
|
@@ -311,11 +323,8 @@ static NAN_METHOD(PtyConnect) {
|
|
|
311
323
|
auto envV = vectorFromString(env);
|
|
312
324
|
LPWSTR envArg = envV.empty() ? nullptr : envV.data();
|
|
313
325
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
BOOL success = ConnectNamedPipe(handle->hIn, nullptr);
|
|
318
|
-
success = ConnectNamedPipe(handle->hOut, nullptr);
|
|
326
|
+
ConnectNamedPipe(handle->hIn, nullptr);
|
|
327
|
+
ConnectNamedPipe(handle->hOut, nullptr);
|
|
319
328
|
|
|
320
329
|
// Attach the pseudoconsole to the client application we're creating
|
|
321
330
|
STARTUPINFOEXW siEx{0};
|
|
@@ -391,20 +400,51 @@ static NAN_METHOD(PtyResize) {
|
|
|
391
400
|
}
|
|
392
401
|
|
|
393
402
|
int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
394
|
-
SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
395
|
-
SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
403
|
+
SHORT cols = static_cast<SHORT>(info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust());
|
|
404
|
+
SHORT rows = static_cast<SHORT>(info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust());
|
|
396
405
|
|
|
397
406
|
const pty_baton* handle = get_pty_baton(id);
|
|
398
407
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
|
|
404
|
-
if (pfnResizePseudoConsole)
|
|
408
|
+
if (handle != nullptr) {
|
|
409
|
+
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
410
|
+
bool fLoadedDll = hLibrary != nullptr;
|
|
411
|
+
if (fLoadedDll)
|
|
405
412
|
{
|
|
406
|
-
|
|
407
|
-
pfnResizePseudoConsole
|
|
413
|
+
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
|
|
414
|
+
if (pfnResizePseudoConsole)
|
|
415
|
+
{
|
|
416
|
+
COORD size = {cols, rows};
|
|
417
|
+
pfnResizePseudoConsole(handle->hpc, size);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return info.GetReturnValue().SetUndefined();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static NAN_METHOD(PtyClear) {
|
|
426
|
+
Nan::HandleScope scope;
|
|
427
|
+
|
|
428
|
+
if (info.Length() != 1 ||
|
|
429
|
+
!info[0]->IsNumber()) {
|
|
430
|
+
Nan::ThrowError("Usage: pty.clear(id)");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
435
|
+
|
|
436
|
+
const pty_baton* handle = get_pty_baton(id);
|
|
437
|
+
|
|
438
|
+
if (handle != nullptr) {
|
|
439
|
+
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
440
|
+
bool fLoadedDll = hLibrary != nullptr;
|
|
441
|
+
if (fLoadedDll)
|
|
442
|
+
{
|
|
443
|
+
PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClearPseudoConsole");
|
|
444
|
+
if (pfnClearPseudoConsole)
|
|
445
|
+
{
|
|
446
|
+
pfnClearPseudoConsole(handle->hpc);
|
|
447
|
+
}
|
|
408
448
|
}
|
|
409
449
|
}
|
|
410
450
|
|
|
@@ -424,18 +464,24 @@ static NAN_METHOD(PtyKill) {
|
|
|
424
464
|
|
|
425
465
|
const pty_baton* handle = get_pty_baton(id);
|
|
426
466
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
|
|
432
|
-
if (pfnClosePseudoConsole)
|
|
467
|
+
if (handle != nullptr) {
|
|
468
|
+
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
469
|
+
bool fLoadedDll = hLibrary != nullptr;
|
|
470
|
+
if (fLoadedDll)
|
|
433
471
|
{
|
|
434
|
-
pfnClosePseudoConsole(
|
|
472
|
+
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
|
|
473
|
+
if (pfnClosePseudoConsole)
|
|
474
|
+
{
|
|
475
|
+
pfnClosePseudoConsole(handle->hpc);
|
|
476
|
+
}
|
|
435
477
|
}
|
|
436
|
-
}
|
|
437
478
|
|
|
438
|
-
|
|
479
|
+
DisconnectNamedPipe(handle->hIn);
|
|
480
|
+
DisconnectNamedPipe(handle->hOut);
|
|
481
|
+
CloseHandle(handle->hIn);
|
|
482
|
+
CloseHandle(handle->hOut);
|
|
483
|
+
CloseHandle(handle->hShell);
|
|
484
|
+
}
|
|
439
485
|
|
|
440
486
|
return info.GetReturnValue().SetUndefined();
|
|
441
487
|
}
|
|
@@ -449,6 +495,7 @@ extern "C" void init(v8::Local<v8::Object> target) {
|
|
|
449
495
|
Nan::SetMethod(target, "startProcess", PtyStartProcess);
|
|
450
496
|
Nan::SetMethod(target, "connect", PtyConnect);
|
|
451
497
|
Nan::SetMethod(target, "resize", PtyResize);
|
|
498
|
+
Nan::SetMethod(target, "clear", PtyClear);
|
|
452
499
|
Nan::SetMethod(target, "kill", PtyKill);
|
|
453
500
|
};
|
|
454
501
|
|
|
@@ -12,7 +12,7 @@ static NAN_METHOD(ApiConsoleProcessList) {
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const DWORD pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
16
16
|
|
|
17
17
|
if (!FreeConsole()) {
|
|
18
18
|
Nan::ThrowError("FreeConsole failed");
|
|
@@ -21,10 +21,10 @@ static NAN_METHOD(ApiConsoleProcessList) {
|
|
|
21
21
|
Nan::ThrowError("AttachConsole failed");
|
|
22
22
|
}
|
|
23
23
|
auto processList = std::vector<DWORD>(64);
|
|
24
|
-
auto processCount = GetConsoleProcessList(&processList[0], processList.size());
|
|
24
|
+
auto processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
|
|
25
25
|
if (processList.size() < processCount) {
|
|
26
26
|
processList.resize(processCount);
|
|
27
|
-
processCount = GetConsoleProcessList(&processList[0], processList.size());
|
|
27
|
+
processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
|
|
28
28
|
}
|
|
29
29
|
FreeConsole();
|
|
30
30
|
|
package/src/win/path_util.cc
CHANGED
|
@@ -13,9 +13,28 @@ namespace path_util {
|
|
|
13
13
|
|
|
14
14
|
const wchar_t* to_wstring(const Nan::Utf8String& str) {
|
|
15
15
|
const char *bytes = *str;
|
|
16
|
-
|
|
16
|
+
int sizeOfStr = MultiByteToWideChar(CP_UTF8, 0, bytes, -1, NULL, 0);
|
|
17
|
+
if (sizeOfStr <= 0) {
|
|
18
|
+
return L"";
|
|
19
|
+
}
|
|
17
20
|
wchar_t *output = new wchar_t[sizeOfStr];
|
|
18
|
-
MultiByteToWideChar(CP_UTF8, 0, bytes, -1, output, sizeOfStr);
|
|
21
|
+
int status = MultiByteToWideChar(CP_UTF8, 0, bytes, -1, output, sizeOfStr);
|
|
22
|
+
if (status == 0) {
|
|
23
|
+
return L"";
|
|
24
|
+
}
|
|
25
|
+
return output;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const char* from_wstring(const wchar_t* wstr) {
|
|
29
|
+
int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
|
|
30
|
+
if (bufferSize <= 0) {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
char *output = new char[bufferSize];
|
|
34
|
+
int status = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, output, bufferSize, NULL, NULL);
|
|
35
|
+
if (status == 0) {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
19
38
|
return output;
|
|
20
39
|
}
|
|
21
40
|
|
|
@@ -52,7 +71,7 @@ std::wstring get_shell_path(std::wstring filename) {
|
|
|
52
71
|
|
|
53
72
|
const wchar_t *filename_ = filename.c_str();
|
|
54
73
|
|
|
55
|
-
for (
|
|
74
|
+
for (size_t i = 0; i < paths.size(); ++i) {
|
|
56
75
|
std::wstring path = paths[i];
|
|
57
76
|
wchar_t searchPath[MAX_PATH];
|
|
58
77
|
::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);
|
package/src/win/path_util.h
CHANGED