Skip to content

Commit bd7fa4f

Browse files
authored
Merge pull request microsoft#694 from microsoft/tyriar/conptydll
Ship conpty.dll/OpenConsole.exe with opt-in experimental option useConptyDll
2 parents efbf8eb + 4392169 commit bd7fa4f

2,381 files changed

Lines changed: 1002030 additions & 39 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ module.exports = {
99
"project": "src/tsconfig.json",
1010
"sourceType": "module"
1111
},
12-
"ignorePatterns": "**/typings/*.d.ts",
12+
"ignorePatterns": [
13+
"**/typings/*.d.ts",
14+
"scripts/**/*"
15+
],
1316
"plugins": [
1417
"@typescript-eslint"
1518
],

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
*.test.js
22
*.test.ts
3+
third_party/

examples/fork/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const ptyProcess = pty.spawn(shell, [], {
1010
rows: 26,
1111
cwd: isWindows ? process.env.USERPROFILE : process.env.HOME,
1212
env: Object.assign({ TEST: "Environment vars work" }, process.env),
13-
useConpty: true
13+
useConpty: true,
14+
useConptyDll: true
1415
});
1516

1617
ptyProcess.onData(data => process.stdout.write(data));

scripts/post-install.js

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
var fs = require('fs');
2-
var path = require('path');
1+
//@ts-check
32

