3 * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
4 * Copyright (c) 2016, Daniel Imms (MIT License).
5 * Copyright (c) 2018, Microsoft Corporation (MIT License).
7 Object.defineProperty(exports, "__esModule", { value: true });
8 var os = require("os");
9 var path = require("path");
10 var net_1 = require("net");
11 var child_process_1 = require("child_process");
15 * The amount of time to wait for additional data after the conpty shell process has exited before
16 * shutting down the socket. The timer will be reset if a new data event comes in after the timer
19 var FLUSH_DATA_INTERVAL = 20;
21 * This agent sits between the WindowsTerminal class and provides a common interface for both conpty
24 var WindowsPtyAgent = /** @class */ (function () {
25 function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty, conptyInheritCursor) {
27 if (conptyInheritCursor === void 0) { conptyInheritCursor = false; }
28 this._useConpty = _useConpty;
29 if (this._useConpty === undefined || this._useConpty === true) {
30 this._useConpty = this._getWindowsBuildNumber() >= 18309;
32 if (this._useConpty) {
35 conptyNative = require('../build/Release/conpty.node');
39 conptyNative = require('../build/Debug/conpty.node');
42 console.error('innerError', innerError);
43 // Re-throw the exception from the Release require if the Debug require fails as well
52 winptyNative = require('../build/Release/pty.node');
56 winptyNative = require('../build/Debug/pty.node');
59 console.error('innerError', innerError);
60 // Re-throw the exception from the Release require if the Debug require fails as well
66 this._ptyNative = this._useConpty ? conptyNative : winptyNative;
67 // Sanitize input variable.
68 cwd = path.resolve(cwd);
69 // Compose command line
70 var commandLine = argsToCommandLine(file, args);
73 if (this._useConpty) {
74 term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
77 term = this._ptyNative.startProcess(file, commandLine, env, cwd, cols, rows, debug);
79 this._innerPid = term.innerPid;
80 this._innerPidHandle = term.innerPidHandle;
82 // Not available on windows.
84 // Generated incremental number that has no real purpose besides using it
87 // Create terminal pipe IPC channel and forward to a local unix socket.
88 this._outSocket = new net_1.Socket();
89 this._outSocket.setEncoding('utf8');
90 this._outSocket.connect(term.conout, function () {
91 // TODO: Emit event on agent instead of socket?
93 _this._outSocket.emit('ready_datapipe');
95 this._inSocket = new net_1.Socket();
96 this._inSocket.setEncoding('utf8');
97 this._inSocket.connect(term.conin);
98 // TODO: Wait for ready event?
99 if (this._useConpty) {
100 var connect = this._ptyNative.connect(this._pty, commandLine, cwd, env, function (c) { return _this._$onProcessExit(c); });
101 this._innerPid = connect.pid;
104 Object.defineProperty(WindowsPtyAgent.prototype, "inSocket", {
105 get: function () { return this._inSocket; },
109 Object.defineProperty(WindowsPtyAgent.prototype, "outSocket", {
110 get: function () { return this._outSocket; },
114 Object.defineProperty(WindowsPtyAgent.prototype, "fd", {
115 get: function () { return this._fd; },
119 Object.defineProperty(WindowsPtyAgent.prototype, "innerPid", {
120 get: function () { return this._innerPid; },
124 Object.defineProperty(WindowsPtyAgent.prototype, "pty", {
125 get: function () { return this._pty; },
129 WindowsPtyAgent.prototype.resize = function (cols, rows) {
130 if (this._useConpty) {
131 if (this._exitCode !== undefined) {
132 throw new Error('Cannot resize a pty that has already exited');
134 this._ptyNative.resize(this._pty, cols, rows);
137 this._ptyNative.resize(this._pid, cols, rows);
139 WindowsPtyAgent.prototype.kill = function () {
141 this._inSocket.readable = false;
142 this._inSocket.writable = false;
143 this._outSocket.readable = false;
144 this._outSocket.writable = false;
145 // Tell the agent to kill the pty, this releases handles to the process
146 if (this._useConpty) {
147 this._getConsoleProcessList().then(function (consoleProcessList) {
148 consoleProcessList.forEach(function (pid) {
153 // Ignore if process cannot be found (kill ESRCH error)
156 _this._ptyNative.kill(_this._pty);
160 this._ptyNative.kill(this._pid, this._innerPidHandle);
161 // Since pty.kill closes the handle it will kill most processes by itself
162 // and process IDs can be reused as soon as all handles to them are
163 // dropped, we want to immediately kill the entire console process list.
164 // If we do not force kill all processes here, node servers in particular
165 // seem to become detached and remain running (see
166 // Microsoft/vscode#26807).
167 var processList = this._ptyNative.getProcessList(this._pid);
168 processList.forEach(function (pid) {
173 // Ignore if process cannot be found (kill ESRCH error)
178 WindowsPtyAgent.prototype._getConsoleProcessList = function () {
180 return new Promise(function (resolve) {
181 var agent = child_process_1.fork(path.join(__dirname, 'conpty_console_list_agent'), [_this._innerPid.toString()]);
182 agent.on('message', function (message) {
183 clearTimeout(timeout);
184 resolve(message.consoleProcessList);
186 var timeout = setTimeout(function () {
187 // Something went wrong, just send back the shell PID
189 resolve([_this._innerPid]);
193 Object.defineProperty(WindowsPtyAgent.prototype, "exitCode", {
195 if (this._useConpty) {
196 return this._exitCode;
198 return this._ptyNative.getExitCode(this._innerPidHandle);
203 WindowsPtyAgent.prototype._getWindowsBuildNumber = function () {
204 var osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
206 if (osVersion && osVersion.length === 4) {
207 buildNumber = parseInt(osVersion[3]);
211 WindowsPtyAgent.prototype._generatePipeName = function () {
212 return "conpty-" + Math.random() * 10000000;
215 * Triggered from the native side when a contpy process exits.
217 WindowsPtyAgent.prototype._$onProcessExit = function (exitCode) {
219 this._exitCode = exitCode;
220 this._flushDataAndCleanUp();
221 this._outSocket.on('data', function () { return _this._flushDataAndCleanUp(); });
223 WindowsPtyAgent.prototype._flushDataAndCleanUp = function () {
225 if (this._closeTimeout) {
226 clearTimeout(this._closeTimeout);
228 this._closeTimeout = setTimeout(function () { return _this._cleanUpProcess(); }, FLUSH_DATA_INTERVAL);
230 WindowsPtyAgent.prototype._cleanUpProcess = function () {
231 this._inSocket.readable = false;
232 this._inSocket.writable = false;
233 this._outSocket.readable = false;
234 this._outSocket.writable = false;
235 this._outSocket.destroy();
237 return WindowsPtyAgent;
239 exports.WindowsPtyAgent = WindowsPtyAgent;
240 // Convert argc/argv into a Win32 command-line following the escaping convention
241 // documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from
243 function argsToCommandLine(file, args) {
244 if (isCommandLine(args)) {
245 if (args.length === 0) {
248 return argsToCommandLine(file, []) + " " + args;
251 Array.prototype.push.apply(argv, args);
253 for (var argIndex = 0; argIndex < argv.length; argIndex++) {
257 var arg = argv[argIndex];
258 // if it is empty or it contains whitespace and is not already quoted
259 var hasLopsidedEnclosingQuote = xOr((arg[0] !== '"'), (arg[arg.length - 1] !== '"'));
260 var hasNoEnclosingQuotes = ((arg[0] !== '"') && (arg[arg.length - 1] !== '"'));
261 var quote = arg === '' ||
262 (arg.indexOf(' ') !== -1 ||
263 arg.indexOf('\t') !== -1) &&
265 (hasLopsidedEnclosingQuote || hasNoEnclosingQuotes));
270 for (var i = 0; i < arg.length; i++) {
275 else if (p === '"') {
276 result += repeatText('\\', bsCount * 2 + 1);
281 result += repeatText('\\', bsCount);
287 result += repeatText('\\', bsCount * 2);
291 result += repeatText('\\', bsCount);
296 exports.argsToCommandLine = argsToCommandLine;
297 function isCommandLine(args) {
298 return typeof args === 'string';
300 function repeatText(text, count) {
302 for (var i = 0; i < count; i++) {
307 function xOr(arg1, arg2) {
308 return ((arg1 && !arg2) || (!arg1 && arg2));
310 //# sourceMappingURL=windowsPtyAgent.js.map