Skip to content

Commit

Permalink
Merge pull request #694 from microsoft/tyriar/conptydll
Browse files Browse the repository at this point in the history
Ship conpty.dll/OpenConsole.exe with opt-in experimental option useConptyDll
  • Loading branch information
Tyriar authored Aug 1, 2024
2 parents efbf8eb + 4392169 commit bd7fa4f
Show file tree
Hide file tree
Showing 2,381 changed files with 1,002,030 additions and 39 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ module.exports = {
"project": "src/tsconfig.json",
"sourceType": "module"
},
"ignorePatterns": "**/typings/*.d.ts",
"ignorePatterns": [
"**/typings/*.d.ts",
"scripts/**/*"
],
"plugins": [
"@typescript-eslint"
],
Expand Down
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.test.js
*.test.ts
third_party/
3 changes: 2 additions & 1 deletion examples/fork/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const ptyProcess = pty.spawn(shell, [], {
rows: 26,
cwd: isWindows ? process.env.USERPROFILE : process.env.HOME,
env: Object.assign({ TEST: "Environment vars work" }, process.env),
useConpty: true
useConpty: true,
useConptyDll: true
});

ptyProcess.onData(data => process.stdout.write(data));
Expand Down
49 changes: 39 additions & 10 deletions scripts/post-install.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
var fs = require('fs');
var path = require('path');
//@ts-check

