1003 lines
26 KiB
C++
Executable File
1003 lines
26 KiB
C++
Executable File
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: ConsoleStd.cpp
|
|
Date: 2021-6-8
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include "ConsoleStd.hpp"
|
|
#include <Source/Locale/Locale.hpp>
|
|
#include <Source/Console/Console.hpp>
|
|
#include <Source/Logging/Sinks/StdConsole.hpp>
|
|
|
|
#include <Source/IO/Loop/ILoopSourceEx.hpp>
|
|
#include <Source/IO/Loop/LSHandle.hpp>
|
|
#include <Source/IO/Loop/LSEvent.hpp>
|
|
|
|
#include <Source/Console/ConsoleTTY/ConsoleTTY.hpp>
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED) || defined(AURORA_IS_POSIX_DERIVED)
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
// nothing yet
|
|
#elif defined(AURORA_IS_POSIX_DERIVED)
|
|
#define IO_POSIX_STREAMS
|
|
|
|
#include <stdio.h>
|
|
#include <sys/select.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
// compile time preprocessor definition to strip std stream based IO
|
|
#if !(defined(AURORA_DISABLE_STD_CONSOLE))
|
|
#define ENABLE_STD_CONSOLE
|
|
#endif
|
|
#endif
|
|
|
|
namespace Aurora::Console::ConsoleStd
|
|
{
|
|
|
|
#if defined(ENABLE_STD_CONSOLE)
|
|
|
|
struct ConsoleHasDataLoopSource :
|
|
#if defined(AURORA_COMPILER_MSVC)
|
|
virtual AuLoop::ILoopSource,
|
|
#endif
|
|
virtual AuLoop::LSEvent
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
ConsoleHasDataLoopSource() : LSEvent(false, false, true)
|
|
{
|
|
|
|
}
|
|
#else
|
|
ConsoleHasDataLoopSource(int fd) : LSEvent(false, false, true)
|
|
{
|
|
handles = {fd, LSEvent::GetHandle()};
|
|
}
|
|
#endif
|
|
|
|
bool IsSignaled() override;
|
|
AuLoop::ELoopSource GetType() override;
|
|
bool OnTrigger(AuUInt handle) override;
|
|
bool WaitOn(AuUInt32 timeout) override;
|
|
|
|
private:
|
|
|
|
#if !defined(AURORA_IS_MODERNNT_DERIVED)
|
|
AuList<AuUInt> handles;
|
|
virtual const AuList<AuUInt> &GetHandles() override;
|
|
virtual bool Singular() override;
|
|
#endif
|
|
};
|
|
|
|
static AuByteBuffer gStdoutBuffer;
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
#define DEFAULT_HANDLE_VAL INVALID_HANDLE_VALUE
|
|
using StreamHandle_t = HANDLE;
|
|
|
|
static StreamHandle_t gWin32Thread = INVALID_HANDLE_VALUE;
|
|
static DWORD gCanonicalBackup {};
|
|
#elif defined(IO_POSIX_STREAMS)
|
|
#define DEFAULT_HANDLE_VAL 0xFFFFFFFF
|
|
using StreamHandle_t = int;
|
|
#endif
|
|
#define IS_STREAM_HANDLE_VALID(h) (h != DEFAULT_HANDLE_VAL)
|
|
|
|
static bool AsyncReadAnyOrReadStreamBlock();
|
|
void WriteStdOut(AuUInt8 level, const ConsoleMessage &msg);
|
|
|
|
static const AuMach kLineBufferMax = 2048;
|
|
static AuUInt8 gLineEncodedBuffer[kLineBufferMax];
|
|
static AuUInt gEncodedIndex = 0;
|
|
static AuString gLineBuffer(kLineBufferMax, 0);
|
|
static AuUInt gLineIndex = 0;
|
|
static StreamHandle_t gTerminateConsole;
|
|
static StreamHandle_t gInputStream = DEFAULT_HANDLE_VAL;
|
|
static StreamHandle_t gOutputStream = DEFAULT_HANDLE_VAL;
|
|
static AuSPtr<ConsoleHasDataLoopSource> gLoopSource;
|
|
static bool gCanonicalEnabled {};
|
|
|
|
static AuList<NoncanonicalInput> gCanonicalBuffer;
|
|
|
|
//static AuThreadPrimitives::MutexUnique_t gRingLock = AuThreadPrimitives::MutexUnique();
|
|
static AuThreadPrimitives::SpinLock gRingLock;// = AuThreadPrimitives::MutexUnique();
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
|
|
static DWORD WINAPI StdInWin32Thread(void*)
|
|
{
|
|
HANDLE a[2] = {gInputStream, gTerminateConsole};
|
|
|
|
while (!gCanonicalEnabled)
|
|
{
|
|
WaitForMultipleObjectsEx(2, a, false, 25, 0);
|
|
|
|
if (WaitForSingleObject(gTerminateConsole, 0) == WAIT_OBJECT_0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
AsyncReadAnyOrReadStreamBlock();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void ProcessCanonical(HANDLE h)
|
|
{
|
|
INPUT_RECORD records[4096];
|
|
DWORD dwRecords;
|
|
|
|
void *data = &gLineEncodedBuffer[gEncodedIndex];
|
|
auto length = kLineBufferMax - gEncodedIndex;
|
|
|
|
if (!IS_STREAM_HANDLE_VALID(h))
|
|
{
|
|
h = gInputStream;
|
|
}
|
|
|
|
if (length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!GetNumberOfConsoleInputEvents(h,
|
|
&dwRecords))
|
|
{
|
|
return;
|
|
}
|
|
|
|
dwRecords = AuMin(dwRecords, DWORD(AuArraySize(records)));
|
|
if (!dwRecords)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!ReadConsoleInputW(h,
|
|
records,
|
|
dwRecords,
|
|
&dwRecords))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AU_LOCK_GUARD(gRingLock);
|
|
|
|
gCanonicalBuffer.reserve(4096);
|
|
|
|
for (int i = 0; i < dwRecords; i++)
|
|
{
|
|
int z;
|
|
bool dBreak = false;
|
|
NoncanonicalInput canInput;
|
|
canInput.type = ENoncanonicalInput::eEnumInvalid;
|
|
canInput.scrollDeltaY = 0;
|
|
|
|
auto &record = records[i];
|
|
AuString key;
|
|
|
|
switch (record.EventType)
|
|
{
|
|
case KEY_EVENT:
|
|
|
|
if (!record.Event.KeyEvent.bKeyDown)
|
|
{
|
|
dBreak = true;
|
|
break;
|
|
}
|
|
|
|
// TODO: numpad
|
|
if (record.Event.KeyEvent.wVirtualKeyCode <= VK_HELP &&
|
|
record.Event.KeyEvent.wVirtualKeyCode != VK_SPACE &&
|
|
(record.Event.KeyEvent.wVirtualKeyCode < VK_NUMPAD0 || record.Event.KeyEvent.wVirtualKeyCode > VK_NUMPAD9))
|
|
{
|
|
switch (record.Event.KeyEvent.wVirtualKeyCode)
|
|
{
|
|
case VK_UP:
|
|
canInput.type = ENoncanonicalInput::eArrowUp;
|
|
break;
|
|
case VK_DOWN:
|
|
canInput.type = ENoncanonicalInput::eArrowDown;
|
|
break;
|
|
case VK_LEFT:
|
|
canInput.type = ENoncanonicalInput::eArrowLeft;
|
|
break;
|
|
case VK_RIGHT:
|
|
canInput.type = ENoncanonicalInput::eArrowRight;
|
|
break;
|
|
case VK_HOME:
|
|
canInput.type = ENoncanonicalInput::eHome;
|
|
break;
|
|
case VK_BACK:
|
|
canInput.type = ENoncanonicalInput::eBackspace;
|
|
break;
|
|
case VK_PRIOR:
|
|
canInput.type = ENoncanonicalInput::ePageUp;
|
|
break;
|
|
case VK_NEXT:
|
|
canInput.type = ENoncanonicalInput::ePageDown;
|
|
break;
|
|
case VK_RETURN:
|
|
canInput.type = ENoncanonicalInput::eEnter;
|
|
break;
|
|
default:
|
|
dBreak = true;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
canInput.type = ENoncanonicalInput::eInput;
|
|
|
|
key = AuLocale::ConvertFromWChar(&record.Event.KeyEvent.uChar.UnicodeChar, 1);
|
|
|
|
if (key.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (z = 0; z < record.Event.KeyEvent.wRepeatCount; z++)
|
|
{
|
|
canInput.string += key;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case MOUSE_EVENT:
|
|
|
|
if (record.Event.MouseEvent.dwEventFlags == MOUSE_WHEELED)
|
|
{
|
|
canInput.type = ENoncanonicalInput::eScroll;
|
|
canInput.scrollDeltaY = AuStaticCast<AuInt16>(AuBitsToHigher((AuUInt32)record.Event.MouseEvent.dwButtonState));
|
|
|
|
if (canInput.scrollDeltaY > 1)
|
|
canInput.scrollDeltaY = 1;
|
|
else if (canInput.scrollDeltaY < -1)
|
|
canInput.scrollDeltaY = -1;
|
|
}
|
|
else
|
|
{
|
|
dBreak = true;
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
default:
|
|
dBreak = true;
|
|
break;
|
|
};
|
|
|
|
if (dBreak) continue;
|
|
|
|
if (!AuTryInsert(gCanonicalBuffer, canInput))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NoncanonicalTick()
|
|
{
|
|
ProcessCanonical(INVALID_HANDLE_VALUE);
|
|
}
|
|
|
|
void SignalKillNT()
|
|
{
|
|
if (IS_STREAM_HANDLE_VALID(gWin32Thread))
|
|
{
|
|
CancelSynchronousIo(gWin32Thread);
|
|
}
|
|
|
|
if (IS_STREAM_HANDLE_VALID(gTerminateConsole))
|
|
{
|
|
SetEvent(gTerminateConsole);
|
|
}
|
|
|
|
if (WaitForSingleObject(gWin32Thread, 200) != WAIT_OBJECT_0)
|
|
{
|
|
TerminateThread(gWin32Thread, 0);
|
|
}
|
|
|
|
AuWin32CloseHandle(gWin32Thread);
|
|
}
|
|
|
|
bool EnterNoncanonicalMode()
|
|
{
|
|
DWORD mode;
|
|
|
|
if (!GetConsoleMode(gInputStream, &mode))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
gCanonicalBackup = mode;
|
|
|
|
|
|
mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_QUICK_EDIT_MODE);
|
|
mode |= ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT | ENABLE_EXTENDED_FLAGS;
|
|
|
|
SetConsoleMode(gInputStream, mode);
|
|
|
|
gCanonicalEnabled = true;
|
|
|
|
SignalKillNT();
|
|
|
|
INPUT_RECORD niceWorkMicrosoft[2] {};
|
|
niceWorkMicrosoft[0].EventType = KEY_EVENT;
|
|
niceWorkMicrosoft[0].Event.KeyEvent.bKeyDown = TRUE;
|
|
niceWorkMicrosoft[0].Event.KeyEvent.dwControlKeyState = 0;
|
|
niceWorkMicrosoft[0].Event.KeyEvent.wRepeatCount = 1;
|
|
niceWorkMicrosoft[0].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
|
|
niceWorkMicrosoft[0].Event.KeyEvent.uChar.UnicodeChar = '\r';
|
|
niceWorkMicrosoft[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
|
|
|
|
niceWorkMicrosoft[1].EventType = KEY_EVENT;
|
|
niceWorkMicrosoft[1].Event.KeyEvent.bKeyDown = FALSE;
|
|
niceWorkMicrosoft[1].Event.KeyEvent.dwControlKeyState = 0;
|
|
niceWorkMicrosoft[1].Event.KeyEvent.wRepeatCount = 1;
|
|
niceWorkMicrosoft[1].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
|
|
niceWorkMicrosoft[1].Event.KeyEvent.uChar.UnicodeChar = '\r';
|
|
niceWorkMicrosoft[1].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
|
|
|
|
DWORD idc;
|
|
WriteConsoleInputW(gInputStream, niceWorkMicrosoft, 2, &idc);
|
|
|
|
return true;
|
|
}
|
|
|
|
void LeaveNoncanonicalMode()
|
|
{
|
|
if (!AuExchange(gCanonicalEnabled, false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetConsoleMode(gInputStream, gCanonicalBackup);
|
|
|
|
gWin32Thread = CreateThread(nullptr, 0, StdInWin32Thread, nullptr, 0, nullptr);
|
|
}
|
|
|
|
#else
|
|
|
|
bool EnterNoncanonicalMode()
|
|
{
|
|
}
|
|
|
|
void LeaveNoncanonicalMode()
|
|
{
|
|
}
|
|
|
|
|
|
void NoncanonicalTick()
|
|
{
|
|
|
|
}
|
|
#endif
|
|
|
|
AuList<NoncanonicalInput> DequeueNoncanonicalInput()
|
|
{
|
|
AU_LOCK_GUARD(gRingLock);
|
|
return AuExchange(gCanonicalBuffer, {});
|
|
}
|
|
|
|
bool ConsoleHasDataLoopSource::IsSignaled()
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
return gLineIndex || (gRuntimeConfig.console.enableStdPassthrough && gRuntimeConfig.console.enableStdIn && gEncodedIndex);
|
|
#else
|
|
return LSHandle::IsSignaled();
|
|
#endif
|
|
}
|
|
|
|
AuLoop::ELoopSource ConsoleHasDataLoopSource::GetType()
|
|
{
|
|
return AuLoop::ELoopSource::eProcessStdIn;
|
|
}
|
|
|
|
bool ConsoleHasDataLoopSource::OnTrigger(AuUInt handle)
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
return IsSignaled();
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool ConsoleHasDataLoopSource::WaitOn(AuUInt32 timeout)
|
|
{
|
|
return LSHandle::WaitOn(timeout);
|
|
}
|
|
|
|
#if !defined(AURORA_IS_MODERNNT_DERIVED)
|
|
|
|
const AuList<AuUInt> &ConsoleHasDataLoopSource::GetHandles()
|
|
{
|
|
return this->handles;
|
|
}
|
|
|
|
bool ConsoleHasDataLoopSource::Singular()
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static void StartLogger()
|
|
{
|
|
if (gRuntimeConfig.console.enableStdPassthrough == gRuntimeConfig.console.enableStdOut)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Console::AddDefaultLogger(AuUnsafeRaiiToShared(&Logging::Sinks::gStdConsoleSink));
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
static bool gConsoleStarted = false;
|
|
if (AuExchange(gConsoleStarted, true)) return;
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
|
|
DWORD dwMode;
|
|
bool ok;
|
|
// Obtain a win32 file HANDLE of STDIN
|
|
auto fileHandle =
|
|
#if defined(AURORA_PLATFORM_WIN32)
|
|
GetStdHandle(STD_INPUT_HANDLE);
|
|
#else
|
|
CreateFileW(L"CONIN$",
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
#endif
|
|
|
|
SysAssert(fileHandle != INVALID_HANDLE_VALUE, "Couldn't open CONIN");
|
|
gInputStream = fileHandle;
|
|
|
|
// Obtain a win32 file HANDLE of STDOUT
|
|
fileHandle =
|
|
#if defined(AURORA_PLATFORM_WIN32)
|
|
GetStdHandle(STD_OUTPUT_HANDLE);
|
|
#else
|
|
CreateFileW(L"CONOUT$",
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
#endif
|
|
|
|
SysAssert(fileHandle != INVALID_HANDLE_VALUE, "Couldn't open CONOUT");
|
|
gOutputStream = fileHandle;
|
|
|
|
bool isNotPipe = GetFileType(fileHandle) != FILE_TYPE_PIPE;
|
|
|
|
if (isNotPipe)
|
|
{
|
|
// Get current console flags
|
|
if (GetConsoleMode(gOutputStream, &dwMode))
|
|
{
|
|
if (gRuntimeConfig.console.enableStdPassthrough ^ gRuntimeConfig.console.enableStdOut)
|
|
{
|
|
// Enable escape processing; enable colored output
|
|
dwMode |= ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
ok = SetConsoleMode(gOutputStream, dwMode);
|
|
SysAssert(ok, "Couldn't set console mode");
|
|
|
|
// Set the output stream to use UTF-8
|
|
ok = SetConsoleOutputCP(CP_UTF8);
|
|
SysAssert(ok, "Couldn't maintain UTF-8 stdout stream");
|
|
}
|
|
}
|
|
|
|
// Set binary mode if redirecting stdin to user
|
|
if (gRuntimeConfig.console.enableStdPassthrough && gRuntimeConfig.console.enableStdOut)
|
|
{
|
|
SetConsoleMode(gOutputStream, 0);
|
|
}
|
|
|
|
if (gRuntimeConfig.console.enableStdPassthrough ^ gRuntimeConfig.console.enableStdIn)
|
|
{
|
|
ok = SetConsoleCP(CP_UTF8);
|
|
#if 0
|
|
SysAssert(ok, "Couldn't maintain UTF-8 stdin stream");
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
if (gRuntimeConfig.console.enableStdPassthrough && gRuntimeConfig.console.enableStdIn && isNotPipe)
|
|
{
|
|
if (!GetConsoleMode(gInputStream, &dwMode))
|
|
{
|
|
ok = SetConsoleMode(gInputStream, dwMode & ~(ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
|
|
SysAssert(ok, "Couldn't maintain binary stdin stream");
|
|
}
|
|
}
|
|
//else
|
|
{
|
|
gTerminateConsole = CreateEvent(nullptr, true, false, nullptr);
|
|
gWin32Thread = CreateThread(nullptr, 0, StdInWin32Thread, nullptr, 0, nullptr);
|
|
}
|
|
|
|
#elif defined(AURORA_IS_POSIX_DERIVED)
|
|
|
|
gInputStream = STDIN_FILENO;
|
|
gOutputStream = STDOUT_FILENO;
|
|
|
|
#endif
|
|
|
|
gLoopSource = AuMakeShared<ConsoleHasDataLoopSource>(
|
|
#if !defined(AURORA_IS_MODERNNT_DERIVED)
|
|
gInputStream
|
|
#endif
|
|
);
|
|
|
|
SysAssert(gInputStream != DEFAULT_HANDLE_VAL, "Couldn't allocate input stream handler");
|
|
SysAssert(gOutputStream != DEFAULT_HANDLE_VAL, "Couldn't allocate output stream handler");
|
|
|
|
StartLogger();
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
|
|
#if defined(AURORA_PLATFORM_WIN32)
|
|
|
|
if (GetConsoleWindow() == NULL)
|
|
{
|
|
if (!gRuntimeConfig.console.enableConsole)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gRuntimeConfig.console.forceConsoleWindow)
|
|
{
|
|
auto ok = AllocConsole();
|
|
SysAssert(ok, "Request of a Win32 console yielded nada, forceConsole wasn't respected");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Under windows, applications linked with the console subsystem flag should have output no matter what disableAll/forceConsoleWindow says
|
|
// The amount of effort it takes to link as console app for a conhost window far exceeds whatever 'forceConsoleWindow' is defined as
|
|
// One does not simply switch over to conapp for the fun of it. We can't or at least shouldn't destroy or hide conhost with hacks
|
|
// Assuming stdconsole is wanted - DO NOT RETURN - LINK AS A WINDOWED APP IF YOU DO NOT WANT A CONSOLE WINDOW
|
|
}
|
|
|
|
#elif defined(AURORA_IS_POSIX_DERIVED)
|
|
|
|
// no always means no under UNIX targets
|
|
// we *know* stdin/out will be available under these standards, dont fuck with what might be an IPC channel
|
|
if (!gRuntimeConfig.console.enableConsole)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
Start();
|
|
}
|
|
|
|
|
|
static AuUInt32 WriteStdOutBlocking(const void *data, AuUInt32 length)
|
|
{
|
|
if (!IS_STREAM_HANDLE_VALID(gOutputStream))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (length == 0)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
#if defined(IO_POSIX_STREAMS)
|
|
AuInt32 written = ::write(gOutputStream, data, length);
|
|
if (written < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
#elif defined(AURORA_IS_MODERNNT_DERIVED)
|
|
DWORD written;
|
|
if (!::WriteFile(gOutputStream, data, length, &written, NULL))
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
return written;
|
|
}
|
|
|
|
void FlushStdOutNb()
|
|
{
|
|
AuUInt32 written {};
|
|
written = WriteStdOutBlocking(gStdoutBuffer.begin(), gStdoutBuffer.size());
|
|
gStdoutBuffer.clear();
|
|
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::fsync(gOutputStream);
|
|
#endif
|
|
}
|
|
|
|
AuUInt32 WriteStdOut(const void *data, AuUInt32 length)
|
|
{
|
|
AU_LOCK_GUARD(gRingLock);
|
|
return gStdoutBuffer.Write(data, length);
|
|
}
|
|
|
|
AuUInt32 WriteStdOutBlocking2(const void *data, AuUInt32 length)
|
|
{
|
|
AU_LOCK_GUARD(gRingLock);
|
|
|
|
FlushStdOutNb();
|
|
|
|
return WriteStdOutBlocking(data, length);
|
|
}
|
|
|
|
void Lock()
|
|
{
|
|
gRingLock.Lock();
|
|
}
|
|
|
|
void Unlock()
|
|
{
|
|
FlushStdOutNb();
|
|
|
|
gRingLock.Unlock();
|
|
}
|
|
|
|
AuUInt32 WriteStdOutNonBlocking(const void *data, AuUInt32 length)
|
|
{
|
|
return gStdoutBuffer.Write(data, length);
|
|
}
|
|
|
|
static void FlushStdOut()
|
|
{
|
|
AU_LOCK_GUARD(gRingLock);
|
|
FlushStdOutNb();
|
|
}
|
|
|
|
void WriteStdOut(AuUInt8 level, const ConsoleMessage &msg)
|
|
{
|
|
auto writeLine = msg.ToConsole();
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
writeLine += '\r';
|
|
#endif
|
|
writeLine += '\n';
|
|
|
|
#if defined(IO_POSIX_STREAMS)
|
|
if (Locale::GetInternalCodePage() == Locale::ECodePage::eUTF8)
|
|
{
|
|
WriteStdOut(writeLine.data(), writeLine.size());
|
|
}
|
|
else
|
|
{
|
|
AuString slow;
|
|
slow.resize(writeLine.size() * 4);
|
|
auto len = Locale::Encoding::EncodeUTF8(writeLine, Aurora::Memory::MemoryViewWrite {slow.data(), slow.size()}, Locale::ECodePage::eSysUnk);
|
|
if (len.first != 0)
|
|
{
|
|
WriteStdOut(slow.data(), len.second);
|
|
}
|
|
else
|
|
{
|
|
// better write this than nothing
|
|
WriteStdOut(writeLine.data(), writeLine.size());
|
|
}
|
|
}
|
|
|
|
#elif defined(AURORA_IS_MODERNNT_DERIVED)
|
|
WriteStdOut(writeLine.data(), writeLine.size());
|
|
#endif
|
|
}
|
|
|
|
static bool InputStreamAvailable()
|
|
{
|
|
#if defined(IO_POSIX_STREAMS)
|
|
timeval tv {};
|
|
fd_set fds {};
|
|
FD_ZERO(&fds);
|
|
FD_SET(gInputStream, &fds);
|
|
select(gInputStream + 1, &fds, NULL, NULL, &tv);
|
|
return (FD_ISSET(0, &fds));
|
|
#elif defined(AURORA_IS_MODERNNT_DERIVED)
|
|
// Inline non-blocking is not legal on windows.
|
|
// One can force it with the overlapped flag, seemingly valid since win8, but defined as illegal since the 90s
|
|
return false;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
AuUInt32 ReadStdIn(void *data, AuUInt32 length)
|
|
{
|
|
Pump();
|
|
|
|
{
|
|
AU_LOCK_GUARD(gRingLock);
|
|
|
|
auto readable = AuMin(AuUInt32(length), AuUInt32(gEncodedIndex));
|
|
|
|
AuMemcpy(data, gLineEncodedBuffer, readable);
|
|
|
|
const auto remainingBytes = gEncodedIndex - readable;
|
|
if (remainingBytes)
|
|
{
|
|
AuMemmove(gLineEncodedBuffer, &gLineEncodedBuffer[readable], remainingBytes);
|
|
}
|
|
|
|
gEncodedIndex = remainingBytes;
|
|
|
|
if (gLoopSource && !gEncodedIndex)
|
|
{
|
|
gLoopSource->Reset();
|
|
}
|
|
|
|
return readable;
|
|
}
|
|
}
|
|
|
|
static void ProcessLines(AuList<AuString> &lines)
|
|
{
|
|
AuMach index = 0, startIdx = 0;
|
|
|
|
if (gLineIndex == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto end = gLineBuffer.data() + gLineIndex;
|
|
|
|
while (true)
|
|
{
|
|
auto a = std::find(gLineBuffer.data() + startIdx, end, '\n');
|
|
if (a == end) break;
|
|
index = a - gLineBuffer.data();
|
|
|
|
auto line = gLineBuffer.substr(startIdx, index - startIdx);
|
|
startIdx = index + 1;
|
|
|
|
if (line[line.size() - 1] == '\r')
|
|
{
|
|
line.pop_back();
|
|
}
|
|
|
|
if (line.size())
|
|
{
|
|
lines.push_back(line);
|
|
}
|
|
}
|
|
|
|
if (index != 0)
|
|
{
|
|
const auto remainingBytes = gLineIndex - startIdx;
|
|
if (remainingBytes)
|
|
{
|
|
AuMemmove(gLineBuffer.data(), &gLineBuffer.data()[startIdx], remainingBytes);
|
|
gLineIndex -= startIdx;
|
|
}
|
|
else
|
|
{
|
|
gLineIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ProcessLinesSysCP()
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
static Locale::Encoding::TextStreamEncoder stream(Locale::ECodePage::eUTF8);
|
|
#else
|
|
static Locale::Encoding::TextStreamEncoder stream(Locale::GetInternalCodePage());
|
|
#endif
|
|
|
|
AuList<AuString> lines;
|
|
|
|
{
|
|
AU_LOCK_GUARD(gRingLock);
|
|
|
|
auto ret = stream.DecodeUTF8(gLineEncodedBuffer, gEncodedIndex, gLineBuffer.data() + gLineIndex, gLineBuffer.size() - gLineIndex);
|
|
|
|
// increment backline buffer
|
|
{
|
|
const auto remainingBytes = gEncodedIndex - ret.first;
|
|
if (remainingBytes)
|
|
{
|
|
AuMemmove(gLineEncodedBuffer, &gLineEncodedBuffer[ret.first], remainingBytes);
|
|
}
|
|
|
|
gEncodedIndex = remainingBytes;
|
|
}
|
|
|
|
// increment frontline buffer
|
|
{
|
|
gLineIndex += ret.second;
|
|
}
|
|
|
|
if (gLoopSource)
|
|
{
|
|
gLoopSource->Set();
|
|
}
|
|
|
|
ProcessLines(lines);
|
|
}
|
|
|
|
if (lines.size())
|
|
{
|
|
for (const auto &line : lines)
|
|
{
|
|
Console::DispatchRawLine(line);
|
|
}
|
|
ConsoleTTY::OnEnter();
|
|
}
|
|
}
|
|
|
|
static AuUInt32 SyncReadConsole()
|
|
{
|
|
void *data = &gLineEncodedBuffer[gEncodedIndex];
|
|
auto length = kLineBufferMax - gEncodedIndex;
|
|
|
|
if (!IS_STREAM_HANDLE_VALID(gInputStream))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (length == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
|
|
DWORD read = length;
|
|
if (!ReadFile(gInputStream, data, read, &read, NULL))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
gRingLock.Lock();
|
|
gEncodedIndex += read;
|
|
gRingLock.Unlock();
|
|
|
|
return read;
|
|
|
|
#elif defined(IO_POSIX_STREAMS)
|
|
|
|
auto bread = ::read(gInputStream, data, length);
|
|
if (bread < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
gRingLock.Lock();
|
|
gEncodedIndex += bread;
|
|
gRingLock.Unlock();
|
|
|
|
return bread;
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
}
|
|
|
|
static bool AsyncReadAnyOrReadStreamBlock()
|
|
{
|
|
if (!SyncReadConsole())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (gRuntimeConfig.console.enableStdPassthrough && gRuntimeConfig.console.enableStdIn)
|
|
{
|
|
if (gLoopSource)
|
|
{
|
|
gLoopSource->Set();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ProcessLinesSysCP();
|
|
return true;
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
FlushStdOut();
|
|
}
|
|
|
|
void Pump()
|
|
{
|
|
FlushStdOut();
|
|
|
|
if (!gRuntimeConfig.console.enableStdPassthrough && !gRuntimeConfig.console.enableStdIn)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!IS_STREAM_HANDLE_VALID(gInputStream))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!InputStreamAvailable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
AsyncReadAnyOrReadStreamBlock();
|
|
}
|
|
|
|
void Exit()
|
|
{
|
|
if (IS_STREAM_HANDLE_VALID(gTerminateConsole))
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
SetEvent(gTerminateConsole);
|
|
#endif
|
|
}
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
|
|
gCanonicalEnabled = false;
|
|
|
|
SignalKillNT();
|
|
|
|
AuWin32CloseHandle(gTerminateConsole);
|
|
|
|
// Note: CloseHandle in the middle of a ReadFile blocks
|
|
#if !defined(AURORA_PLATFORM_WIN32)
|
|
AuWin32CloseHandle(gInputStream);
|
|
AuWin32CloseHandle(gOutputStream);
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
AuSPtr<AuLoop::ILoopSource> GetLoopSource()
|
|
{
|
|
return gLoopSource;
|
|
}
|
|
#else
|
|
|
|
void Pump()
|
|
{
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
}
|
|
|
|
void Exit()
|
|
{
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
}
|
|
|
|
AuSPtr<AuLoop::ILoopSource> GetLoopSource()
|
|
{
|
|
return {};
|
|
}
|
|
#endif
|
|
}
|