mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-04 20:24:23 +02:00
Rewrite terminal.cpp
Rewrites the Terminal class as a GDExtension to be used directly in Godot without a terminal.gd proxy. Breaks a lot of things in its current state (e.g. signals and other functions have not be implemented yet), but does add support for transparent colors and true color inversion. It also seems to be about 4x faster (FPS-wise) than the old version with some basic stress testing. Old source code has been moved to a different directory to be copied over and/or rewritten piece by piece.
This commit is contained in:
parent
7d2e22530e
commit
a849423096
29 changed files with 1431 additions and 857 deletions
433
addons/godot_xterm/native/src_old/node_pty/win/conpty.cc
Normal file
433
addons/godot_xterm/native/src_old/node_pty/win/conpty.cc
Normal file
|
@ -0,0 +1,433 @@
|
|||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
* Copyright (c) 2021, Leroy Hopson (MIT License).
|
||||
*
|
||||
* pty.cc:
|
||||
* This file is responsible for starting processes
|
||||
* with pseudo-terminal file descriptors.
|
||||
*/
|
||||
|
||||
// node versions lower than 10 define this as 0x502 which disables many of the
|
||||
// definitions needed to compile
|
||||
#include <node_version.h>
|
||||
#if NODE_MODULE_VERSION <= 57
|
||||
#define _WIN32_WINNT 0x600
|
||||
#endif
|
||||
|
||||
#include "path_util.h"
|
||||
#include <Shlwapi.h> // PathCombine, PathIsRelative
|
||||
#include <Windows.h>
|
||||
#include <iostream>
|
||||
#include <nan.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <strsafe.h>
|
||||
#include <vector>
|
||||
|
||||
extern "C" void init(v8::Local<v8::Object>);
|
||||
|
||||
// Taken from the RS5 Windows SDK, but redefined here in case we're targeting <=
|
||||
// 17134
|
||||
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
|
||||
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
|
||||
ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
|
||||
|
||||
typedef VOID *HPCON;
|
||||
typedef HRESULT(__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn,
|
||||
HANDLE hOut, DWORD dwFlags,
|
||||
HPCON *phpcon);
|
||||
typedef HRESULT(__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
|
||||
typedef void(__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
|
||||
|
||||
#endif
|
||||
|
||||
struct pty_baton {
|
||||
int id;
|
||||
HANDLE hIn;
|
||||
HANDLE hOut;
|
||||
HPCON hpc;
|
||||
|
||||
HANDLE hShell;
|
||||
HANDLE hWait;
|
||||
Nan::Callback cb;
|
||||
uv_async_t async;
|
||||
uv_thread_t tid;
|
||||
|
||||
pty_baton(int _id, HANDLE _hIn, HANDLE _hOut, HPCON _hpc)
|
||||
: id(_id), hIn(_hIn), hOut(_hOut), hpc(_hpc){};
|
||||
};
|
||||
|
||||
static std::vector<pty_baton *> ptyHandles;
|
||||
static volatile LONG ptyCounter;
|
||||
|
||||
static pty_baton *get_pty_baton(int id) {
|
||||
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
||||
pty_baton *ptyHandle = ptyHandles[i];
|
||||
if (ptyHandle->id == id) {
|
||||
return ptyHandle;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vectorFromString(const std::basic_string<T> &str) {
|
||||
return std::vector<T>(str.begin(), str.end());
|
||||
}
|
||||
|
||||
void throwNanError(const Nan::FunctionCallbackInfo<v8::Value> *info,
|
||||
const char *text, const bool getLastError) {
|
||||
std::stringstream errorText;
|
||||
errorText << text;
|
||||
if (getLastError) {
|
||||
errorText << ", error code: " << GetLastError();
|
||||
}
|
||||
Nan::ThrowError(errorText.str().c_str());
|
||||
(*info).GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
// Returns a new server named pipe. It has not yet been connected.
|
||||
bool createDataServerPipe(bool write, std::wstring kind, HANDLE *hServer,
|
||||
std::wstring &name, const std::wstring &pipeName) {
|
||||
*hServer = INVALID_HANDLE_VALUE;
|
||||
|
||||
name = L"\\\\.\\pipe\\" + pipeName + L"-" + kind;
|
||||
|
||||
const DWORD winOpenMode =
|
||||
PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE /* | FILE_FLAG_OVERLAPPED */;
|
||||
|
||||
SECURITY_ATTRIBUTES sa = {};
|
||||
sa.nLength = sizeof(sa);
|
||||
|
||||
*hServer = CreateNamedPipeW(name.c_str(),
|
||||
/*dwOpenMode=*/winOpenMode,
|
||||
/*dwPipeMode=*/PIPE_TYPE_BYTE |
|
||||
PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
/*nMaxInstances=*/1,
|
||||
/*nOutBufferSize=*/0,
|
||||
/*nInBufferSize=*/0,
|
||||
/*nDefaultTimeOut=*/30000, &sa);
|
||||
|
||||
return *hServer != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
HRESULT CreateNamedPipesAndPseudoConsole(COORD size, DWORD dwFlags,
|
||||
HANDLE *phInput, HANDLE *phOutput,
|
||||
HPCON *phPC, std::wstring &inName,
|
||||
std::wstring &outName,
|
||||
const std::wstring &pipeName) {
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
bool fLoadedDll = hLibrary != nullptr;
|
||||
if (fLoadedDll) {
|
||||
PFNCREATEPSEUDOCONSOLE const pfnCreate =
|
||||
(PFNCREATEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary,
|
||||
"CreatePseudoConsole");
|
||||
if (pfnCreate) {
|
||||
if (phPC == NULL || phInput == NULL || phOutput == NULL) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
bool success =
|
||||
createDataServerPipe(true, L"in", phInput, inName, pipeName);
|
||||
if (!success) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
success =
|
||||
createDataServerPipe(false, L"out", phOutput, outName, pipeName);
|
||||
if (!success) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
return pfnCreate(size, *phInput, *phOutput, dwFlags, phPC);
|
||||
} else {
|
||||
// Failed to find CreatePseudoConsole in kernel32. This is likely because
|
||||
// the user is not running a build of Windows that supports that API.
|
||||
// We should fall back to winpty in this case.
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to find kernel32. This is realy unlikely - honestly no idea how
|
||||
// this is even possible to hit. But if it does happen, fall back to
|
||||
// winpty.
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyStartProcess) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
v8::Local<v8::Object> marshal;
|
||||
std::wstring inName, outName;
|
||||
BOOL fSuccess = FALSE;
|
||||
std::unique_ptr<wchar_t[]> mutableCommandline;
|
||||
PROCESS_INFORMATION _piClient{};
|
||||
|
||||
if (info.Length() != 6 || !info[0]->IsString() || !info[1]->IsNumber() ||
|
||||
!info[2]->IsNumber() || !info[3]->IsBoolean() || !info[4]->IsString() ||
|
||||
!info[5]->IsBoolean()) {
|
||||
Nan::ThrowError("Usage: pty.startProcess(file, cols, rows, debug, "
|
||||
"pipeName, inheritCursor)");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::wstring filename(path_util::to_wstring(Nan::Utf8String(info[0])));
|
||||
const SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
const SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
const bool debug = Nan::To<bool>(info[3]).FromJust();
|
||||
const std::wstring pipeName(path_util::to_wstring(Nan::Utf8String(info[4])));
|
||||
const bool inheritCursor = Nan::To<bool>(info[5]).FromJust();
|
||||
|
||||
// use environment 'Path' variable to determine location of
|
||||
// the relative path that we have recieved (e.g cmd.exe)
|
||||
std::wstring shellpath;
|
||||
if (::PathIsRelativeW(filename.c_str())) {
|
||||
shellpath = path_util::get_shell_path(filename.c_str());
|
||||
} else {
|
||||
shellpath = filename;
|
||||
}
|
||||
|
||||
std::string shellpath_(shellpath.begin(), shellpath.end());
|
||||
|
||||
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
|
||||
std::stringstream why;
|
||||
why << "File not found: " << shellpath_;
|
||||
Nan::ThrowError(why.str().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE hIn, hOut;
|
||||
HPCON hpc;
|
||||
HRESULT hr = CreateNamedPipesAndPseudoConsole(
|
||||
{cols, rows}, inheritCursor ? 1 /*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0,
|
||||
&hIn, &hOut, &hpc, inName, outName, pipeName);
|
||||
|
||||
// Restore default handling of ctrl+c
|
||||
SetConsoleCtrlHandler(NULL, FALSE);
|
||||
|
||||
// Set return values
|
||||
marshal = Nan::New<v8::Object>();
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// We were able to instantiate a conpty
|
||||
const int ptyId = InterlockedIncrement(&ptyCounter);
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pty").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(ptyId));
|
||||
ptyHandles.insert(ptyHandles.end(), new pty_baton(ptyId, hIn, hOut, hpc));
|
||||
} else {
|
||||
Nan::ThrowError("Cannot launch conpty");
|
||||
return;
|
||||
}
|
||||
|
||||
Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(-1));
|
||||
{
|
||||
std::string coninPipeNameStr(inName.begin(), inName.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(),
|
||||
Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
|
||||
|
||||
std::string conoutPipeNameStr(outName.begin(), outName.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(),
|
||||
Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
|
||||
}
|
||||
info.GetReturnValue().Set(marshal);
|
||||
}
|
||||
|
||||
VOID CALLBACK OnProcessExitWinEvent(_In_ PVOID context,
|
||||
_In_ BOOLEAN TimerOrWaitFired) {
|
||||
pty_baton *baton = static_cast<pty_baton *>(context);
|
||||
|
||||
// Fire OnProcessExit
|
||||
uv_async_send(&baton->async);
|
||||
}
|
||||
|
||||
static void OnProcessExit(uv_async_t *async) {
|
||||
Nan::HandleScope scope;
|
||||
pty_baton *baton = static_cast<pty_baton *>(async->data);
|
||||
|
||||
UnregisterWait(baton->hWait);
|
||||
|
||||
// Get exit code
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeProcess(baton->hShell, &exitCode);
|
||||
|
||||
// Call function
|
||||
v8::Local<v8::Value> args[1] = {Nan::New<v8::Number>(exitCode)};
|
||||
|
||||
Nan::AsyncResource asyncResource("node-pty.callback");
|
||||
baton->cb.Call(1, args, &asyncResource);
|
||||
// Clean up
|
||||
baton->cb.Reset();
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyConnect) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
// If we're working with conpty's we need to call ConnectNamedPipe here AFTER
|
||||
// the Socket has attempted to connect to the other end, then actually
|
||||
// spawn the process here.
|
||||
|
||||
std::stringstream errorText;
|
||||
BOOL fSuccess = FALSE;
|
||||
|
||||
if (info.Length() != 5 || !info[0]->IsNumber() || !info[1]->IsString() ||
|
||||
!info[2]->IsString() || !info[3]->IsArray() || !info[4]->IsFunction()) {
|
||||
Nan::ThrowError("Usage: pty.connect(id, cmdline, cwd, env, exitCallback)");
|
||||
return;
|
||||
}
|
||||
|
||||
const int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
const std::wstring cmdline(path_util::to_wstring(Nan::Utf8String(info[1])));
|
||||
const std::wstring cwd(path_util::to_wstring(Nan::Utf8String(info[2])));
|
||||
const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
|
||||
const v8::Local<v8::Function> exitCallback =
|
||||
v8::Local<v8::Function>::Cast(info[4]);
|
||||
|
||||
// Prepare command line
|
||||
std::unique_ptr<wchar_t[]> mutableCommandline =
|
||||
std::make_unique<wchar_t[]>(cmdline.length() + 1);
|
||||
HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1,
|
||||
cmdline.c_str());
|
||||
|
||||
// Prepare cwd
|
||||
std::unique_ptr<wchar_t[]> mutableCwd =
|
||||
std::make_unique<wchar_t[]>(cwd.length() + 1);
|
||||
hr = StringCchCopyW(mutableCwd.get(), cwd.length() + 1, cwd.c_str());
|
||||
|
||||
// Prepare environment
|
||||
std::wstring env;
|
||||
if (!envValues.IsEmpty()) {
|
||||
std::wstringstream envBlock;
|
||||
for (uint32_t i = 0; i < envValues->Length(); i++) {
|
||||
std::wstring envValue(path_util::to_wstring(
|
||||
Nan::Utf8String(Nan::Get(envValues, i).ToLocalChecked())));
|
||||
envBlock << envValue << L'\0';
|
||||
}
|
||||
envBlock << L'\0';
|
||||
env = envBlock.str();
|
||||
}
|
||||
auto envV = vectorFromString(env);
|
||||
LPWSTR envArg = envV.empty() ? nullptr : envV.data();
|
||||
|
||||
// Fetch pty handle from ID and start process
|
||||
pty_baton *handle = get_pty_baton(id);
|
||||
|
||||
BOOL success = ConnectNamedPipe(handle->hIn, nullptr);
|
||||
success = ConnectNamedPipe(handle->hOut, nullptr);
|
||||
|
||||
// Attach the pseudoconsole to the client application we're creating
|
||||
STARTUPINFOEXW siEx{0};
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
||||
siEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||
siEx.StartupInfo.hStdError = nullptr;
|
||||
siEx.StartupInfo.hStdInput = nullptr;
|
||||
siEx.StartupInfo.hStdOutput = nullptr;
|
||||
|
||||
SIZE_T size = 0;
|
||||
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
|
||||
BYTE *attrList = new BYTE[size];
|
||||
siEx.lpAttributeList =
|
||||
reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
|
||||
|
||||
fSuccess =
|
||||
InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size);
|
||||
if (!fSuccess) {
|
||||
return throwNanError(&info, "InitializeProcThreadAttributeList failed",
|
||||
true);
|
||||
}
|
||||
fSuccess = UpdateProcThreadAttribute(siEx.lpAttributeList, 0,
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
|
||||
handle->hpc, sizeof(HPCON), NULL, NULL);
|
||||
if (!fSuccess) {
|
||||
return throwNanError(&info, "UpdateProcThreadAttribute failed", true);
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION piClient{};
|
||||
fSuccess = !!CreateProcessW(
|
||||
nullptr, mutableCommandline.get(),
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
false, // bInheritHandles VERY IMPORTANT that this is false
|
||||
EXTENDED_STARTUPINFO_PRESENT |
|
||||
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
|
||||
envArg, // lpEnvironment
|
||||
mutableCwd.get(), // lpCurrentDirectory
|
||||
&siEx.StartupInfo, // lpStartupInfo
|
||||
&piClient // lpProcessInformation
|
||||
);
|
||||
if (!fSuccess) {
|
||||
return throwNanError(&info, "Cannot create process", true);
|
||||
}
|
||||
|
||||
// Update handle
|
||||
handle->hShell = piClient.hProcess;
|
||||
handle->cb.Reset(exitCallback);
|
||||
handle->async.data = handle;
|
||||
|
||||
// Setup OnProcessExit callback
|
||||
uv_async_init(uv_default_loop(), &handle->async, OnProcessExit);
|
||||
|
||||
// Setup Windows wait for process exit event
|
||||
RegisterWaitForSingleObject(&handle->hWait, piClient.hProcess,
|
||||
OnProcessExitWinEvent, (PVOID)handle, INFINITE,
|
||||
WT_EXECUTEONLYONCE);
|
||||
|
||||
// Return
|
||||
v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(piClient.dwProcessId));
|
||||
info.GetReturnValue().Set(marshal);
|
||||
}
|
||||
|
||||
void ConPTY::resize(int id, int cols, int rows) {
|
||||
// SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
// SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
const pty_baton *handle = get_pty_baton(id);
|
||||
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
bool fLoadedDll = hLibrary != nullptr;
|
||||
if (fLoadedDll) {
|
||||
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole =
|
||||
(PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary,
|
||||
"ResizePseudoConsole");
|
||||
if (pfnResizePseudoConsole) {
|
||||
COORD size = {cols, rows};
|
||||
pfnResizePseudoConsole(handle->hpc, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConPTY::kill(int id) {
|
||||
const pty_baton *handle = get_pty_baton(id);
|
||||
|
||||
HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
|
||||
bool fLoadedDll = hLibrary != nullptr;
|
||||
if (fLoadedDll) {
|
||||
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole =
|
||||
(PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary,
|
||||
"ClosePseudoConsole");
|
||||
if (pfnClosePseudoConsole) {
|
||||
pfnClosePseudoConsole(handle->hpc);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(handle->hShell);
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
|
||||
void ConPTY::_register_methods() {
|
||||
register_method("_init", &ConPTY::_init);
|
||||
register_method("start_process", &ConPTY::start_process);
|
||||
register_method("connect_to_named_pipe", &ConPTY::connect_to_named_pipe);
|
||||
register_method("resize", &ConPTY::resize);
|
||||
register_method("kill", &ConPTY::kill);
|
||||
};
|
||||
|
||||
void ConPTY::_init() {}
|
36
addons/godot_xterm/native/src_old/node_pty/win/conpty.h
Normal file
36
addons/godot_xterm/native/src_old/node_pty/win/conpty.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
// SPDX-FileCopyrightText: 2021 Leroy Hopson <godot-xterm@leroy.geek.nz>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef GODOT_XTERM_CONPTY_H
|
||||
#define GODOT_XTERM_CONPTY_H
|
||||
|
||||
#include <godot_cpp/classes/ref_counted.hpp>
|
||||
#include <godot_cpp/variant/callable.hpp>
|
||||
|
||||
namespace godot {
|
||||
|
||||
class ConPTY : public RefCounted {
|
||||
GDCLASS(ConPTY, RefCounted)
|
||||
|
||||
public:
|
||||
// Array fork(String file,
|
||||
// int _ignored, /* FIXME: For some reason Pipe throws
|
||||
// ENOTSOCK in read callback if args (or another
|
||||
// non-empty, non-zero) value is in this position. */
|
||||
// PoolStringArray args, PoolStringArray env, String cwd, int
|
||||
// cols, int rows, int uid, int gid, bool utf8, Ref<FuncRef>
|
||||
// on_exit);
|
||||
// Array open(int cols, int rows);
|
||||
void resize(int id, int cols, int rows);
|
||||
void kill(int id);
|
||||
// String process(int fd, String tty);
|
||||
|
||||
void _init();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
};
|
||||
|
||||
} // namespace godot
|
||||
|
||||
#endif // GODOT_XTERM_CONPTY_H
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Copyright (c) 2019, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
#include <nan.h>
|
||||
#include <windows.h>
|
||||
|
||||
static NAN_METHOD(ApiConsoleProcessList) {
|
||||
if (info.Length() != 1 || !info[0]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: getConsoleProcessList(shellPid)");
|
||||
return;
|
||||
}
|
||||
|
||||
const SHORT pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
if (!FreeConsole()) {
|
||||
Nan::ThrowError("FreeConsole failed");
|
||||
}
|
||||
if (!AttachConsole(pid)) {
|
||||
Nan::ThrowError("AttachConsole failed");
|
||||
}
|
||||
auto processList = std::vector<DWORD>(64);
|
||||
auto processCount =
|
||||
GetConsoleProcessList(&processList[0], processList.size());
|
||||
if (processList.size() < processCount) {
|
||||
processList.resize(processCount);
|
||||
processCount = GetConsoleProcessList(&processList[0], processList.size());
|
||||
}
|
||||
FreeConsole();
|
||||
|
||||
v8::Local<v8::Array> result = Nan::New<v8::Array>();
|
||||
for (DWORD i = 0; i < processCount; i++) {
|
||||
Nan::Set(result, i, Nan::New<v8::Number>(processList[i]));
|
||||
}
|
||||
info.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
extern "C" void init(v8::Local<v8::Object> target) {
|
||||
Nan::HandleScope scope;
|
||||
Nan::SetMethod(target, "getConsoleProcessList", ApiConsoleProcessList);
|
||||
};
|
||||
|
||||
NODE_MODULE(pty, init);
|
73
addons/godot_xterm/native/src_old/node_pty/win/path_util.cc
Normal file
73
addons/godot_xterm/native/src_old/node_pty/win/path_util.cc
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
#include <Shlwapi.h> // PathCombine
|
||||
#include <nan.h>
|
||||
|
||||
#include "path_util.h"
|
||||
|
||||
namespace path_util {
|
||||
|
||||
const wchar_t *to_wstring(const Nan::Utf8String &str) {
|
||||
const char *bytes = *str;
|
||||
unsigned int sizeOfStr = MultiByteToWideChar(CP_UTF8, 0, bytes, -1, NULL, 0);
|
||||
wchar_t *output = new wchar_t[sizeOfStr];
|
||||
MultiByteToWideChar(CP_UTF8, 0, bytes, -1, output, sizeOfStr);
|
||||
return output;
|
||||
}
|
||||
|
||||
bool file_exists(std::wstring filename) {
|
||||
DWORD attr = ::GetFileAttributesW(filename.c_str());
|
||||
if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// cmd.exe -> C:\Windows\system32\cmd.exe
|
||||
std::wstring get_shell_path(std::wstring filename) {
|
||||
std::wstring shellpath;
|
||||
|
||||
if (file_exists(filename)) {
|
||||
return shellpath;
|
||||
}
|
||||
|
||||
wchar_t buffer_[MAX_ENV];
|
||||
int read = ::GetEnvironmentVariableW(L"Path", buffer_, MAX_ENV);
|
||||
if (!read) {
|
||||
return shellpath;
|
||||
}
|
||||
|
||||
std::wstring delimiter = L";";
|
||||
size_t pos = 0;
|
||||
std::vector<std::wstring> paths;
|
||||
std::wstring buffer(buffer_);
|
||||
while ((pos = buffer.find(delimiter)) != std::wstring::npos) {
|
||||
paths.push_back(buffer.substr(0, pos));
|
||||
buffer.erase(0, pos + delimiter.length());
|
||||
}
|
||||
|
||||
const wchar_t *filename_ = filename.c_str();
|
||||
|
||||
for (int i = 0; i < paths.size(); ++i) {
|
||||
std::wstring path = paths[i];
|
||||
wchar_t searchPath[MAX_PATH];
|
||||
::PathCombineW(searchPath, const_cast<wchar_t *>(path.c_str()), filename_);
|
||||
|
||||
if (searchPath == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists(searchPath)) {
|
||||
shellpath = searchPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return shellpath;
|
||||
}
|
||||
|
||||
} // namespace path_util
|
22
addons/godot_xterm/native/src_old/node_pty/win/path_util.h
Normal file
22
addons/godot_xterm/native/src_old/node_pty/win/path_util.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*/
|
||||
|
||||
#ifndef NODE_PTY_PATH_UTIL_H_
|
||||
#define NODE_PTY_PATH_UTIL_H_
|
||||
|
||||
#include <nan.h>
|
||||
|
||||
#define MAX_ENV 65536
|
||||
|
||||
namespace path_util {
|
||||
|
||||
const wchar_t *to_wstring(const Nan::Utf8String &str);
|
||||
bool file_exists(std::wstring filename);
|
||||
std::wstring get_shell_path(std::wstring filename);
|
||||
|
||||
} // namespace path_util
|
||||
|
||||
#endif // NODE_PTY_PATH_UTIL_H_
|
320
addons/godot_xterm/native/src_old/node_pty/win/winpty.cc
Normal file
320
addons/godot_xterm/native/src_old/node_pty/win/winpty.cc
Normal file
|
@ -0,0 +1,320 @@
|
|||
/**
|
||||
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
||||
* Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
*
|
||||
* pty.cc:
|
||||
* This file is responsible for starting processes
|
||||
* with pseudo-terminal file descriptors.
|
||||
*/
|
||||
|
||||
#include <Shlwapi.h> // PathCombine, PathIsRelative
|
||||
#include <iostream>
|
||||
#include <nan.h>
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <winpty.h>
|
||||
|
||||
#include "path_util.h"
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
extern "C" void init(v8::Local<v8::Object>);
|
||||
|
||||
#define WINPTY_DBG_VARIABLE TEXT("WINPTYDBG")
|
||||
|
||||
/**
|
||||
* winpty
|
||||
*/
|
||||
static std::vector<winpty_t *> ptyHandles;
|
||||
static volatile LONG ptyCounter;
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
static winpty_t *get_pipe_handle(int handle) {
|
||||
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
||||
winpty_t *ptyHandle = ptyHandles[i];
|
||||
int current = (int)winpty_agent_process(ptyHandle);
|
||||
if (current == handle) {
|
||||
return ptyHandle;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool remove_pipe_handle(int handle) {
|
||||
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
||||
winpty_t *ptyHandle = ptyHandles[i];
|
||||
if ((int)winpty_agent_process(ptyHandle) == handle) {
|
||||
winpty_free(ptyHandle);
|
||||
ptyHandles.erase(ptyHandles.begin() + i);
|
||||
ptyHandle = nullptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void throw_winpty_error(const char *generalMsg, winpty_error_ptr_t error_ptr) {
|
||||
std::stringstream why;
|
||||
std::wstring msg(winpty_error_msg(error_ptr));
|
||||
std::string msg_(msg.begin(), msg.end());
|
||||
why << generalMsg << ": " << msg_;
|
||||
Nan::ThrowError(why.str().c_str());
|
||||
winpty_error_free(error_ptr);
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyGetExitCode) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 1 || !info[0]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.getExitCode(pidHandle)");
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeProcess(
|
||||
(HANDLE)info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust(),
|
||||
&exitCode);
|
||||
|
||||
info.GetReturnValue().Set(Nan::New<v8::Number>(exitCode));
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyGetProcessList) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 1 || !info[0]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.getProcessList(pid)");
|
||||
return;
|
||||
}
|
||||
|
||||
int pid = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
winpty_t *pc = get_pipe_handle(pid);
|
||||
if (pc == nullptr) {
|
||||
info.GetReturnValue().Set(Nan::New<v8::Array>(0));
|
||||
return;
|
||||
}
|
||||
int processList[64];
|
||||
const int processCount = 64;
|
||||
int actualCount =
|
||||
winpty_get_console_process_list(pc, processList, processCount, nullptr);
|
||||
|
||||
v8::Local<v8::Array> result = Nan::New<v8::Array>(actualCount);
|
||||
for (uint32_t i = 0; i < actualCount; i++) {
|
||||
Nan::Set(result, i, Nan::New<v8::Number>(processList[i]));
|
||||
}
|
||||
info.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyStartProcess) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 7 || !info[0]->IsString() || !info[1]->IsString() ||
|
||||
!info[2]->IsArray() || !info[3]->IsString() || !info[4]->IsNumber() ||
|
||||
!info[5]->IsNumber() || !info[6]->IsBoolean()) {
|
||||
Nan::ThrowError(
|
||||
"Usage: pty.startProcess(file, cmdline, env, cwd, cols, rows, debug)");
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream why;
|
||||
|
||||
const wchar_t *filename = path_util::to_wstring(Nan::Utf8String(info[0]));
|
||||
const wchar_t *cmdline = path_util::to_wstring(Nan::Utf8String(info[1]));
|
||||
const wchar_t *cwd = path_util::to_wstring(Nan::Utf8String(info[3]));
|
||||
|
||||
// create environment block
|
||||
std::wstring env;
|
||||
const v8::Local<v8::Array> envValues = v8::Local<v8::Array>::Cast(info[2]);
|
||||
if (!envValues.IsEmpty()) {
|
||||
|
||||
std::wstringstream envBlock;
|
||||
|
||||
for (uint32_t i = 0; i < envValues->Length(); i++) {
|
||||
std::wstring envValue(path_util::to_wstring(
|
||||
Nan::Utf8String(Nan::Get(envValues, i).ToLocalChecked())));
|
||||
envBlock << envValue << L'\0';
|
||||
}
|
||||
|
||||
env = envBlock.str();
|
||||
}
|
||||
|
||||
// use environment 'Path' variable to determine location of
|
||||
// the relative path that we have recieved (e.g cmd.exe)
|
||||
std::wstring shellpath;
|
||||
if (::PathIsRelativeW(filename)) {
|
||||
shellpath = path_util::get_shell_path(filename);
|
||||
} else {
|
||||
shellpath = filename;
|
||||
}
|
||||
|
||||
std::string shellpath_(shellpath.begin(), shellpath.end());
|
||||
|
||||
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
|
||||
why << "File not found: " << shellpath_;
|
||||
Nan::ThrowError(why.str().c_str());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int cols = info[4]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
int rows = info[5]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
bool debug = Nan::To<bool>(info[6]).FromJust();
|
||||
|
||||
// Enable/disable debugging
|
||||
SetEnvironmentVariable(WINPTY_DBG_VARIABLE,
|
||||
debug ? "1" : NULL); // NULL = deletes variable
|
||||
|
||||
// Create winpty config
|
||||
winpty_error_ptr_t error_ptr = nullptr;
|
||||
winpty_config_t *winpty_config = winpty_config_new(0, &error_ptr);
|
||||
if (winpty_config == nullptr) {
|
||||
throw_winpty_error("Error creating WinPTY config", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Set pty size on config
|
||||
winpty_config_set_initial_size(winpty_config, cols, rows);
|
||||
|
||||
// Start the pty agent
|
||||
winpty_t *pc = winpty_open(winpty_config, &error_ptr);
|
||||
winpty_config_free(winpty_config);
|
||||
if (pc == nullptr) {
|
||||
throw_winpty_error("Error launching WinPTY agent", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Save pty struct for later use
|
||||
ptyHandles.insert(ptyHandles.end(), pc);
|
||||
|
||||
// Create winpty spawn config
|
||||
winpty_spawn_config_t *config = winpty_spawn_config_new(
|
||||
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, shellpath.c_str(), cmdline, cwd,
|
||||
env.c_str(), &error_ptr);
|
||||
if (config == nullptr) {
|
||||
throw_winpty_error("Error creating WinPTY spawn config", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Spawn the new process
|
||||
HANDLE handle = nullptr;
|
||||
BOOL spawnSuccess =
|
||||
winpty_spawn(pc, config, &handle, nullptr, nullptr, &error_ptr);
|
||||
winpty_spawn_config_free(config);
|
||||
if (!spawnSuccess) {
|
||||
throw_winpty_error("Unable to start terminal process", error_ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
winpty_error_free(error_ptr);
|
||||
|
||||
// Set return values
|
||||
v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
|
||||
Nan::Set(marshal, Nan::New<v8::String>("innerPid").ToLocalChecked(),
|
||||
Nan::New<v8::Number>((int)GetProcessId(handle)));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("innerPidHandle").ToLocalChecked(),
|
||||
Nan::New<v8::Number>((int)handle));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(),
|
||||
Nan::New<v8::Number>((int)winpty_agent_process(pc)));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("pty").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(InterlockedIncrement(&ptyCounter)));
|
||||
Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(),
|
||||
Nan::New<v8::Number>(-1));
|
||||
{
|
||||
LPCWSTR coninPipeName = winpty_conin_name(pc);
|
||||
std::wstring coninPipeNameWStr(coninPipeName);
|
||||
std::string coninPipeNameStr(coninPipeNameWStr.begin(),
|
||||
coninPipeNameWStr.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(),
|
||||
Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
|
||||
LPCWSTR conoutPipeName = winpty_conout_name(pc);
|
||||
std::wstring conoutPipeNameWStr(conoutPipeName);
|
||||
std::string conoutPipeNameStr(conoutPipeNameWStr.begin(),
|
||||
conoutPipeNameWStr.end());
|
||||
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(),
|
||||
Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
|
||||
}
|
||||
info.GetReturnValue().Set(marshal);
|
||||
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
delete filename;
|
||||
delete cmdline;
|
||||
delete cwd;
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyResize) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 3 || !info[0]->IsNumber() || !info[1]->IsNumber() ||
|
||||
!info[2]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.resize(pid, cols, rows)");
|
||||
return;
|
||||
}
|
||||
|
||||
int handle = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
int cols = info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
int rows = info[2]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
winpty_t *pc = get_pipe_handle(handle);
|
||||
|
||||
if (pc == nullptr) {
|
||||
Nan::ThrowError("The pty doesn't appear to exist");
|
||||
return;
|
||||
}
|
||||
BOOL success = winpty_set_size(pc, cols, rows, nullptr);
|
||||
if (!success) {
|
||||
Nan::ThrowError("The pty could not be resized");
|
||||
return;
|
||||
}
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
static NAN_METHOD(PtyKill) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
if (info.Length() != 2 || !info[0]->IsNumber() || !info[1]->IsNumber()) {
|
||||
Nan::ThrowError("Usage: pty.kill(pid, innerPidHandle)");
|
||||
return;
|
||||
}
|
||||
|
||||
int handle = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
HANDLE innerPidHandle =
|
||||
(HANDLE)info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
||||
|
||||
winpty_t *pc = get_pipe_handle(handle);
|
||||
if (pc == nullptr) {
|
||||
Nan::ThrowError("Pty seems to have been killed already");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(remove_pipe_handle(handle));
|
||||
CloseHandle(innerPidHandle);
|
||||
|
||||
return info.GetReturnValue().SetUndefined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
|
||||
extern "C" void init(v8::Local<v8::Object> target) {
|
||||
Nan::HandleScope scope;
|
||||
Nan::SetMethod(target, "startProcess", PtyStartProcess);
|
||||
Nan::SetMethod(target, "resize", PtyResize);
|
||||
Nan::SetMethod(target, "kill", PtyKill);
|
||||
Nan::SetMethod(target, "getExitCode", PtyGetExitCode);
|
||||
Nan::SetMethod(target, "getProcessList", PtyGetProcessList);
|
||||
};
|
||||
|
||||
NODE_MODULE(pty, init);
|
Loading…
Add table
Add a link
Reference in a new issue