Jamie Reece Wilson
ca2c0462ab
[*] NT: Fix missing CoTaskMemFree leak on startup [*] Fix ConsoleStd restart bug
546 lines
14 KiB
C++
546 lines
14 KiB
C++
/***
|
|
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
Date: 2022-5-2
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include <Source/Console/Console.hpp>
|
|
#include "ConsoleTTY.NT.hpp"
|
|
#include <Source/Console/ColorConvert.hpp>
|
|
#include <Source/Console/ConsoleStd/ConsoleStd.hpp>
|
|
|
|
namespace Aurora::Console::ConsoleTTY
|
|
{
|
|
static HANDLE gConsole {INVALID_HANDLE_VALUE};
|
|
static COORD gSavedCoord {};
|
|
|
|
static bool gIsRecording {};
|
|
static AuThreadPrimitives::Mutex gRecordLock;
|
|
static AuList<AuFunction<void()>> gRecordedActions {};
|
|
|
|
struct Console
|
|
{
|
|
HANDLE h {INVALID_HANDLE_VALUE};
|
|
int width, height;
|
|
};
|
|
|
|
static Console gConsoles[2];
|
|
static AuUInt32 gConsoleIndex {0};
|
|
static HANDLE gConsoleHandle {INVALID_HANDLE_VALUE};
|
|
|
|
#define TTY_RECORD_FOR_FLIP(func, ...) \
|
|
if (gIsRecording) \
|
|
{ \
|
|
AU_LOCK_GUARD(gRecordLock); \
|
|
gRecordedActions.push_back(std::bind(func, ## __VA_ARGS__)); \
|
|
}
|
|
|
|
void GetConsoleHandles(HANDLE(&handles)[2])
|
|
{
|
|
handles[0] = gConsoles[0].h;
|
|
handles[1] = gConsoles[1].h;
|
|
}
|
|
|
|
AUKN_SYM void SuperSecretTTYReplacer(HANDLE console)
|
|
{
|
|
gConsole = console;
|
|
}
|
|
|
|
AUKN_SYM void TTYClearLine(EAnsiColor bgColor)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYClearLine, bgColor);
|
|
|
|
HANDLE hConsole;
|
|
DWORD cCharsWritten;
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!FillConsoleOutputCharacterW(hConsole,
|
|
L' ',
|
|
csbi.dwSize.X,
|
|
COORD {0, csbi.dwCursorPosition.Y},
|
|
&cCharsWritten))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!FillConsoleOutputAttribute(hConsole,
|
|
bgColor == EAnsiColor::eEnumCount ? csbi.wAttributes : kAnsiColorBackgroundToNT[AuStaticCast<AuUInt>(bgColor)],
|
|
csbi.dwSize.X,
|
|
COORD {0, csbi.dwCursorPosition.Y},
|
|
&cCharsWritten))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetConsoleCursorPosition(hConsole, {0, csbi.dwCursorPosition.Y});
|
|
}
|
|
|
|
AUKN_SYM void TTYClearScreen()
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYClearScreen);
|
|
|
|
HANDLE hConsole;
|
|
DWORD cCharsWritten;
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
DWORD dwConSize;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
|
|
|
|
if (!FillConsoleOutputCharacterW(hConsole,
|
|
L' ',
|
|
dwConSize,
|
|
COORD {0, 0},
|
|
&cCharsWritten))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!FillConsoleOutputAttribute(hConsole,
|
|
csbi.wAttributes,
|
|
dwConSize,
|
|
COORD {0, 0},
|
|
&cCharsWritten))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetConsoleCursorPosition(hConsole, {0, 0});
|
|
}
|
|
|
|
AUKN_SYM void TTYFill(char character, EAnsiColor fgColor, EAnsiColor bgColor)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYFill, character, fgColor, bgColor);
|
|
|
|
DWORD attrib {0}, cCharsWritten {};
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
HANDLE hConsole;
|
|
|
|
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (fgColor != EAnsiColor::eEnumCount)
|
|
{
|
|
attrib |= kAnsiColorForegroundToNT[AuStaticCast<AuUInt>(fgColor)];
|
|
}
|
|
else if (bgColor != EAnsiColor::eEnumCount)
|
|
{
|
|
attrib = FOREGROUND_WHITE;
|
|
}
|
|
|
|
if (bgColor != EAnsiColor::eEnumCount)
|
|
{
|
|
attrib |= kAnsiColorBackgroundToNT[AuStaticCast<AuUInt>(bgColor)];
|
|
}
|
|
|
|
if (!FillConsoleOutputCharacterW(hConsole,
|
|
L' ',
|
|
csbi.dwSize.X * csbi.dwSize.Y,
|
|
COORD {0, 0},
|
|
&cCharsWritten))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (attrib)
|
|
{
|
|
if (!FillConsoleOutputAttribute(hConsole,
|
|
attrib,
|
|
csbi.dwSize.X * csbi.dwSize.Y,
|
|
COORD {0, 0},
|
|
&cCharsWritten))
|
|
{
|
|
//return;
|
|
}
|
|
}
|
|
}
|
|
|
|
AUKN_SYM AuUInt32 TTYWrite(const void *buffer, AuUInt32 length)
|
|
{
|
|
// no record required
|
|
return ConsoleStd::WriteStdOutBlocking2(buffer, length);
|
|
}
|
|
|
|
AUKN_SYM void TTYWrite(const char *string, EAnsiColor fgColor, EAnsiColor bgColor)
|
|
{
|
|
TTY_RECORD_FOR_FLIP((void(*)(const char *, EAnsiColor, EAnsiColor))(TTYWrite), string, fgColor, bgColor);
|
|
|
|
DWORD attrib {};
|
|
HANDLE hConsole;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (fgColor != EAnsiColor::eEnumCount)
|
|
{
|
|
attrib |= kAnsiColorForegroundToNT[AuStaticCast<AuUInt>(fgColor)];
|
|
}
|
|
else
|
|
{
|
|
attrib = FOREGROUND_WHITE;
|
|
}
|
|
|
|
if (bgColor != EAnsiColor::eEnumCount)
|
|
{
|
|
attrib |= kAnsiColorBackgroundToNT[AuStaticCast<AuUInt>(bgColor)];
|
|
}
|
|
else
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
attrib |= csbi.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
|
|
}
|
|
|
|
SetConsoleTextAttribute(hConsole, attrib);
|
|
TTYWrite(string, AuUInt32(strlen(string)));
|
|
SetConsoleTextAttribute(hConsole, FOREGROUND_WHITE);
|
|
}
|
|
|
|
AUKN_SYM void TTYReturnHome()
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYReturnHome);
|
|
|
|
SetConsoleCursorPosition(GetTTYHandle(), {0, 0});
|
|
}
|
|
|
|
AUKN_SYM AuPair<AuUInt32, AuUInt32> TTYScreenSize()
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
HANDLE hConsole;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
return AuMakePair(AuStaticCast<AuUInt32>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
|
|
AuStaticCast<AuUInt32>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1));
|
|
}
|
|
|
|
AUKN_SYM void TTYStorePos()
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYStorePos);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
|
|
if (!GetConsoleScreenBufferInfo(GetTTYHandle(), &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
gSavedCoord = csbi.dwCursorPosition;
|
|
}
|
|
|
|
AUKN_SYM void TTYRestorePos()
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYRestorePos);
|
|
|
|
SetConsoleCursorPosition(GetTTYHandle(), gSavedCoord);
|
|
}
|
|
|
|
AUKN_SYM void TTYMoveY(AuInt16 lines)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYMoveY, lines);
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
|
|
HANDLE hConsole;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
csbi.dwCursorPosition.Y += lines;
|
|
SetConsoleCursorPosition(hConsole, gSavedCoord);
|
|
}
|
|
|
|
AUKN_SYM void TTYMoveX(AuInt16 lines)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYMoveX, lines);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
HANDLE hConsole;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
csbi.dwCursorPosition.X += lines;
|
|
SetConsoleCursorPosition(hConsole, gSavedCoord);
|
|
}
|
|
|
|
AUKN_SYM void TTYSetY(AuUInt16 Y)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYSetY, Y);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
HANDLE hConsole;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
csbi.dwCursorPosition.Y = Y;
|
|
SetConsoleCursorPosition(hConsole, gSavedCoord);
|
|
}
|
|
|
|
AUKN_SYM void TTYSetX(AuUInt16 X)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYSetX, X);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
HANDLE hConsole;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
csbi.dwCursorPosition.X = X;
|
|
SetConsoleCursorPosition(hConsole, gSavedCoord);
|
|
}
|
|
|
|
AUKN_SYM void TTYSetPos(AuPair<AuUInt32, AuUInt32> position)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYSetPos, position);
|
|
|
|
SetConsoleCursorPosition(GetTTYHandle(), COORD {AuStaticCast<short>(position.first), AuStaticCast<short>(position.second)});
|
|
}
|
|
|
|
AUKN_SYM void TTYScrollBuffer(int Y)
|
|
{
|
|
TTY_RECORD_FOR_FLIP(TTYScrollBuffer, Y);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
SMALL_RECT srctScrollRect, srctClipRect;
|
|
HANDLE hConsole;
|
|
COORD coordDest;
|
|
|
|
hConsole = GetTTYHandle();
|
|
|
|
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto res = AuMakePair(AuStaticCast<AuUInt32>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
|
|
AuStaticCast<AuUInt32>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1));
|
|
|
|
srctScrollRect.Top = 0;
|
|
srctScrollRect.Bottom = csbi.dwSize.Y - 1;
|
|
srctScrollRect.Left = 0;
|
|
srctScrollRect.Right = csbi.dwSize.X - 1;
|
|
|
|
coordDest.X = 0;
|
|
coordDest.Y = -Y;
|
|
|
|
srctClipRect = srctScrollRect;
|
|
|
|
CHAR_INFO chiFill;
|
|
chiFill.Attributes = BACKGROUND_BLACK | FOREGROUND_WHITE;
|
|
chiFill.Char.UnicodeChar = ' ';
|
|
|
|
ScrollConsoleScreenBufferW(
|
|
hConsole,
|
|
&srctScrollRect,
|
|
&srctClipRect,
|
|
coordDest,
|
|
&chiFill);
|
|
}
|
|
|
|
HANDLE GetTTYHandle()
|
|
{
|
|
if (gConsole != INVALID_HANDLE_VALUE)
|
|
{
|
|
return gConsole;
|
|
}
|
|
|
|
return gConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
}
|
|
|
|
static bool AU_NOINLINE HasFBChanged(CONSOLE_SCREEN_BUFFER_INFO &csbi)
|
|
{
|
|
auto &curConsole = gConsoles[(gConsoleIndex ) % 2];
|
|
|
|
if (!GetConsoleScreenBufferInfo(gConsoleHandle, &csbi))
|
|
{
|
|
if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
auto res = AuMakePair(AuStaticCast<AuUInt32>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
|
|
AuStaticCast<AuUInt32>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1));
|
|
|
|
if ((res.first != csbi.dwSize.X) || (res.second != csbi.dwSize.Y))
|
|
{
|
|
if ((res.first != curConsole.width) ||
|
|
(res.second != curConsole.height))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
SMALL_RECT screen = {0, 0, AuInt16(res.first - 1), AuInt16(res.second - 1)};
|
|
SetConsoleScreenBufferSize(gConsoles[0].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
|
|
SetConsoleScreenBufferSize(gConsoles[1].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
|
|
|
|
SetConsoleWindowInfo(gConsoles[0].h, true, &screen);
|
|
SetConsoleWindowInfo(gConsoles[1].h, true, &screen);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (((res.first == curConsole.width &&
|
|
res.second == curConsole.height)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AU_NOINLINE InitConsoles()
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
|
|
if (!HasFBChanged(csbi))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
auto res = AuMakePair(AuStaticCast<AuUInt32>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
|
|
AuStaticCast<AuUInt32>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1));
|
|
|
|
if (gConsoles[0].h == INVALID_HANDLE_VALUE)
|
|
{
|
|
auto a = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CONSOLE_TEXTMODE_BUFFER, nullptr);
|
|
auto b = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CONSOLE_TEXTMODE_BUFFER, nullptr);
|
|
|
|
gConsoles[0].h = a;
|
|
gConsoles[1].h = b;
|
|
}
|
|
|
|
|
|
SMALL_RECT screen = {0, 0, AuInt16(res.first - 1), AuInt16(res.second - 1)};
|
|
SetConsoleScreenBufferSize(gConsoles[0].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
|
|
SetConsoleScreenBufferSize(gConsoles[1].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
|
|
|
|
SetConsoleWindowInfo(gConsoles[0].h, true, &screen);
|
|
SetConsoleWindowInfo(gConsoles[1].h, true, &screen);
|
|
|
|
gConsoles[0].width = gConsoles[1].width = res.first;
|
|
gConsoles[0].height = gConsoles[1].height = res.second;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WarmBuffering()
|
|
{
|
|
return InitConsoles();
|
|
}
|
|
|
|
void BeginBuffering()
|
|
{
|
|
InitConsoles();
|
|
|
|
gConsoleIndex++;
|
|
gIsRecording = true;
|
|
|
|
SetConsoleActiveScreenBuffer(gConsoleHandle = gConsoles[(gConsoleIndex + 1) % 2].h);
|
|
gConsole = gConsoles[gConsoleIndex % 2].h;
|
|
}
|
|
|
|
void RecordFunction(const AuFunction<void()> &func)
|
|
{
|
|
if (gIsRecording)
|
|
{
|
|
AU_LOCK_GUARD(gRecordLock);
|
|
gRecordedActions.push_back(func);
|
|
}
|
|
}
|
|
|
|
static void RepeatRecord()
|
|
{
|
|
AU_LOCK_GUARD(gRecordLock);
|
|
|
|
for (auto action : AuExchange(gRecordedActions, {}))
|
|
{
|
|
action();
|
|
}
|
|
}
|
|
|
|
static bool IdkMan()
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
|
|
if (HasFBChanged(csbi))
|
|
{
|
|
gRecordedActions.clear();
|
|
return {};
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EndBuffering()
|
|
{
|
|
if (!IdkMan())
|
|
{
|
|
gIsRecording = false;
|
|
return false;
|
|
}
|
|
|
|
SetConsoleActiveScreenBuffer(gConsoleHandle = gConsoles[(gConsoleIndex) % 2].h);
|
|
gConsole = gConsoles[(gConsoleIndex + 1) % 2].h;
|
|
gIsRecording = false;
|
|
RepeatRecord();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsBuffering()
|
|
{
|
|
return gIsRecording;
|
|
}
|
|
} |