-
Notifications
You must be signed in to change notification settings - Fork 250
/
windowsPtyAgent.ts
173 lines (152 loc) · 4.81 KB
/
windowsPtyAgent.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
*/
import * as path from 'path';
import { Socket } from 'net';
import { ArgvOrCommandLine } from './types';
const pty = require(path.join('..', 'build', 'Release', 'pty.node'));
/**
* Agent. Internal class.
*
* Everytime a new pseudo terminal is created it is contained
* within agent.exe. When this process is started there are two
* available named pipes (control and data socket).
*/
export class WindowsPtyAgent {
private _inSocket: Socket;
private _outSocket: Socket;
private _pid: number;
private _innerPid: number;
private _innerPidHandle: number;
private _fd: any;
private _pty: number;
public get inSocket(): Socket { return this._inSocket; }
public get outSocket(): Socket { return this._outSocket; }
public get fd(): any { return this._fd; }
public get innerPid(): number { return this._innerPid; }
public get pty(): number { return this._pty; }
constructor(
file: string,
args: ArgvOrCommandLine,
env: string[],
cwd: string,
cols: number,
rows: number,
debug: boolean
) {
// Sanitize input variable.
cwd = path.resolve(cwd);
// Compose command line
const commandLine = argsToCommandLine(file, args);
// Open pty session.
const term = pty.startProcess(file, commandLine, env, cwd, cols, rows, debug);
// Terminal pid.
this._pid = term.pid;
this._innerPid = term.innerPid;
this._innerPidHandle = term.innerPidHandle;
// Not available on windows.
this._fd = term.fd;
// Generated incremental number that has no real purpose besides using it
// as a terminal id.
this._pty = term.pty;
// Create terminal pipe IPC channel and forward to a local unix socket.
this._outSocket = new Socket();
this._outSocket.setEncoding('utf8');
this._outSocket.connect(term.conout, () => {
// TODO: Emit event on agent instead of socket?
// Emit ready event.
this._outSocket.emit('ready_datapipe');
});
this._inSocket = new Socket();
this._inSocket.setEncoding('utf8');
this._inSocket.connect(term.conin);
// TODO: Wait for ready event?
}
public resize(cols: number, rows: number): void {
pty.resize(this._pid, cols, rows);
}
public kill(): void {
this._inSocket.readable = false;
this._inSocket.writable = false;
this._outSocket.readable = false;
this._outSocket.writable = false;
const processList: number[] = pty.getProcessList(this._pid);
// Tell the agent to kill the pty, this releases handles to the process
pty.kill(this._pid, this._innerPidHandle);
// Since pty.kill will kill most processes by itself and process IDs can be
// reused as soon as all handles to them are dropped, we want to immediately
// kill the entire console process list. If we do not force kill all
// processes here, node servers in particular seem to become detached and
// remain running (see Microsoft/vscode#26807).
processList.forEach(pid => {
try {
process.kill(pid);
} catch (e) {
// Ignore if process cannot be found (kill ESRCH error)
}
});
}
public getExitCode(): number {
return pty.getExitCode(this._innerPidHandle);
}
}
// Convert argc/argv into a Win32 command-line following the escaping convention
// documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from
// winpty project.
export function argsToCommandLine(file: string, args: ArgvOrCommandLine): string {
if (isCommandLine(args)) {
if (args.length === 0) {
return file;
}
return `${argsToCommandLine(file, [])} ${args}`;
}
const argv = [file];
Array.prototype.push.apply(argv, args);
let result = '';
for (let argIndex = 0; argIndex < argv.length; argIndex++) {
if (argIndex > 0) {
result += ' ';
}
const arg = argv[argIndex];
const quote =
arg.indexOf(' ') !== -1 ||
arg.indexOf('\t') !== -1 ||
arg === '';
if (quote) {
result += '\"';
}
let bsCount = 0;
for (let i = 0; i < arg.length; i++) {
const p = arg[i];
if (p === '\\') {
bsCount++;
} else if (p === '"') {
result += repeatText('\\', bsCount * 2 + 1);
result += '"';
bsCount = 0;
} else {
result += repeatText('\\', bsCount);
bsCount = 0;
result += p;
}
}
if (quote) {
result += repeatText('\\', bsCount * 2);
result += '\"';
} else {
result += repeatText('\\', bsCount);
}
}
return result;
}
function isCommandLine(args: ArgvOrCommandLine): args is string {
return typeof args === 'string';
}
function repeatText(text: string, count: number): string {
let result = '';
for (let i = 0; i < count; i++) {
result += text;
}
return result;
}