Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ship conpty.dll/OpenConsole.exe with opt-in experimental option useConptyDll #694

Merged
merged 4 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
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