/*
Terminal
by John Ryland
Copyright (c) 2023
*/
////////////////////////////////////////////////////////////////////////////////////
// Terminal Process
#include "TerminalProcess.h"
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <fcntl.h>
#ifdef _WIN32
# include <Windows.h>
# include <ConsoleApi.h>
# include <memory>
#else
#ifdef __LINUX__
# include <pty.h>
#else
# include <util.h>
#endif
# include <unistd.h>
#endif
namespace Terminal {
TerminalProcess::TerminalProcess()
: m_terminalFd(0)
, m_exited(false)
{
}
// virtual
TerminalProcess::~TerminalProcess()
{
Shutdown();
}
// virtual
void TerminalProcess::Update()
{
}
// virtual
bool TerminalProcess::HasExited()
{
return m_terminalFd && m_exited;
}
#ifndef _WIN32
// virtual
void TerminalProcess::Initialize()
{
if (m_terminalFd)
printf("TerminalProcess double initialized\n");
if (::forkpty(&m_terminalFd, NULL, NULL, NULL) == 0)
{
//::setenv("term", "TerminalEmulator", 1);
::setenv("TERM", "xterm-256color", 1);
::execl("/bin/bash", "/bin/bash", "-i", nullptr);
::exit(0);
}
::fcntl(m_terminalFd, F_SETFL, fcntl(m_terminalFd, F_GETFL) | O_NONBLOCK);
}
// virtual
void TerminalProcess::Shutdown()
{
if (m_terminalFd)
::close(m_terminalFd);
m_terminalFd = 0;
}
// virtual
bool TerminalProcess::GetAvailableData(uint8_t*& buffer, uint32_t& bytesAvailable)
{
if (!m_terminalFd)
return false;
ssize_t read_size = ::read(m_terminalFd, m_buffer, BUFFER_SIZE);
if (read_size > 0)
{
buffer = m_buffer;
bytesAvailable = read_size;
return true;
}
if (read_size <= 0 && errno != EAGAIN)
{
printf("Bad read: closing\n");
m_exited = true;
}
return false;
}
// virtual
bool TerminalProcess::SendBytes(const char* buffer, uint32_t bytesToWrite)
{
return m_terminalFd && ::write(m_terminalFd, buffer, bytesToWrite) == bytesToWrite;
}
// virtual
void TerminalProcess::UpdateSize(int rows, int cols)
{
struct winsize size { (uint16_t)rows, (uint16_t)cols, 0, 0 };
#ifdef __LINUX__
::ioctl(m_terminalFd, TIOCSWINSZ, &size);
#endif
}
#else
/*
typedef void* HPCON;
// Creates a "Pseudo Console" (ConPTY).
HRESULT WINAPI CreatePseudoConsole(
_In_ COORD size, // ConPty Dimensions
_In_ HANDLE hInput, // ConPty Input
_In_ HANDLE hOutput, // ConPty Output
_In_ DWORD dwFlags, // ConPty Flags
_Out_ HPCON* phPC); // ConPty Reference
// Resizes the given ConPTY to the specified size, in characters.
HRESULT WINAPI ResizePseudoConsole(_In_ HPCON hPC, _In_ COORD size);
// Closes the ConPTY and all associated handles. Client applications attached
// to the ConPTY will also terminated.
VOID WINAPI ClosePseudoConsole(_In_ HPCON hPC);
*/
// Initializes the specified startup info struct with the required properties and
// updates its thread attribute list with the specified ConPTY handle
HRESULT InitializeStartupInfoAttachedToConPTY(STARTUPINFOEX* siEx, HPCON hPC)
{
HRESULT hr = E_UNEXPECTED;
size_t size;
siEx->StartupInfo.cb = sizeof(STARTUPINFOEX);
// Create the appropriately sized thread attribute list
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
std::unique_ptr<BYTE[]> attrList = std::make_unique<BYTE[]>(size);
// Set startup info's attribute list & initialize it
siEx->lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(
attrList.get());
bool fSuccess = InitializeProcThreadAttributeList(
siEx->lpAttributeList, 1, 0, (PSIZE_T)&size);
if (fSuccess)
{
// Set thread attribute list's Pseudo Console to the specified ConPTY
fSuccess = UpdateProcThreadAttribute(
lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
sizeof(HPCON),
NULL,
NULL);
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
// virtual
void TerminalProcess::Initialize()
{
if (m_terminalFd)
printf("TerminalProcess double initialized\n");
HANDLE hOut, hIn;
HANDLE outPipeOurSide, inPipeOurSide;
HANDLE outPipePseudoConsoleSide, inPipePseudoConsoleSide;
HPCON m_terminalFd = 0;
// Create the in/out pipes:
CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, NULL, 0);
CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, NULL, 0);
// Create the Pseudo Console, using the pipes
CreatePseudoConsole(
{80, 32},
inPipePseudoConsoleSide,
outPipePseudoConsoleSide,
0,
&m_terminalFd);
// Prepare the StartupInfoEx structure attached to the ConPTY.
STARTUPINFOEX siEx{};
InitializeStartupInfoAttachedToConPTY(&siEx, m_terminalFd);
// Create the client application, using startup info containing ConPTY info
wchar_t* commandline = L"c:\\windows\\system32\\cmd.exe";
PROCESS_INFORMATION piClient{};
fSuccess = CreateProcessW(
nullptr,
commandline,
nullptr,
nullptr,
TRUE,
EXTENDED_STARTUPINFO_PRESENT,
nullptr,
nullptr,
&siEx->StartupInfo,
&piClient);
}
// virtual
void TerminalProcess::Shutdown()
{
if (m_terminalFd)
ClosePseudoConsole(m_terminalFd);
m_terminalFd = 0;
}
// virtual
bool TerminalProcess::GetAvailableData(uint8_t*& buffer, uint32_t& bytesAvailable)
{
if (!m_terminalFd)
return false;
/*
DWORD dwRead;
if (!PeekNamedPipe(
__in HANDLE hNamedPipe,
__out_opt LPVOID lpBuffer,
__in DWORD nBufferSize,
__out_opt LPDWORD lpBytesRead,
__out_opt LPDWORD lpTotalBytesAvail,
__out_opt LPDWORD lpBytesLeftThisMessage
);
if(TotalBytesAvail > 0)
*/
DWORD dwRead;
if (ReadFile(hOut, m_buffer, (DWORD)BUFFER_SIZE, &dwRead, nullptr))
{
buffer = m_buffer;
bytesAvailable = dwRead;
return true;
}
/*
if (read_size <= 0 && errno != EAGAIN)
{
printf("Bad read: closing\n");
m_exited = true;
}
*/
return false;
}
// virtual
bool TerminalProcess::SendBytes(const char* buffer, uint32_t bytesToWrite)
{
DWORD dwWritten;
WriteFile(hIn, buffer, (DWORD)bytesToWrite, &dwWritten, nullptr);
return m_terminalFd && dwWritten == bytesToWrite;
}
// virtual
void TerminalProcess::UpdateSize(int rows, int cols)
{
COORD coord { (uint16_t)rows, (uint16_t)cols };
ResizePseudoConsole(m_terminalFd, coord);
}
#endif
} // Terminal namespace