node-pty 1.1.0-beta3 → 1.1.0-beta31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/binding.gyp +4 -15
- package/deps/winpty/src/agent/Agent.cc +11 -3
- package/lib/unixTerminal.js +4 -27
- package/lib/unixTerminal.js.map +1 -1
- package/lib/unixTerminal.test.js +3 -5
- package/lib/unixTerminal.test.js.map +1 -1
- package/lib/windowsConoutConnection.js +6 -3
- package/lib/windowsConoutConnection.js.map +1 -1
- package/lib/windowsPtyAgent.js +42 -22
- package/lib/windowsPtyAgent.js.map +1 -1
- package/lib/windowsTerminal.js +1 -1
- package/lib/windowsTerminal.js.map +1 -1
- package/lib/windowsTerminal.test.js +38 -26
- package/lib/windowsTerminal.test.js.map +1 -1
- package/lib/worker/conoutSocketWorker.js.map +1 -1
- package/package.json +4 -2
- package/scripts/{publish.js → increment-version.js} +7 -15
- package/scripts/post-install.js +51 -10
- package/src/interfaces.ts +1 -0
- package/src/native.d.ts +5 -5
- package/src/unix/pty.cc +241 -284
- package/src/unixTerminal.test.ts +3 -6
- package/src/unixTerminal.ts +4 -18
- package/src/win/conpty.cc +266 -185
- package/src/win/conpty.h +41 -0
- package/src/win/conpty_console_list.cc +16 -15
- package/src/win/path_util.cc +39 -36
- package/src/win/path_util.h +5 -2
- package/src/win/winpty.cc +108 -151
- package/src/windowsConoutConnection.ts +6 -3
- package/src/windowsPtyAgent.ts +39 -21
- package/src/windowsTerminal.test.ts +43 -32
- package/src/windowsTerminal.ts +1 -1
- package/src/worker/conoutSocketWorker.ts +2 -2
- package/third_party/conpty/1.22.250204002/win10-arm64/OpenConsole.exe +0 -0
- package/third_party/conpty/1.22.250204002/win10-arm64/conpty.dll +0 -0
- package/third_party/conpty/1.22.250204002/win10-x64/OpenConsole.exe +0 -0
- package/third_party/conpty/1.22.250204002/win10-x64/conpty.dll +0 -0
- package/typings/node-pty.d.ts +8 -0
package/src/win/conpty.cc
CHANGED
|
@@ -8,23 +8,21 @@
|
|
|
8
8
|
* with pseudo-terminal file descriptors.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
#include <node_version.h>
|
|
13
|
-
#if NODE_MODULE_VERSION <= 57
|
|
14
|
-
#define _WIN32_WINNT 0x600
|
|
15
|
-
#endif
|
|
11
|
+
#define _WIN32_WINNT 0x600
|
|
16
12
|
|
|
17
|
-
#
|
|
18
|
-
#include <
|
|
13
|
+
#define NODE_ADDON_API_DISABLE_DEPRECATED
|
|
14
|
+
#include <node_api.h>
|
|
15
|
+
#include <assert.h>
|
|
19
16
|
#include <Shlwapi.h> // PathCombine, PathIsRelative
|
|
20
17
|
#include <sstream>
|
|
18
|
+
#include <iostream>
|
|
21
19
|
#include <string>
|
|
20
|
+
#include <thread>
|
|
22
21
|
#include <vector>
|
|
23
22
|
#include <Windows.h>
|
|
24
23
|
#include <strsafe.h>
|
|
25
24
|
#include "path_util.h"
|
|
26
|
-
|
|
27
|
-
extern "C" void init(v8::Local<v8::Object>);
|
|
25
|
+
#include "conpty.h"
|
|
28
26
|
|
|
29
27
|
// Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17134
|
|
30
28
|
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
|
|
@@ -36,6 +34,7 @@ typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE
|
|
|
36
34
|
typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
|
|
37
35
|
typedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);
|
|
38
36
|
typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
|
|
37
|
+
typedef void (__stdcall *PFNRELEASEPSEUDOCONSOLE)(HPCON hpc);
|
|
39
38
|
|
|
40
39
|
#endif
|
|
41
40
|
|
|
@@ -46,40 +45,91 @@ struct pty_baton {
|
|
|
46
45
|
HPCON hpc;
|
|
47
46
|
|
|
48
47
|
HANDLE hShell;
|
|
49
|
-
HANDLE hWait;
|
|
50
|
-
Nan::Callback cb;
|
|
51
|
-
uv_async_t async;
|
|
52
|
-
uv_thread_t tid;
|
|
53
48
|
|
|
54
49
|
pty_baton(int _id, HANDLE _hIn, HANDLE _hOut, HPCON _hpc) : id(_id), hIn(_hIn), hOut(_hOut), hpc(_hpc) {};
|
|
55
50
|
};
|
|
56
51
|
|
|
57
|
-
static std::vector<pty_baton
|
|
52
|
+
static std::vector<std::unique_ptr<pty_baton>> ptyHandles;
|
|
58
53
|
static volatile LONG ptyCounter;
|
|
59
54
|
|
|
60
55
|
static pty_baton* get_pty_baton(int id) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
auto it = std::find_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
|
|
57
|
+
return ptyHandle->id == id;
|
|
58
|
+
});
|
|
59
|
+
if (it != ptyHandles.end()) {
|
|
60
|
+
return it->get();
|
|
66
61
|
}
|
|
67
62
|
return nullptr;
|
|
68
63
|
}
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
std::
|
|
72
|
-
return
|
|
65
|
+
static bool remove_pty_baton(int id) {
|
|
66
|
+
auto it = std::remove_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
|
|
67
|
+
return ptyHandle->id == id;
|
|
68
|
+
});
|
|
69
|
+
if (it != ptyHandles.end()) {
|
|
70
|
+
ptyHandles.erase(it);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
struct ExitEvent {
|
|
77
|
+
int exit_code = 0;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
void SetupExitCallback(Napi::Env env, Napi::Function cb, pty_baton* baton) {
|
|
81
|
+
std::thread *th = new std::thread;
|
|
82
|
+
// Don't use Napi::AsyncWorker which is limited by UV_THREADPOOL_SIZE.
|
|
83
|
+
auto tsfn = Napi::ThreadSafeFunction::New(
|
|
84
|
+
env,
|
|
85
|
+
cb, // JavaScript function called asynchronously
|
|
86
|
+
"SetupExitCallback_resource", // Name
|
|
87
|
+
0, // Unlimited queue
|
|
88
|
+
1, // Only one thread will use this initially
|
|
89
|
+
[th](Napi::Env) { // Finalizer used to clean threads up
|
|
90
|
+
th->join();
|
|
91
|
+
delete th;
|
|
92
|
+
});
|
|
93
|
+
*th = std::thread([tsfn = std::move(tsfn), baton] {
|
|
94
|
+
auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {
|
|
95
|
+
cb.Call({Napi::Number::New(env, exit_event->exit_code)});
|
|
96
|
+
delete exit_event;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
ExitEvent *exit_event = new ExitEvent;
|
|
100
|
+
// Wait for process to complete.
|
|
101
|
+
WaitForSingleObject(baton->hShell, INFINITE);
|
|
102
|
+
// Get process exit code.
|
|
103
|
+
GetExitCodeProcess(baton->hShell, (LPDWORD)(&exit_event->exit_code));
|
|
104
|
+
// Clean up handles
|
|
105
|
+
CloseHandle(baton->hShell);
|
|
106
|
+
assert(remove_pty_baton(baton->id));
|
|
107
|
+
|
|
108
|
+
auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
|
|
109
|
+
switch (status) {
|
|
110
|
+
case napi_closing:
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case napi_queue_full:
|
|
114
|
+
Napi::Error::Fatal("SetupExitCallback", "Queue was full");
|
|
115
|
+
|
|
116
|
+
case napi_ok:
|
|
117
|
+
if (tsfn.Release() != napi_ok) {
|
|
118
|
+
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.Release() failed");
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
default:
|
|
123
|
+
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.BlockingCall() failed");
|
|
124
|
+
}
|
|
125
|
+
});
|
|
73
126
|
}
|
|
74
127
|
|
|
75
|
-
|
|
128
|
+
Napi::Error errorWithCode(const Napi::CallbackInfo& info, const char* text) {
|
|
76
129
|
std::stringstream errorText;
|
|
77
130
|
errorText << text;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
Nan::ThrowError(errorText.str().c_str());
|
|
82
|
-
(*info).GetReturnValue().SetUndefined();
|
|
131
|
+
errorText << ", error code: " << GetLastError();
|
|
132
|
+
return Napi::Error::New(info.Env(), errorText.str());
|
|
83
133
|
}
|
|
84
134
|
|
|
85
135
|
// Returns a new server named pipe. It has not yet been connected.
|
|
@@ -111,20 +161,52 @@ bool createDataServerPipe(bool write,
|
|
|
111
161
|
return *hServer != INVALID_HANDLE_VALUE;
|
|
112
162
|
}
|
|
113
163
|
|
|
114
|
-
|
|
164
|
+
HANDLE LoadConptyDll(const Napi::CallbackInfo& info,
|
|
165
|
+
const bool useConptyDll)
|
|
166
|
+
{
|
|
167
|
+
if (!useConptyDll) {
|
|
168
|
+
return LoadLibraryExW(L"kernel32.dll", 0, 0);
|
|
169
|
+
}
|
|
170
|
+
wchar_t currentDir[MAX_PATH];
|
|
171
|
+
HMODULE hModule = GetModuleHandleA("conpty.node");
|
|
172
|
+
if (hModule == NULL) {
|
|
173
|
+
throw errorWithCode(info, "Failed to get conpty.node module handle");
|
|
174
|
+
}
|
|
175
|
+
DWORD result = GetModuleFileNameW(hModule, currentDir, MAX_PATH);
|
|
176
|
+
if (result == 0) {
|
|
177
|
+
throw errorWithCode(info, "Failed to get conpty.node module file name");
|
|
178
|
+
}
|
|
179
|
+
PathRemoveFileSpecW(currentDir);
|
|
180
|
+
wchar_t conptyDllPath[MAX_PATH];
|
|
181
|
+
PathCombineW(conptyDllPath, currentDir, L"conpty\\conpty.dll");
|
|
182
|
+
if (!path_util::file_exists(conptyDllPath)) {
|
|
183
|
+
std::wstring errorMessage = L"Cannot find conpty.dll at " + std::wstring(conptyDllPath);
|
|
184
|
+
std::string errorMessageStr = path_util::wstring_to_string(errorMessage);
|
|
185
|
+
throw errorWithCode(info, errorMessageStr.c_str());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return LoadLibraryW(conptyDllPath);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
HRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,
|
|
192
|
+
COORD size,
|
|
115
193
|
DWORD dwFlags,
|
|
116
194
|
HANDLE *phInput,
|
|
117
195
|
HANDLE *phOutput,
|
|
118
196
|
HPCON* phPC,
|
|
119
197
|
std::wstring& inName,
|
|
120
198
|
std::wstring& outName,
|
|
121
|
-
const std::wstring& pipeName
|
|
199
|
+
const std::wstring& pipeName,
|
|
200
|
+
const bool useConptyDll)
|
|
122
201
|
{
|
|
123
|
-
HANDLE hLibrary =
|
|
202
|
+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
|
|
203
|
+
DWORD error = GetLastError();
|
|
124
204
|
bool fLoadedDll = hLibrary != nullptr;
|
|
125
205
|
if (fLoadedDll)
|
|
126
206
|
{
|
|
127
|
-
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress(
|
|
207
|
+
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress(
|
|
208
|
+
(HMODULE)hLibrary,
|
|
209
|
+
useConptyDll ? "ConptyCreatePseudoConsole" : "CreatePseudoConsole");
|
|
128
210
|
if (pfnCreate)
|
|
129
211
|
{
|
|
130
212
|
if (phPC == NULL || phInput == NULL || phOutput == NULL)
|
|
@@ -151,6 +233,8 @@ HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
|
|
|
151
233
|
// We should fall back to winpty in this case.
|
|
152
234
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
153
235
|
}
|
|
236
|
+
} else {
|
|
237
|
+
throw errorWithCode(info, "Failed to load conpty.dll");
|
|
154
238
|
}
|
|
155
239
|
|
|
156
240
|
// Failed to find kernel32. This is realy unlikely - honestly no idea how
|
|
@@ -158,32 +242,34 @@ HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
|
|
|
158
242
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
159
243
|
}
|
|
160
244
|
|
|
161
|
-
static
|
|
162
|
-
|
|
245
|
+
static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
|
|
246
|
+
Napi::Env env(info.Env());
|
|
247
|
+
Napi::HandleScope scope(env);
|
|
163
248
|
|
|
164
|
-
|
|
249
|
+
Napi::Object marshal;
|
|
165
250
|
std::wstring inName, outName;
|
|
166
251
|
BOOL fSuccess = FALSE;
|
|
167
252
|
std::unique_ptr<wchar_t[]> mutableCommandline;
|
|
168
253
|
PROCESS_INFORMATION _piClient{};
|
|
169
254
|
|
|
170
|
-
if (info.Length() !=
|
|
171
|
-
!info[0]
|
|
172
|
-
!info[1]
|
|
173
|
-
!info[2]
|
|
174
|
-
!info[3]
|
|
175
|
-
!info[4]
|
|
176
|
-
!info[5]
|
|
177
|
-
|
|
178
|
-
|
|
255
|
+
if (info.Length() != 7 ||
|
|
256
|
+
!info[0].IsString() ||
|
|
257
|
+
!info[1].IsNumber() ||
|
|
258
|
+
!info[2].IsNumber() ||
|
|
259
|
+
!info[3].IsBoolean() ||
|
|
260
|
+
!info[4].IsString() ||
|
|
261
|
+
!info[5].IsBoolean() ||
|
|
262
|
+
!info[6].IsBoolean()) {
|
|
263
|
+
throw Napi::Error::New(env, "Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor, useConptyDll)");
|
|
179
264
|
}
|
|
180
265
|
|
|
181
|
-
const std::wstring filename(path_util::to_wstring(
|
|
182
|
-
const SHORT cols = static_cast<SHORT>(info[1]
|
|
183
|
-
const SHORT rows = static_cast<SHORT>(info[2]
|
|
184
|
-
const bool debug =
|
|
185
|
-
const std::wstring pipeName(path_util::to_wstring(
|
|
186
|
-
const bool inheritCursor =
|
|
266
|
+
const std::wstring filename(path_util::to_wstring(info[0].As<Napi::String>()));
|
|
267
|
+
const SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());
|
|
268
|
+
const SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
|
|
269
|
+
const bool debug = info[3].As<Napi::Boolean>().Value();
|
|
270
|
+
const std::wstring pipeName(path_util::to_wstring(info[4].As<Napi::String>()));
|
|
271
|
+
const bool inheritCursor = info[5].As<Napi::Boolean>().Value();
|
|
272
|
+
const bool useConptyDll = info[6].As<Napi::Boolean>().Value();
|
|
187
273
|
|
|
188
274
|
// use environment 'Path' variable to determine location of
|
|
189
275
|
// the relative path that we have recieved (e.g cmd.exe)
|
|
@@ -195,81 +281,50 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
195
281
|
}
|
|
196
282
|
|
|
197
283
|
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
|
|
198
|
-
std::
|
|
199
|
-
why
|
|
200
|
-
|
|
201
|
-
|
|
284
|
+
std::string why;
|
|
285
|
+
why += "File not found: ";
|
|
286
|
+
why += path_util::wstring_to_string(shellpath);
|
|
287
|
+
throw Napi::Error::New(env, why);
|
|
202
288
|
}
|
|
203
289
|
|
|
204
290
|
HANDLE hIn, hOut;
|
|
205
291
|
HPCON hpc;
|
|
206
|
-
HRESULT hr = CreateNamedPipesAndPseudoConsole({cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName);
|
|
292
|
+
HRESULT hr = CreateNamedPipesAndPseudoConsole(info, {cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName, useConptyDll);
|
|
207
293
|
|
|
208
294
|
// Restore default handling of ctrl+c
|
|
209
295
|
SetConsoleCtrlHandler(NULL, FALSE);
|
|
210
296
|
|
|
211
297
|
// Set return values
|
|
212
|
-
marshal =
|
|
298
|
+
marshal = Napi::Object::New(env);
|
|
213
299
|
|
|
214
300
|
if (SUCCEEDED(hr)) {
|
|
215
301
|
// We were able to instantiate a conpty
|
|
216
302
|
const int ptyId = InterlockedIncrement(&ptyCounter);
|
|
217
|
-
|
|
218
|
-
ptyHandles.
|
|
303
|
+
marshal.Set("pty", Napi::Number::New(env, ptyId));
|
|
304
|
+
ptyHandles.emplace_back(
|
|
305
|
+
std::make_unique<pty_baton>(ptyId, hIn, hOut, hpc));
|
|
219
306
|
} else {
|
|
220
|
-
|
|
221
|
-
return;
|
|
307
|
+
throw Napi::Error::New(env, "Cannot launch conpty");
|
|
222
308
|
}
|
|
223
309
|
|
|
224
|
-
std::string inNameStr
|
|
310
|
+
std::string inNameStr = path_util::wstring_to_string(inName);
|
|
225
311
|
if (inNameStr.empty()) {
|
|
226
|
-
|
|
227
|
-
return;
|
|
312
|
+
throw Napi::Error::New(env, "Failed to initialize conpty conin");
|
|
228
313
|
}
|
|
229
|
-
std::string outNameStr
|
|
314
|
+
std::string outNameStr = path_util::wstring_to_string(outName);
|
|
230
315
|
if (outNameStr.empty()) {
|
|
231
|
-
|
|
232
|
-
return;
|
|
316
|
+
throw Napi::Error::New(env, "Failed to initialize conpty conout");
|
|
233
317
|
}
|
|
234
318
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
319
|
+
marshal.Set("fd", Napi::Number::New(env, -1));
|
|
320
|
+
marshal.Set("conin", Napi::String::New(env, inNameStr));
|
|
321
|
+
marshal.Set("conout", Napi::String::New(env, outNameStr));
|
|
322
|
+
return marshal;
|
|
239
323
|
}
|
|
240
324
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
pty_baton *baton = static_cast<pty_baton*>(context);
|
|
245
|
-
|
|
246
|
-
// Fire OnProcessExit
|
|
247
|
-
uv_async_send(&baton->async);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
static void OnProcessExit(uv_async_t *async) {
|
|
251
|
-
Nan::HandleScope scope;
|
|
252
|
-
pty_baton *baton = static_cast<pty_baton*>(async->data);
|
|
253
|
-
|
|
254
|
-
UnregisterWait(baton->hWait);
|
|
255
|
-
|
|
256
|
-
// Get exit code
|
|
257
|
-
DWORD exitCode = 0;
|
|
258
|
-
GetExitCodeProcess(baton->hShell, &exitCode);
|
|
259
|
-
|
|
260
|
-
// Call function
|
|
261
|
-
v8::Local<v8::Value> args[1] = {
|
|
262
|
-
Nan::New<v8::Number>(exitCode)
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
Nan::AsyncResource asyncResource("node-pty.callback");
|
|
266
|
-
baton->cb.Call(1, args, &asyncResource);
|
|
267
|
-
// Clean up
|
|
268
|
-
baton->cb.Reset();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
static NAN_METHOD(PtyConnect) {
|
|
272
|
-
Nan::HandleScope scope;
|
|
325
|
+
static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
|
|
326
|
+
Napi::Env env(info.Env());
|
|
327
|
+
Napi::HandleScope scope(env);
|
|
273
328
|
|
|
274
329
|
// If we're working with conpty's we need to call ConnectNamedPipe here AFTER
|
|
275
330
|
// the Socket has attempted to connect to the other end, then actually
|
|
@@ -278,27 +333,27 @@ static NAN_METHOD(PtyConnect) {
|
|
|
278
333
|
std::stringstream errorText;
|
|
279
334
|
BOOL fSuccess = FALSE;
|
|
280
335
|
|
|
281
|
-
if (info.Length() !=
|
|
282
|
-
!info[0]
|
|
283
|
-
!info[1]
|
|
284
|
-
!info[2]
|
|
285
|
-
!info[3]
|
|
286
|
-
!info[4]
|
|
287
|
-
|
|
288
|
-
|
|
336
|
+
if (info.Length() != 6 ||
|
|
337
|
+
!info[0].IsNumber() ||
|
|
338
|
+
!info[1].IsString() ||
|
|
339
|
+
!info[2].IsString() ||
|
|
340
|
+
!info[3].IsArray() ||
|
|
341
|
+
!info[4].IsBoolean() ||
|
|
342
|
+
!info[5].IsFunction()) {
|
|
343
|
+
throw Napi::Error::New(env, "Usage: pty.connect(id, cmdline, cwd, env, useConptyDll, exitCallback)");
|
|
289
344
|
}
|
|
290
345
|
|
|
291
|
-
const int id = info[0]
|
|
292
|
-
const std::wstring cmdline(path_util::to_wstring(
|
|
293
|
-
const std::wstring cwd(path_util::to_wstring(
|
|
294
|
-
const
|
|
295
|
-
const
|
|
346
|
+
const int id = info[0].As<Napi::Number>().Int32Value();
|
|
347
|
+
const std::wstring cmdline(path_util::to_wstring(info[1].As<Napi::String>()));
|
|
348
|
+
const std::wstring cwd(path_util::to_wstring(info[2].As<Napi::String>()));
|
|
349
|
+
const Napi::Array envValues = info[3].As<Napi::Array>();
|
|
350
|
+
const bool useConptyDll = info[4].As<Napi::Boolean>().Value();
|
|
351
|
+
Napi::Function exitCallback = info[5].As<Napi::Function>();
|
|
296
352
|
|
|
297
353
|
// Fetch pty handle from ID and start process
|
|
298
354
|
pty_baton* handle = get_pty_baton(id);
|
|
299
355
|
if (!handle) {
|
|
300
|
-
|
|
301
|
-
return;
|
|
356
|
+
throw Napi::Error::New(env, "Invalid pty handle");
|
|
302
357
|
}
|
|
303
358
|
|
|
304
359
|
// Prepare command line
|
|
@@ -310,17 +365,17 @@ static NAN_METHOD(PtyConnect) {
|
|
|
310
365
|
hr = StringCchCopyW(mutableCwd.get(), cwd.length() + 1, cwd.c_str());
|
|
311
366
|
|
|
312
367
|
// Prepare environment
|
|
313
|
-
std::wstring
|
|
368
|
+
std::wstring envStr;
|
|
314
369
|
if (!envValues.IsEmpty()) {
|
|
315
|
-
std::
|
|
316
|
-
for(uint32_t i = 0; i < envValues
|
|
317
|
-
|
|
318
|
-
envBlock
|
|
370
|
+
std::wstring envBlock;
|
|
371
|
+
for(uint32_t i = 0; i < envValues.Length(); i++) {
|
|
372
|
+
envBlock += path_util::to_wstring(envValues.Get(i).As<Napi::String>());
|
|
373
|
+
envBlock += L'\0';
|
|
319
374
|
}
|
|
320
|
-
envBlock
|
|
321
|
-
|
|
375
|
+
envBlock += L'\0';
|
|
376
|
+
envStr = std::move(envBlock);
|
|
322
377
|
}
|
|
323
|
-
|
|
378
|
+
std::vector<wchar_t> envV(envStr.cbegin(), envStr.cend());
|
|
324
379
|
LPWSTR envArg = envV.empty() ? nullptr : envV.data();
|
|
325
380
|
|
|
326
381
|
ConnectNamedPipe(handle->hIn, nullptr);
|
|
@@ -341,7 +396,7 @@ static NAN_METHOD(PtyConnect) {
|
|
|
341
396
|
|
|
342
397
|
fSuccess = InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size);
|
|
343
398
|
if (!fSuccess) {
|
|
344
|
-
|
|
399
|
+
throw errorWithCode(info, "InitializeProcThreadAttributeList failed");
|
|
345
400
|
}
|
|
346
401
|
fSuccess = UpdateProcThreadAttribute(siEx.lpAttributeList,
|
|
347
402
|
0,
|
|
@@ -351,7 +406,7 @@ static NAN_METHOD(PtyConnect) {
|
|
|
351
406
|
NULL,
|
|
352
407
|
NULL);
|
|
353
408
|
if (!fSuccess) {
|
|
354
|
-
|
|
409
|
+
throw errorWithCode(info, "UpdateProcThreadAttribute failed");
|
|
355
410
|
}
|
|
356
411
|
|
|
357
412
|
PROCESS_INFORMATION piClient{};
|
|
@@ -368,49 +423,65 @@ static NAN_METHOD(PtyConnect) {
|
|
|
368
423
|
&piClient // lpProcessInformation
|
|
369
424
|
);
|
|
370
425
|
if (!fSuccess) {
|
|
371
|
-
|
|
426
|
+
throw errorWithCode(info, "Cannot create process");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
|
|
430
|
+
bool fLoadedDll = hLibrary != nullptr;
|
|
431
|
+
if (fLoadedDll)
|
|
432
|
+
{
|
|
433
|
+
PFNRELEASEPSEUDOCONSOLE const pfnReleasePseudoConsole = (PFNRELEASEPSEUDOCONSOLE)GetProcAddress(
|
|
434
|
+
(HMODULE)hLibrary, "ConptyReleasePseudoConsole");
|
|
435
|
+
if (pfnReleasePseudoConsole)
|
|
436
|
+
{
|
|
437
|
+
pfnReleasePseudoConsole(handle->hpc);
|
|
438
|
+
}
|
|
372
439
|
}
|
|
373
440
|
|
|
374
441
|
// Update handle
|
|
375
442
|
handle->hShell = piClient.hProcess;
|
|
376
|
-
handle->cb.Reset(exitCallback);
|
|
377
|
-
handle->async.data = handle;
|
|
378
443
|
|
|
379
|
-
//
|
|
380
|
-
|
|
444
|
+
// Close the thread handle to avoid resource leak
|
|
445
|
+
CloseHandle(piClient.hThread);
|
|
446
|
+
// Close the input read and output write handle of the pseudoconsole
|
|
447
|
+
CloseHandle(handle->hIn);
|
|
448
|
+
CloseHandle(handle->hOut);
|
|
381
449
|
|
|
382
|
-
|
|
383
|
-
RegisterWaitForSingleObject(&handle->hWait, piClient.hProcess, OnProcessExitWinEvent, (PVOID)handle, INFINITE, WT_EXECUTEONLYONCE);
|
|
450
|
+
SetupExitCallback(env, exitCallback, handle);
|
|
384
451
|
|
|
385
452
|
// Return
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
453
|
+
auto marshal = Napi::Object::New(env);
|
|
454
|
+
marshal.Set("pid", Napi::Number::New(env, piClient.dwProcessId));
|
|
455
|
+
return marshal;
|
|
389
456
|
}
|
|
390
457
|
|
|
391
|
-
static
|
|
392
|
-
|
|
458
|
+
static Napi::Value PtyResize(const Napi::CallbackInfo& info) {
|
|
459
|
+
Napi::Env env(info.Env());
|
|
460
|
+
Napi::HandleScope scope(env);
|
|
393
461
|
|
|
394
|
-
if (info.Length() !=
|
|
395
|
-
!info[0]
|
|
396
|
-
!info[1]
|
|
397
|
-
!info[2]
|
|
398
|
-
|
|
399
|
-
|
|
462
|
+
if (info.Length() != 4 ||
|
|
463
|
+
!info[0].IsNumber() ||
|
|
464
|
+
!info[1].IsNumber() ||
|
|
465
|
+
!info[2].IsNumber() ||
|
|
466
|
+
!info[3].IsBoolean()) {
|
|
467
|
+
throw Napi::Error::New(env, "Usage: pty.resize(id, cols, rows, useConptyDll)");
|
|
400
468
|
}
|
|
401
469
|
|
|
402
|
-
int id = info[0]
|
|
403
|
-
SHORT cols = static_cast<SHORT>(info[1]
|
|
404
|
-
SHORT rows = static_cast<SHORT>(info[2]
|
|
470
|
+
int id = info[0].As<Napi::Number>().Int32Value();
|
|
471
|
+
SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());
|
|
472
|
+
SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
|
|
473
|
+
const bool useConptyDll = info[3].As<Napi::Boolean>().Value();
|
|
405
474
|
|
|
406
475
|
const pty_baton* handle = get_pty_baton(id);
|
|
407
476
|
|
|
408
477
|
if (handle != nullptr) {
|
|
409
|
-
HANDLE hLibrary =
|
|
478
|
+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
|
|
410
479
|
bool fLoadedDll = hLibrary != nullptr;
|
|
411
480
|
if (fLoadedDll)
|
|
412
481
|
{
|
|
413
|
-
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress(
|
|
482
|
+
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress(
|
|
483
|
+
(HMODULE)hLibrary,
|
|
484
|
+
useConptyDll ? "ConptyResizePseudoConsole" : "ResizePseudoConsole");
|
|
414
485
|
if (pfnResizePseudoConsole)
|
|
415
486
|
{
|
|
416
487
|
COORD size = {cols, rows};
|
|
@@ -419,28 +490,37 @@ static NAN_METHOD(PtyResize) {
|
|
|
419
490
|
}
|
|
420
491
|
}
|
|
421
492
|
|
|
422
|
-
return
|
|
493
|
+
return env.Undefined();
|
|
423
494
|
}
|
|
424
495
|
|
|
425
|
-
static
|
|
426
|
-
|
|
496
|
+
static Napi::Value PtyClear(const Napi::CallbackInfo& info) {
|
|
497
|
+
Napi::Env env(info.Env());
|
|
498
|
+
Napi::HandleScope scope(env);
|
|
427
499
|
|
|
428
|
-
if (info.Length() !=
|
|
429
|
-
!info[0]
|
|
430
|
-
|
|
431
|
-
|
|
500
|
+
if (info.Length() != 2 ||
|
|
501
|
+
!info[0].IsNumber() ||
|
|
502
|
+
!info[1].IsBoolean()) {
|
|
503
|
+
throw Napi::Error::New(env, "Usage: pty.clear(id, useConptyDll)");
|
|
432
504
|
}
|
|
433
505
|
|
|
434
|
-
int id = info[0]
|
|
506
|
+
int id = info[0].As<Napi::Number>().Int32Value();
|
|
507
|
+
const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
|
|
508
|
+
|
|
509
|
+
// This API is only supported for conpty.dll as it was introduced in a later version of Windows.
|
|
510
|
+
// We could hook it up to point at >= a version of Windows only, but the future is conpty.dll
|
|
511
|
+
// anyway.
|
|
512
|
+
if (!useConptyDll) {
|
|
513
|
+
return env.Undefined();
|
|
514
|
+
}
|
|
435
515
|
|
|
436
516
|
const pty_baton* handle = get_pty_baton(id);
|
|
437
517
|
|
|
438
518
|
if (handle != nullptr) {
|
|
439
|
-
HANDLE hLibrary =
|
|
519
|
+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
|
|
440
520
|
bool fLoadedDll = hLibrary != nullptr;
|
|
441
521
|
if (fLoadedDll)
|
|
442
522
|
{
|
|
443
|
-
PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "
|
|
523
|
+
PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ConptyClearPseudoConsole");
|
|
444
524
|
if (pfnClearPseudoConsole)
|
|
445
525
|
{
|
|
446
526
|
pfnClearPseudoConsole(handle->hpc);
|
|
@@ -448,55 +528,56 @@ static NAN_METHOD(PtyClear) {
|
|
|
448
528
|
}
|
|
449
529
|
}
|
|
450
530
|
|
|
451
|
-
return
|
|
531
|
+
return env.Undefined();
|
|
452
532
|
}
|
|
453
533
|
|
|
454
|
-
static
|
|
455
|
-
|
|
534
|
+
static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
|
|
535
|
+
Napi::Env env(info.Env());
|
|
536
|
+
Napi::HandleScope scope(env);
|
|
456
537
|
|
|
457
|
-
if (info.Length() !=
|
|
458
|
-
!info[0]
|
|
459
|
-
|
|
460
|
-
|
|
538
|
+
if (info.Length() != 2 ||
|
|
539
|
+
!info[0].IsNumber() ||
|
|
540
|
+
!info[1].IsBoolean()) {
|
|
541
|
+
throw Napi::Error::New(env, "Usage: pty.kill(id, useConptyDll)");
|
|
461
542
|
}
|
|
462
543
|
|
|
463
|
-
int id = info[0]
|
|
544
|
+
int id = info[0].As<Napi::Number>().Int32Value();
|
|
545
|
+
const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
|
|
464
546
|
|
|
465
547
|
const pty_baton* handle = get_pty_baton(id);
|
|
466
548
|
|
|
467
549
|
if (handle != nullptr) {
|
|
468
|
-
HANDLE hLibrary =
|
|
550
|
+
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
|
|
469
551
|
bool fLoadedDll = hLibrary != nullptr;
|
|
470
552
|
if (fLoadedDll)
|
|
471
553
|
{
|
|
472
|
-
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress(
|
|
554
|
+
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress(
|
|
555
|
+
(HMODULE)hLibrary,
|
|
556
|
+
useConptyDll ? "ConptyClosePseudoConsole" : "ClosePseudoConsole");
|
|
473
557
|
if (pfnClosePseudoConsole)
|
|
474
558
|
{
|
|
475
559
|
pfnClosePseudoConsole(handle->hpc);
|
|
476
560
|
}
|
|
477
561
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
CloseHandle(handle->hIn);
|
|
482
|
-
CloseHandle(handle->hOut);
|
|
483
|
-
CloseHandle(handle->hShell);
|
|
562
|
+
if (useConptyDll) {
|
|
563
|
+
TerminateProcess(handle->hShell, 1);
|
|
564
|
+
}
|
|
484
565
|
}
|
|
485
566
|
|
|
486
|
-
return
|
|
567
|
+
return env.Undefined();
|
|
487
568
|
}
|
|
488
569
|
|
|
489
570
|
/**
|
|
490
571
|
* Init
|
|
491
572
|
*/
|
|
492
573
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
574
|
+
Napi::Object init(Napi::Env env, Napi::Object exports) {
|
|
575
|
+
exports.Set("startProcess", Napi::Function::New(env, PtyStartProcess));
|
|
576
|
+
exports.Set("connect", Napi::Function::New(env, PtyConnect));
|
|
577
|
+
exports.Set("resize", Napi::Function::New(env, PtyResize));
|
|
578
|
+
exports.Set("clear", Napi::Function::New(env, PtyClear));
|
|
579
|
+
exports.Set("kill", Napi::Function::New(env, PtyKill));
|
|
580
|
+
return exports;
|
|
500
581
|
};
|
|
501
582
|
|
|
502
|
-
|
|
583
|
+
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init);
|