Skip to content

Commit a9936b1

Browse files
authored
Add a new VT-100 compatible built-in terminal emulator (#3903)
1 parent 8fab477 commit a9936b1

File tree

193 files changed

+6538
-616
lines changed

Some content is hidden

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

193 files changed

+6538
-616
lines changed

CodeLite/AsyncProcess/asyncprocess.h

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,13 @@ enum IProcessCreateFlags {
4444
IProcessCreateWithHiddenConsole = (1 << 2), // Create process with a hidden console
4545
IProcessCreateSync =
4646
(1 << 3), // Create a synchronous process (i.e. there is no background thread that performs the reads)
47-
IProcessCreateAsSuperuser = (1 << 4), // On platforms that support it, start the process as superuser
48-
IProcessNoRedirect = (1 << 5),
49-
IProcessStderrEvent = (1 << 6), // fire a separate event for stderr output
50-
IProcessRawOutput = (1 << 7), // return the process output as is, don't strip anything. By default CodeLite strips
47+
IProcessNoRedirect = (1 << 4),
48+
IProcessStderrEvent = (1 << 5), // fire a separate event for stderr output
49+
IProcessRawOutput = (1 << 6), // return the process output as is, don't strip anything. By default CodeLite strips
5150
// terminal colours escape sequences
52-
IProcessCreateSSH = (1 << 8), // Create a remote process, over SSH
53-
IProcessInteractiveSSH = (1 << 9),
54-
IProcessWrapInShell = (1 << 10), // wrap the command in the OS shell (CMD, BASH)
55-
IProcessPseudoConsole = (1 << 11), // MSW only: use CreatePseudoConsole API for creating the process
56-
IProcessNoPty = (1 << 12), // Unix only: do not use forkpty, use normal fork()
51+
IProcessCreateSSH = (1 << 7), // Create a remote process, over SSH
52+
IProcessWrapInShell = (1 << 8), // wrap the command in the OS shell (CMD, BASH)
53+
IProcessNoPty = (1 << 9), // Unix only: do not use forkpty, use normal fork()
5754
};
5855

5956
class WXDLLIMPEXP_CL IProcess;

CodeLite/AsyncProcess/winprocess_impl.cpp

Lines changed: 16 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -71,30 +71,6 @@ thread_local ClosePseudoConsole_T ClosePseudoConsoleFunc = nullptr;
7171

7272
using HPCON = HANDLE;
7373

74-
/**
75-
* @class ConsoleAttacher
76-
* @date 11/03/10
77-
* @brief a helper class to attach this process to a process' console
78-
* this allows us to write directly into that process console-input-buffer
79-
* One should check isAttached once this object is constructed
80-
*/
81-
class ConsoleAttacher
82-
{
83-
public:
84-
bool isAttached;
85-
86-
public:
87-
ConsoleAttacher(long pid) { isAttached = AttachConsole(pid); }
88-
89-
~ConsoleAttacher()
90-
{
91-
if (isAttached) {
92-
FreeConsole();
93-
}
94-
isAttached = false;
95-
}
96-
};
97-
9874
static bool CheckIsAlive(HANDLE hProcess)
9975
{
10076
DWORD dwExitCode;
@@ -284,117 +260,10 @@ IProcess* WinProcessImpl::Execute(wxEvtHandler* parent,
284260
return Execute(parent, cmd, flags, workingDirectory, cb);
285261
}
286262

287-
IProcess*
288-
WinProcessImpl::ExecuteConPTY(wxEvtHandler* parent, const wxString& cmd, size_t flags, const wxString& workingDir)
289-
{
290-
// - Close these after CreateProcess of child application with pseudoconsole object.
291-
HANDLE inputReadSide, outputWriteSide;
292-
293-
// - Hold onto these and use them for communication with the child through the pseudoconsole.
294-
HANDLE outputReadSide, inputWriteSide;
295-
HPCON hPC = 0;
296-
297-
// Create the in/out pipes:
298-
if (!CreatePipe(&inputReadSide, &inputWriteSide, NULL, 0)) {
299-
return nullptr;
300-
}
301-
if (!CreatePipe(&outputReadSide, &outputWriteSide, NULL, 0)) {
302-
::CloseHandle(inputReadSide);
303-
::CloseHandle(inputWriteSide);
304-
return nullptr;
305-
}
306-
307-
#if !defined(_MSC_VER)
308-
// Create the Pseudo Console, using the pipes
309-
if (loadOnce) {
310-
loadOnce = false;
311-
auto hDLL = ::LoadLibrary(L"Kernel32.dll");
312-
if (hDLL) {
313-
CreatePseudoConsoleFunc = (CreatePseudoConsole_T)::GetProcAddress(hDLL, "CreatePseudoConsole");
314-
ClosePseudoConsoleFunc = (ClosePseudoConsole_T)::GetProcAddress(hDLL, "ClosePseudoConsole");
315-
FreeLibrary(hDLL);
316-
}
317-
}
318-
#endif
319-
320-
if (!CreatePseudoConsoleFunc || !ClosePseudoConsoleFunc) {
321-
::CloseHandle(inputReadSide);
322-
::CloseHandle(outputWriteSide);
323-
::CloseHandle(inputWriteSide);
324-
::CloseHandle(outputReadSide);
325-
return nullptr;
326-
}
327-
auto hr = CreatePseudoConsoleFunc({1000, 32}, inputReadSide, outputWriteSide, 0, &hPC);
328-
if (FAILED(hr)) {
329-
::CloseHandle(inputReadSide);
330-
::CloseHandle(outputWriteSide);
331-
::CloseHandle(inputWriteSide);
332-
::CloseHandle(outputReadSide);
333-
return nullptr;
334-
}
335-
336-
// Prepare the StartupInfoEx structure attached to the ConPTY.
337-
STARTUPINFOEX siEx{};
338-
PrepareStartupInformation(hPC, &siEx);
339-
340-
WinProcessImpl* prc = new WinProcessImpl(parent);
341-
::ZeroMemory(&prc->piProcInfo, sizeof(prc->piProcInfo));
342-
343-
auto fSuccess = CreateProcess(nullptr,
344-
(wchar_t*)cmd.wc_str(),
345-
nullptr,
346-
nullptr,
347-
FALSE,
348-
EXTENDED_STARTUPINFO_PRESENT,
349-
nullptr,
350-
nullptr,
351-
&siEx.StartupInfo,
352-
&prc->piProcInfo);
353-
354-
if (!fSuccess) {
355-
clERROR() << "Failed to launch process:" << cmd << "." << GetLastError() << endl;
356-
wxDELETE(prc);
357-
return nullptr;
358-
}
359-
::CloseHandle(inputReadSide);
360-
::CloseHandle(outputWriteSide);
361-
362-
if (!(prc->m_flags & IProcessCreateSync)) {
363-
prc->StartReaderThread();
364-
}
365-
prc->m_writerThread = new WinWriterThread(prc->piProcInfo.hProcess, inputWriteSide);
366-
prc->m_writerThread->Start();
367-
368-
prc->m_callback = nullptr;
369-
prc->m_flags = flags;
370-
prc->m_pid = prc->piProcInfo.dwProcessId;
371-
prc->hChildStdoutRdDup = outputReadSide;
372-
prc->m_hPseudoConsole = hPC;
373-
return prc;
374-
}
375-
376-
IProcess* WinProcessImpl::ExecuteConPTY(wxEvtHandler* parent,
377-
const std::vector<wxString>& args,
378-
size_t flags,
379-
const wxString& workingDir)
380-
{
381-
wxArrayString wxarr;
382-
wxarr.reserve(args.size());
383-
for (const auto& arg : args) {
384-
wxarr.Add(arg);
385-
}
386-
wxString cmd = ArrayJoin(wxarr, flags);
387-
return ExecuteConPTY(parent, cmd, flags, workingDir);
388-
}
389-
390263
/*static*/
391264
IProcess* WinProcessImpl::Execute(
392265
wxEvtHandler* parent, const wxString& cmd, size_t flags, const wxString& workingDir, IProcessCallback* cb)
393266
{
394-
if (flags & IProcessPseudoConsole) {
395-
return ExecuteConPTY(parent, cmd, flags, workingDir);
396-
}
397-
398267
SECURITY_ATTRIBUTES saAttr;
399268
BOOL fSuccess;
400269

@@ -415,29 +284,17 @@ IProcess* WinProcessImpl::Execute(
415284
bool redirectOutput = !(flags & IProcessNoRedirect);
416285

417286
// The steps for redirecting child process's STDOUT:
418-
// 1. Save current STDOUT, to be restored later.
419-
// 2. Create anonymous pipe to be STDOUT for child process.
420-
// 3. Set STDOUT of the parent process to be write handle to
421-
// the pipe, so it is inherited by the child process.
422-
// 4. Create a noninheritable duplicate of the read handle and
287+
// 1. Create anonymous pipe to be STDOUT for child process.
288+
// 2. Create a noninheritable duplicate of the read handle and
423289
// close the inheritable read handle.
424290

425291
if (redirectOutput) {
426-
// Save the handle to the current STDOUT.
427-
prc->hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
428-
429292
// Create a pipe for the child process's STDOUT.
430293
if (!CreatePipe(&prc->hChildStdoutRd, &prc->hChildStdoutWr, &saAttr, 0)) {
431294
delete prc;
432295
return NULL;
433296
}
434297

435-
// Set a write handle to the pipe to be STDOUT.
436-
if (!SetStdHandle(STD_OUTPUT_HANDLE, prc->hChildStdoutWr)) {
437-
delete prc;
438-
return NULL;
439-
}
440-
441298
// Create noninheritable read handle and close the inheritable read handle.
442299
fSuccess = DuplicateHandle(GetCurrentProcess(),
443300
prc->hChildStdoutRd,
@@ -453,28 +310,16 @@ IProcess* WinProcessImpl::Execute(
453310
CloseHandle(prc->hChildStdoutRd);
454311

455312
// The steps for redirecting child process's STDERR:
456-
// 1. Save current STDERR, to be restored later.
457-
// 2. Create anonymous pipe to be STDERR for child process.
458-
// 3. Set STDERR of the parent process to be write handle to
459-
// the pipe, so it is inherited by the child process.
460-
// 4. Create a noninheritable duplicate of the read handle and
313+
// 1. Create anonymous pipe to be STDERR for child process.
314+
// 2. Create a noninheritable duplicate of the read handle and
461315
// close the inheritable read handle.
462316

463-
// Save the handle to the current STDERR.
464-
prc->hSaveStderr = GetStdHandle(STD_ERROR_HANDLE);
465-
466317
// Create a pipe for the child process's STDERR.
467318
if (!CreatePipe(&prc->hChildStderrRd, &prc->hChildStderrWr, &saAttr, 0)) {
468319
delete prc;
469320
return NULL;
470321
}
471322

472-
// Set a write handle to the pipe to be STDERR.
473-
if (!SetStdHandle(STD_ERROR_HANDLE, prc->hChildStderrWr)) {
474-
delete prc;
475-
return NULL;
476-
}
477-
478323
// Create noninheritable read handle and close the inheritable read handle.
479324
fSuccess = DuplicateHandle(GetCurrentProcess(),
480325
prc->hChildStderrRd,
@@ -490,26 +335,16 @@ IProcess* WinProcessImpl::Execute(
490335
CloseHandle(prc->hChildStderrRd);
491336

492337
// The steps for redirecting child process's STDIN:
493-
// 1. Save current STDIN, to be restored later.
494-
// 2. Create anonymous pipe to be STDIN for child process.
495-
// 3. Set STDIN of the parent to be the read handle to the
496-
// pipe, so it is inherited by the child process.
497-
// 4. Create a noninheritable duplicate of the write handle,
338+
// 1. Create anonymous pipe to be STDIN for child process.
339+
// 2. Create a noninheritable duplicate of the write handle,
498340
// and close the inheritable write handle.
499341

500-
// Save the handle to the current STDIN.
501-
prc->hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);
502-
503342
// Create a pipe for the child process's STDIN.
504343
if (!CreatePipe(&prc->hChildStdinRd, &prc->hChildStdinWr, &saAttr, 0)) {
505344
delete prc;
506345
return NULL;
507346
}
508-
// Set a read handle to the pipe to be STDIN.
509-
if (!SetStdHandle(STD_INPUT_HANDLE, prc->hChildStdinRd)) {
510-
delete prc;
511-
return NULL;
512-
}
347+
513348
// Duplicate the write handle to the pipe so it is not inherited.
514349
fSuccess = DuplicateHandle(GetCurrentProcess(),
515350
prc->hChildStdinWr,
@@ -546,13 +381,19 @@ IProcess* WinProcessImpl::Execute(
546381

547382
if (flags & IProcessCreateWithHiddenConsole) {
548383
siStartInfo.wShowWindow = SW_HIDE;
549-
creationFlags = CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP;
384+
// When redirecting output, we cannot use CREATE_NEW_CONSOLE because Windows
385+
// ignores STARTF_USESTDHANDLES when CREATE_NEW_CONSOLE is set. The child would
386+
// get its own console buffer that we cannot read from. Use CREATE_NO_WINDOW instead.
387+
if (redirectOutput) {
388+
creationFlags = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP;
389+
} else {
390+
creationFlags = CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP;
391+
}
550392
}
551393

552394
LOG_IF_TRACE { clDEBUG1() << "Running process:" << cmd << endl; }
553395
BOOL ret = FALSE;
554396
{
555-
ConsoleAttacher ca(prc->GetPid());
556397
ret = CreateProcess(NULL,
557398
cmd.wchar_str(), // shell line execution command
558399
NULL, // process security attributes
@@ -573,30 +414,6 @@ IProcess* WinProcessImpl::Execute(
573414
wxDELETE(prc);
574415
return NULL;
575416
}
576-
577-
if (redirectOutput) {
578-
// After process creation, restore the saved STDIN and STDOUT.
579-
if (!SetStdHandle(STD_INPUT_HANDLE, prc->hSaveStdin)) {
580-
delete prc;
581-
return NULL;
582-
}
583-
if (!SetStdHandle(STD_OUTPUT_HANDLE, prc->hSaveStdout)) {
584-
delete prc;
585-
return NULL;
586-
}
587-
if (!SetStdHandle(STD_OUTPUT_HANDLE, prc->hSaveStderr)) {
588-
delete prc;
589-
return NULL;
590-
}
591-
}
592-
593-
if ((prc->m_flags & IProcessCreateConsole) || (prc->m_flags & IProcessCreateWithHiddenConsole)) {
594-
ConsoleAttacher ca(prc->GetPid());
595-
if (ca.isAttached) {
596-
freopen("CONOUT$", "wb", stdout); // reopen stout handle as console window output
597-
freopen("CONOUT$", "wb", stderr); // reopen stderr handle as console window output
598-
}
599-
}
600417
prc->SetPid(prc->dwProcessId);
601418
if (!(prc->m_flags & IProcessCreateSync)) {
602419
prc->StartReaderThread();
@@ -829,46 +646,7 @@ void WinProcessImpl::Terminate()
829646
}
830647
}
831648

832-
bool WinProcessImpl::WriteToConsole(const wxString& buff)
833-
{
834-
wxString pass(buff);
835-
pass.Trim().Trim(false);
836-
837-
// To write password, we need to attach to the child process console
838-
if (!(m_flags & (IProcessCreateWithHiddenConsole | IProcessCreateConsole)))
839-
return false;
840-
841-
ConsoleAttacher ca(GetPid());
842-
if (ca.isAttached == false)
843-
return false;
844-
845-
HANDLE hStdIn = ::CreateFile(
846-
L"CONIN$", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
847-
if (hStdIn == INVALID_HANDLE_VALUE) {
848-
return false;
849-
}
850-
851-
pass += wxT("\r\n");
852-
std::vector<INPUT_RECORD> pKeyEvents(pass.Len());
853-
854-
for (size_t i = 0; i < pass.Len(); i++) {
855-
pKeyEvents[i].EventType = KEY_EVENT;
856-
pKeyEvents[i].Event.KeyEvent.bKeyDown = TRUE;
857-
pKeyEvents[i].Event.KeyEvent.wRepeatCount = 1;
858-
pKeyEvents[i].Event.KeyEvent.wVirtualKeyCode = LOBYTE(::VkKeyScan(pass[i]));
859-
pKeyEvents[i].Event.KeyEvent.wVirtualScanCode = 0;
860-
pKeyEvents[i].Event.KeyEvent.uChar.UnicodeChar = pass[i];
861-
pKeyEvents[i].Event.KeyEvent.dwControlKeyState = 0;
862-
}
863-
864-
DWORD dwTextWritten;
865-
if (::WriteConsoleInput(hStdIn, pKeyEvents.data(), pass.Len(), &dwTextWritten) == FALSE) {
866-
CloseHandle(hStdIn);
867-
return false;
868-
}
869-
CloseHandle(hStdIn);
870-
return true;
871-
}
649+
bool WinProcessImpl::WriteToConsole(const wxString& buff) { return Write(buff); }
872650

873651
void WinProcessImpl::Detach()
874652
{

CodeLite/AsyncProcess/winprocess_impl.h

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,6 @@ class WXDLLIMPEXP_CL WinProcessImpl : public IProcess
6767
const wxString& workingDir = wxEmptyString,
6868
IProcessCallback* cb = NULL);
6969

70-
// Create process asynchronously and return a process object
71-
static IProcess* ExecuteConPTY(wxEvtHandler* parent,
72-
const std::vector<wxString>& args,
73-
size_t flags = IProcessCreateWithHiddenConsole,
74-
const wxString& workingDir = wxEmptyString);
75-
76-
// Create process asynchronously and return a process object
77-
static IProcess* ExecuteConPTY(wxEvtHandler* parent,
78-
const wxString& cmd,
79-
size_t flags = IProcessCreateWithHiddenConsole,
80-
const wxString& workingDir = wxEmptyString);
81-
8270
/**
8371
* @brief read data from stdout and error
8472
* @param buff check the buffer when true is returned
@@ -109,8 +97,7 @@ class WXDLLIMPEXP_CL WinProcessImpl : public IProcess
10997
hChildStdinWrDup = INVALID_HANDLE_VALUE, hChildStdoutRd = INVALID_HANDLE_VALUE,
11098
hChildStdoutWr = INVALID_HANDLE_VALUE, hChildStdoutRdDup = INVALID_HANDLE_VALUE,
11199
hChildStderrRd = INVALID_HANDLE_VALUE, hChildStderrWr = INVALID_HANDLE_VALUE,
112-
hChildStderrRdDup = INVALID_HANDLE_VALUE, hSaveStdin = INVALID_HANDLE_VALUE,
113-
hSaveStdout = INVALID_HANDLE_VALUE, hSaveStderr = INVALID_HANDLE_VALUE;
100+
hChildStderrRdDup = INVALID_HANDLE_VALUE;
114101

115102
VOID* m_hPseudoConsole = nullptr;
116103
// Child process id & information

0 commit comments

Comments
 (0)