var RELEASE_DIR = path.join(__dirname, '..', 'build', 'Release');
var BUILD_FILES = [
const fs = require('fs');
const os = require('os');
const path = require('path');

const RELEASE_DIR = path.join(__dirname, '../build/Release');
const BUILD_FILES = [
path.join(RELEASE_DIR, 'conpty.node'),
path.join(RELEASE_DIR, 'conpty.pdb'),
path.join(RELEASE_DIR, 'conpty_console_list.node'),
Expand All @@ -15,14 +18,18 @@ var BUILD_FILES = [
path.join(RELEASE_DIR, 'winpty.dll'),
path.join(RELEASE_DIR, 'winpty.pdb')
];
const CONPTY_DIR = path.join(__dirname, '../third_party/conpty');
const CONPTY_SUPPORTED_ARCH = ['x64', 'arm64'];

console.log('\x1b[32m> Cleaning release folder...\x1b[0m');

cleanFolderRecursive = function(folder) {
function cleanFolderRecursive(folder) {
var files = [];
if( fs.existsSync(folder) ) {
if (fs.existsSync(folder)) {
files = fs.readdirSync(folder);
files.forEach(function(file,index) {
var curPath = path.join(folder, file);
if(fs.lstatSync(curPath).isDirectory()) { // recurse
if (fs.lstatSync(curPath).isDirectory()) { // recurse
cleanFolderRecursive(curPath);
fs.rmdirSync(curPath);
} else if (BUILD_FILES.indexOf(curPath) < 0){ // delete file
Expand All @@ -36,7 +43,29 @@ try {
cleanFolderRecursive(RELEASE_DIR);
} catch(e) {
console.log(e);
//process.exit(1);
} finally {
process.exit(0);
process.exit(1);
}

console.log(`\x1b[32m> Moving conpty.dll...\x1b[0m`);
if (os.platform() !== 'win32') {
console.log(' SKIPPED (not Windows)');
} else {
const windowsArch = os.arch();
if (!CONPTY_SUPPORTED_ARCH.includes(windowsArch)) {
console.log(` SKIPPED (unsupported architecture ${windowsArch})`);
} else {
const versionFolder = fs.readdirSync(CONPTY_DIR)[0];
console.log(` Found version ${versionFolder}`);
const sourceFolder = path.join(CONPTY_DIR, versionFolder, `win10-${windowsArch}`);
const destFolder = path.join(RELEASE_DIR, 'conpty');
fs.mkdirSync(destFolder, { recursive: true });
for (const file of ['conpty.dll', 'OpenConsole.exe']) {
const sourceFile = path.join(sourceFolder, file);
const destFile = path.join(destFolder, file);
console.log(` Copying ${sourceFile} -> ${destFile}`);
fs.copyFileSync(sourceFile, destFile);
}
}
}

process.exit(0);
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export interface IPtyForkOptions extends IBasePtyForkOptions {

export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
useConpty?: boolean;
useConptyDll?: boolean;
conptyInheritCursor?: boolean;
}

Expand Down
6 changes: 3 additions & 3 deletions src/native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*/

interface IConptyNative {
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean): IConptyProcess;
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean, useConptyDll: boolean): IConptyProcess;
connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number };
resize(ptyId: number, cols: number, rows: number): void;
resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void;
clear(ptyId: number): void;
kill(ptyId: number): void;
kill(ptyId: number, useConptyDll: boolean): void;
}

interface IWinptyNative {
Expand Down
74 changes: 55 additions & 19 deletions src/win/conpty.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@
#define _WIN32_WINNT 0x600

#define NODE_ADDON_API_DISABLE_DEPRECATED
#include <napi.h>
#include <node_api.h>
#include <assert.h>
#include <Shlwapi.h> // PathCombine, PathIsRelative
#include <sstream>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <Windows.h>
#include <strsafe.h>
#include "path_util.h"
#include "conpty.h"

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

HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
HANDLE LoadConptyDll(const Napi::CallbackInfo& info,
const bool useConptyDll)
{
if (!useConptyDll) {
return LoadLibraryExW(L"kernel32.dll", 0, 0);
}
wchar_t currentDir[MAX_PATH];
DWORD result = GetCurrentDirectoryW(MAX_PATH, currentDir);
if (result == 0) {
throw errorWithCode(info, "Failed to get current directory");
}
std::wstring currentDirStr(currentDir);

std::wstring conptyDllPath = currentDirStr + L"\\build\\Release\\conpty\\conpty.dll";
if (!path_util::file_exists(conptyDllPath)) {
throw errorWithCode(info, "Cannot find conpty.dll");
}

return LoadLibraryW(conptyDllPath.c_str());
}

HRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,
COORD size,
DWORD dwFlags,
HANDLE *phInput,
HANDLE *phOutput,
HPCON* phPC,
std::wstring& inName,
std::wstring& outName,
const std::wstring& pipeName)
const std::wstring& pipeName,
const bool useConptyDll)
{
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
DWORD error = GetLastError();
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
{
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "CreatePseudoConsole");
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress(
(HMODULE)hLibrary,
useConptyDll ? "ConptyCreatePseudoConsole" : "CreatePseudoConsole");
if (pfnCreate)
{
if (phPC == NULL || phInput == NULL || phOutput == NULL)
Expand All @@ -202,6 +230,8 @@ HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
// We should fall back to winpty in this case.
return HRESULT_FROM_WIN32(GetLastError());
}
} else {
throw errorWithCode(info, "Failed to load conpty.dll");
}

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

if (info.Length() != 6 ||
if (info.Length() != 7 ||
!info[0].IsString() ||
!info[1].IsNumber() ||
!info[2].IsNumber() ||
!info[3].IsBoolean() ||
!info[4].IsString() ||
!info[5].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor)");
!info[5].IsBoolean() ||
!info[6].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor, useConptyDll)");
}

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

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

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

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

if (info.Length() != 3 ||
if (info.Length() != 4 ||
!info[0].IsNumber() ||
!info[1].IsNumber() ||
!info[2].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.resize(id, cols, rows)");
!info[2].IsNumber() ||
!info[3].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.resize(id, cols, rows, useConptyDll)");
}

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

const pty_baton* handle = get_pty_baton(id);

if (handle != nullptr) {
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
{
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ConptyResizePseudoConsole");
if (pfnResizePseudoConsole)
{
COORD size = {cols, rows};
Expand Down Expand Up @@ -466,21 +500,23 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);

if (info.Length() != 1 ||
!info[0].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.kill(id)");
if (info.Length() != 2 ||
!info[0].IsNumber() ||
!info[1].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.kill(id, useConptyDll)");
}

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

const pty_baton* handle = get_pty_baton(id);

if (handle != nullptr) {
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
{
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ConptyClosePseudoConsole");
if (pfnClosePseudoConsole)
{
pfnClosePseudoConsole(handle->hpc);
Expand Down
41 changes: 41 additions & 0 deletions src/win/conpty.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

// This header prototypes the Pseudoconsole symbols from conpty.lib with their original names.
// This is required because we cannot import __imp_CreatePseudoConsole from a static library
// as it doesn't produce an import lib.
// We can't use an /ALTERNATENAME trick because it seems that that name is only resolved when the
// linker cannot otherwise find the symbol.

#pragma once

#include <consoleapi.h>

#ifndef CONPTY_IMPEXP
#define CONPTY_IMPEXP __declspec(dllimport)
#endif

#ifndef CONPTY_EXPORT
#ifdef __cplusplus
#define CONPTY_EXPORT extern "C" CONPTY_IMPEXP
#else
#define CONPTY_EXPORT extern CONPTY_IMPEXP
#endif
#endif

#define PSEUDOCONSOLE_RESIZE_QUIRK (2u)
#define PSEUDOCONSOLE_PASSTHROUGH_MODE (8u)

CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);

CONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);
CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);
CONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show);
CONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent);
CONPTY_EXPORT HRESULT WINAPI ConptyReleasePseudoConsole(HPCON hPC);

CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);
CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsoleTimeout(HPCON hPC, DWORD dwMilliseconds);

CONPTY_EXPORT HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);
9 changes: 5 additions & 4 deletions src/windowsPtyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class WindowsPtyAgent {
rows: number,
debug: boolean,
private _useConpty: boolean | undefined,
private _useConptyDll: boolean = false,
conptyInheritCursor: boolean = false
) {
if (this._useConpty === undefined || this._useConpty === true) {
Expand Down Expand Up @@ -99,7 +100,7 @@ export class WindowsPtyAgent {
// Open pty session.
let term: IConptyProcess | IWinptyProcess;
if (this._useConpty) {
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
} else {
term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);
this._pid = (term as IWinptyProcess).pid;
Expand Down Expand Up @@ -144,10 +145,10 @@ export class WindowsPtyAgent {
if (this._exitCode !== undefined) {
throw new Error('Cannot resize a pty that has already exited');
}
this._ptyNative.resize(this._pty, cols, rows);
this._ptyNative.resize(this._pty, cols, rows, this._useConptyDll);
return;
}
this._ptyNative.resize(this._pid, cols, rows);
this._ptyNative.resize(this._pid, cols, rows, this._useConptyDll);
}

public clear(): void {
Expand All @@ -169,7 +170,7 @@ export class WindowsPtyAgent {
// Ignore if process cannot be found (kill ESRCH error)
}
});
(this._ptyNative as IConptyNative).kill(this._pty);
(this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll);
});
} else {
// Because pty.kill closes the handle, it will kill most processes by itself.
Expand Down
2 changes: 1 addition & 1 deletion src/windowsTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class WindowsTerminal extends Terminal {
this._deferreds = [];

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

// Not available until `ready` event emitted.
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit bd7fa4f

Please sign in to comment.