4-
var RELEASE_DIR = path.join(__dirname, '..', 'build', 'Release');
5-
var BUILD_FILES = [
3+
const fs = require('fs');
4+
const os = require('os');
5+
const path = require('path');
6+
7+
const RELEASE_DIR = path.join(__dirname, '../build/Release');
8+
const BUILD_FILES = [
69
path.join(RELEASE_DIR, 'conpty.node'),
710
path.join(RELEASE_DIR, 'conpty.pdb'),
811
path.join(RELEASE_DIR, 'conpty_console_list.node'),
@@ -15,14 +18,18 @@ var BUILD_FILES = [
1518
path.join(RELEASE_DIR, 'winpty.dll'),
1619
path.join(RELEASE_DIR, 'winpty.pdb')
1720
];
21+
const CONPTY_DIR = path.join(__dirname, '../third_party/conpty');
22+
const CONPTY_SUPPORTED_ARCH = ['x64', 'arm64'];
23+
24+
console.log('\x1b[32m> Cleaning release folder...\x1b[0m');
1825

19-
cleanFolderRecursive = function(folder) {
26+
function cleanFolderRecursive(folder) {
2027
var files = [];
21-
if( fs.existsSync(folder) ) {
28+
if (fs.existsSync(folder)) {
2229
files = fs.readdirSync(folder);
2330
files.forEach(function(file,index) {
2431
var curPath = path.join(folder, file);
25-
if(fs.lstatSync(curPath).isDirectory()) { // recurse
32+
if (fs.lstatSync(curPath).isDirectory()) { // recurse
2633
cleanFolderRecursive(curPath);
2734
fs.rmdirSync(curPath);
2835
} else if (BUILD_FILES.indexOf(curPath) < 0){ // delete file
@@ -36,7 +43,29 @@ try {
3643
cleanFolderRecursive(RELEASE_DIR);
3744
} catch(e) {
3845
console.log(e);
39-
//process.exit(1);
40-
} finally {
41-
process.exit(0);
46+
process.exit(1);
47+
}
48+
49+
console.log(`\x1b[32m> Moving conpty.dll...\x1b[0m`);
50+
if (os.platform() !== 'win32') {
51+
console.log(' SKIPPED (not Windows)');
52+
} else {
53+
const windowsArch = os.arch();
54+
if (!CONPTY_SUPPORTED_ARCH.includes(windowsArch)) {
55+
console.log(` SKIPPED (unsupported architecture ${windowsArch})`);
56+
} else {
57+
const versionFolder = fs.readdirSync(CONPTY_DIR)[0];
58+
console.log(` Found version ${versionFolder}`);
59+
const sourceFolder = path.join(CONPTY_DIR, versionFolder, `win10-${windowsArch}`);
60+
const destFolder = path.join(RELEASE_DIR, 'conpty');
61+
fs.mkdirSync(destFolder, { recursive: true });
62+
for (const file of ['conpty.dll', 'OpenConsole.exe']) {
63+
const sourceFile = path.join(sourceFolder, file);
64+
const destFile = path.join(destFolder, file);
65+
console.log(` Copying ${sourceFile} -> ${destFile}`);
66+
fs.copyFileSync(sourceFile, destFile);
67+
}
68+
}
4269
}
70+
71+
process.exit(0);

src/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export interface IPtyForkOptions extends IBasePtyForkOptions {
119119

120120
export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
121121
useConpty?: boolean;
122+
useConptyDll?: boolean;
122123
conptyInheritCursor?: boolean;
123124
}
124125

src/native.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
*/
44

55
interface IConptyNative {
6-
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean): IConptyProcess;
6+
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean, useConptyDll: boolean): IConptyProcess;
77
connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number };
8-
resize(ptyId: number, cols: number, rows: number): void;
8+
resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void;
99
clear(ptyId: number): void;
10-
kill(ptyId: number): void;
10+
kill(ptyId: number, useConptyDll: boolean): void;
1111
}
1212

1313
interface IWinptyNative {

src/win/conpty.cc

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111
#define _WIN32_WINNT 0x600
1212

1313
#define NODE_ADDON_API_DISABLE_DEPRECATED
14-
#include <napi.h>
14+
#include <node_api.h>
1515
#include <assert.h>
1616
#include <Shlwapi.h> // PathCombine, PathIsRelative
1717
#include <sstream>
18+
#include <iostream>
1819
#include <string>
1920
#include <thread>
2021
#include <vector>
2122
#include <Windows.h>
2223
#include <strsafe.h>
2324
#include "path_util.h"
25+
#include "conpty.h"
2426

2527
// Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17134
2628
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
@@ -162,20 +164,46 @@ bool createDataServerPipe(bool write,
162164
return *hServer != INVALID_HANDLE_VALUE;
163165
}
164166

165-
HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
167+
HANDLE LoadConptyDll(const Napi::CallbackInfo& info,
168+
const bool useConptyDll)
169+
{
170+
if (!useConptyDll) {
171+
return LoadLibraryExW(L"kernel32.dll", 0, 0);
172+
}
173+
wchar_t currentDir[MAX_PATH];
174+
DWORD result = GetCurrentDirectoryW(MAX_PATH, currentDir);
175+
if (result == 0) {
176+
throw errorWithCode(info, "Failed to get current directory");
177+
}
178+
std::wstring currentDirStr(currentDir);
179+
180+
std::wstring conptyDllPath = currentDirStr + L"\\build\\Release\\conpty\\conpty.dll";
181+
if (!path_util::file_exists(conptyDllPath)) {
182+
throw errorWithCode(info, "Cannot find conpty.dll");
183+
}
184+
185+
return LoadLibraryW(conptyDllPath.c_str());
186+
}
187+
188+
HRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,
189+
COORD size,
166190
DWORD dwFlags,
167191
HANDLE *phInput,
168192
HANDLE *phOutput,
169193
HPCON* phPC,
170194
std::wstring& inName,
171195
std::wstring& outName,
172-
const std::wstring& pipeName)
196+
const std::wstring& pipeName,
197+
const bool useConptyDll)
173198
{
174-
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
199+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
200+
DWORD error = GetLastError();
175201
bool fLoadedDll = hLibrary != nullptr;
176202
if (fLoadedDll)
177203
{
178-
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "CreatePseudoConsole");
204+
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress(
205+
(HMODULE)hLibrary,
206+
useConptyDll ? "ConptyCreatePseudoConsole" : "CreatePseudoConsole");
179207
if (pfnCreate)
180208
{
181209
if (phPC == NULL || phInput == NULL || phOutput == NULL)
@@ -202,6 +230,8 @@ HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
202230
// We should fall back to winpty in this case.
203231
return HRESULT_FROM_WIN32(GetLastError());
204232
}
233+
} else {
234+
throw errorWithCode(info, "Failed to load conpty.dll");
205235
}
206236

207237
// Failed to find kernel32. This is realy unlikely - honestly no idea how
@@ -219,14 +249,15 @@ static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
219249
std::unique_ptr<wchar_t[]> mutableCommandline;
220250
PROCESS_INFORMATION _piClient{};
221251

222-
if (info.Length() != 6 ||
252+
if (info.Length() != 7 ||
223253
!info[0].IsString() ||
224254
!info[1].IsNumber() ||
225255
!info[2].IsNumber() ||
226256
!info[3].IsBoolean() ||
227257
!info[4].IsString() ||
228-
!info[5].IsBoolean()) {
229-
throw Napi::Error::New(env, "Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor)");
258+
!info[5].IsBoolean() ||
259+
!info[6].IsBoolean()) {
260+
throw Napi::Error::New(env, "Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor, useConptyDll)");
230261
}
231262

232263
const std::wstring filename(path_util::to_wstring(info[0].As<Napi::String>()));
@@ -235,6 +266,7 @@ static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
235266
const bool debug = info[3].As<Napi::Boolean>().Value();
236267
const std::wstring pipeName(path_util::to_wstring(info[4].As<Napi::String>()));
237268
const bool inheritCursor = info[5].As<Napi::Boolean>().Value();
269+
const bool useConptyDll = info[6].As<Napi::Boolean>().Value();
238270

239271
// use environment 'Path' variable to determine location of
240272
// the relative path that we have recieved (e.g cmd.exe)
@@ -254,7 +286,7 @@ static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
254286

255287
HANDLE hIn, hOut;
256288
HPCON hpc;
257-
HRESULT hr = CreateNamedPipesAndPseudoConsole({cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName);
289+
HRESULT hr = CreateNamedPipesAndPseudoConsole(info, {cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName, useConptyDll);
258290

259291
// Restore default handling of ctrl+c
260292
SetConsoleCtrlHandler(NULL, FALSE);
@@ -403,25 +435,27 @@ static Napi::Value PtyResize(const Napi::CallbackInfo& info) {
403435
Napi::Env env(info.Env());
404436
Napi::HandleScope scope(env);
405437

406-
if (info.Length() != 3 ||
438+
if (info.Length() != 4 ||
407439
!info[0].IsNumber() ||
408440
!info[1].IsNumber() ||
409-
!info[2].IsNumber()) {
410-
throw Napi::Error::New(env, "Usage: pty.resize(id, cols, rows)");
441+
!info[2].IsNumber() ||
442+
!info[3].IsBoolean()) {
443+
throw Napi::Error::New(env, "Usage: pty.resize(id, cols, rows, useConptyDll)");
411444
}
412445

413446
int id = info[0].As<Napi::Number>().Int32Value();
414447
SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());
415448
SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
449+
const bool useConptyDll = info[3].As<Napi::Boolean>().Value();
416450

417451
const pty_baton* handle = get_pty_baton(id);
418452

419453
if (handle != nullptr) {
420-
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
454+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
421455
bool fLoadedDll = hLibrary != nullptr;
422456
if (fLoadedDll)
423457
{
424-
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
458+
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ConptyResizePseudoConsole");
425459
if (pfnResizePseudoConsole)
426460
{
427461
COORD size = {cols, rows};
@@ -466,21 +500,23 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
466500
Napi::Env env(info.Env());
467501
Napi::HandleScope scope(env);
468502

469-
if (info.Length() != 1 ||
470-
!info[0].IsNumber()) {
471-
throw Napi::Error::New(env, "Usage: pty.kill(id)");
503+
if (info.Length() != 2 ||
504+
!info[0].IsNumber() ||
505+
!info[1].IsBoolean()) {
506+
throw Napi::Error::New(env, "Usage: pty.kill(id, useConptyDll)");
472507
}
473508

474509
int id = info[0].As<Napi::Number>().Int32Value();
510+
const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
475511

476512
const pty_baton* handle = get_pty_baton(id);
477513

478514
if (handle != nullptr) {
479-
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
515+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
480516
bool fLoadedDll = hLibrary != nullptr;
481517
if (fLoadedDll)
482518
{
483-
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
519+
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ConptyClosePseudoConsole");
484520
if (pfnClosePseudoConsole)
485521
{
486522
pfnClosePseudoConsole(handle->hpc);

src/win/conpty.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
// This header prototypes the Pseudoconsole symbols from conpty.lib with their original names.
5+
// This is required because we cannot import __imp_CreatePseudoConsole from a static library
6+
// as it doesn't produce an import lib.
7+
// We can't use an /ALTERNATENAME trick because it seems that that name is only resolved when the
8+
// linker cannot otherwise find the symbol.
9+
10+
#pragma once
11+
12+
#include <consoleapi.h>
13+
14+
#ifndef CONPTY_IMPEXP
15+
#define CONPTY_IMPEXP __declspec(dllimport)
16+
#endif
17+
18+
#ifndef CONPTY_EXPORT
19+
#ifdef __cplusplus
20+
#define CONPTY_EXPORT extern "C" CONPTY_IMPEXP
21+
#else
22+
#define CONPTY_EXPORT extern CONPTY_IMPEXP
23+
#endif
24+
#endif
25+
26+
#define PSEUDOCONSOLE_RESIZE_QUIRK (2u)
27+
#define PSEUDOCONSOLE_PASSTHROUGH_MODE (8u)
28+
29+
CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
30+
CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
31+
32+
CONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);
33+
CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);
34+
CONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show);
35+
CONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent);
36+
CONPTY_EXPORT HRESULT WINAPI ConptyReleasePseudoConsole(HPCON hPC);
37+
38+
CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);
39+
CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsoleTimeout(HPCON hPC, DWORD dwMilliseconds);
40+
41+
CONPTY_EXPORT HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);

src/windowsPtyAgent.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class WindowsPtyAgent {
5454
rows: number,
5555
debug: boolean,
5656
private _useConpty: boolean | undefined,
57+
private _useConptyDll: boolean = false,
5758
conptyInheritCursor: boolean = false
5859
) {
5960
if (this._useConpty === undefined || this._useConpty === true) {
@@ -99,7 +100,7 @@ export class WindowsPtyAgent {
99100
// Open pty session.
100101
let term: IConptyProcess | IWinptyProcess;
101102
if (this._useConpty) {
102-
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
103+
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
103104
} else {
104105
term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);
105106
this._pid = (term as IWinptyProcess).pid;
@@ -144,10 +145,10 @@ export class WindowsPtyAgent {
144145
if (this._exitCode !== undefined) {
145146
throw new Error('Cannot resize a pty that has already exited');
146147
}
147-
this._ptyNative.resize(this._pty, cols, rows);
148+
this._ptyNative.resize(this._pty, cols, rows, this._useConptyDll);
148149
return;
149150
}
150-
this._ptyNative.resize(this._pid, cols, rows);
151+
this._ptyNative.resize(this._pid, cols, rows, this._useConptyDll);
151152
}
152153

153154
public clear(): void {
@@ -169,7 +170,7 @@ export class WindowsPtyAgent {
169170
// Ignore if process cannot be found (kill ESRCH error)
170171
}
171172
});
172-
(this._ptyNative as IConptyNative).kill(this._pty);
173+
(this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll);
173174
});
174175
} else {
175176
// Because pty.kill closes the handle, it will kill most processes by itself.

src/windowsTerminal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class WindowsTerminal extends Terminal {
4848
this._deferreds = [];
4949

5050
// Create new termal.
51-
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.conptyInheritCursor);
51+
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor);
5252
this._socket = this._agent.outSocket;
5353

5454
// Not available until `ready` event emitted.

0 commit comments

Comments
 (0)