AuroraRuntime/Source/Console/ConsoleTTY/ConsoleTTY.NT.cpp

710 lines
18 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>
#if !defined(WINAPI_FAMILY_PARTITION) || \
WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
#define ENABLE_CONTTY
#endif
namespace Aurora::Console::ConsoleTTY
{
static HANDLE gConsole { INVALID_HANDLE_VALUE };
#if defined(ENABLE_CONTTY)
static COORD gSavedCoord {};
#endif
static bool gIsRecording {};
static AuMutex 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)
{
#if defined(ENABLE_CONTTY)
if (!pSetConsoleCursorPosition ||
!pGetConsoleScreenBufferInfo ||
!pFillConsoleOutputCharacterW)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYClearLine, bgColor);
HANDLE hConsole;
DWORD cCharsWritten;
CONSOLE_SCREEN_BUFFER_INFO csbi;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
if (!pFillConsoleOutputCharacterW(hConsole,
L' ',
csbi.dwSize.X,
COORD {0, csbi.dwCursorPosition.Y},
&cCharsWritten))
{
return;
}
if (!pFillConsoleOutputAttribute(hConsole,
bgColor == EAnsiColor::eEnumCount ? csbi.wAttributes : kAnsiColorBackgroundToNT[AuStaticCast<AuUInt>(bgColor)], // TODO: Why?
csbi.dwSize.X,
COORD {0, csbi.dwCursorPosition.Y},
&cCharsWritten))
{
return;
}
pSetConsoleCursorPosition(hConsole, {0, csbi.dwCursorPosition.Y});
#endif
}
AUKN_SYM void TTYClearScreen()
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo ||
!pFillConsoleOutputCharacterW ||
!pFillConsoleOutputAttribute)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYClearScreen);
HANDLE hConsole;
DWORD cCharsWritten;
CONSOLE_SCREEN_BUFFER_INFO csbi;
DWORD dwConSize;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
if (!pFillConsoleOutputCharacterW(hConsole,
L' ',
dwConSize,
COORD {0, 0},
&cCharsWritten))
{
return;
}
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
if (!pFillConsoleOutputAttribute(hConsole,
csbi.wAttributes,
dwConSize,
COORD {0, 0},
&cCharsWritten))
{
return;
}
pSetConsoleCursorPosition(hConsole, {0, 0});
#endif
}
AUKN_SYM void TTYFill(char character, EAnsiColor fgColor, EAnsiColor bgColor)
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo)
{
return;
}
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 (!pGetConsoleScreenBufferInfo(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 (!pFillConsoleOutputCharacterW(hConsole,
L' ',
csbi.dwSize.X * csbi.dwSize.Y,
COORD {0, 0},
&cCharsWritten))
{
return;
}
if (attrib)
{
if (!pFillConsoleOutputAttribute(hConsole,
attrib,
csbi.dwSize.X * csbi.dwSize.Y,
COORD {0, 0},
&cCharsWritten))
{
//return;
}
}
#endif
}
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)
{
#if defined(ENABLE_CONTTY)
if (!pSetConsoleTextAttribute)
{
ConsoleStd::WriteStdOutBlocking2(string, strlen(string));
return;
}
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 (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
attrib |= csbi.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
}
pSetConsoleTextAttribute(hConsole, attrib);
TTYWrite(string, AuUInt32(strlen(string)));
pSetConsoleTextAttribute(hConsole, FOREGROUND_WHITE);
#else
ConsoleStd::WriteStdOutBlocking2(string, strlen(string));
#endif
}
AUKN_SYM void TTYReturnHome()
{
#if defined(ENABLE_CONTTY)
if (!pSetConsoleCursorPosition)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYReturnHome);
pSetConsoleCursorPosition(GetTTYHandle(), {0, 0});
#endif
}
AUKN_SYM AuPair<AuUInt32, AuUInt32> TTYScreenSize()
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo)
{
return {};
}
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE hConsole;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return {};
}
return AuMakePair(AuStaticCast<AuUInt32>(csbi.srWindow.Right - csbi.srWindow.Left + 1),
AuStaticCast<AuUInt32>(csbi.srWindow.Bottom - csbi.srWindow.Top + 1));
#else
return {};
#endif
}
AUKN_SYM void TTYStorePos()
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYStorePos);
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!pGetConsoleScreenBufferInfo(GetTTYHandle(), &csbi))
{
return;
}
gSavedCoord = csbi.dwCursorPosition;
#endif
}
AUKN_SYM void TTYRestorePos()
{
#if defined(ENABLE_CONTTY)
if (!pSetConsoleCursorPosition)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYRestorePos);
pSetConsoleCursorPosition(GetTTYHandle(), gSavedCoord);
#endif
}
AUKN_SYM void TTYMoveY(AuInt16 lines)
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo ||
!pSetConsoleCursorPosition)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYMoveY, lines);
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE hConsole;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
csbi.dwCursorPosition.Y += lines;
pSetConsoleCursorPosition(hConsole, gSavedCoord);
#endif
}
AUKN_SYM void TTYMoveX(AuInt16 lines)
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo ||
!pSetConsoleCursorPosition)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYMoveX, lines);
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE hConsole;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
csbi.dwCursorPosition.X += lines;
pSetConsoleCursorPosition(hConsole, gSavedCoord);
#endif
}
AUKN_SYM void TTYSetY(AuUInt16 Y)
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo ||
!pSetConsoleCursorPosition)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYSetY, Y);
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE hConsole;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
csbi.dwCursorPosition.Y = Y;
pSetConsoleCursorPosition(hConsole, gSavedCoord);
#endif
}
AUKN_SYM void TTYSetX(AuUInt16 X)
{
#if defined(ENABLE_CONTTY)
if (!pGetConsoleScreenBufferInfo ||
!pSetConsoleCursorPosition)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYSetX, X);
CONSOLE_SCREEN_BUFFER_INFO csbi;
HANDLE hConsole;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(hConsole, &csbi))
{
return;
}
csbi.dwCursorPosition.X = X;
pSetConsoleCursorPosition(hConsole, gSavedCoord);
#endif
}
AUKN_SYM void TTYSetPos(AuPair<AuUInt32, AuUInt32> position)
{
#if defined(ENABLE_CONTTY)
if (!pSetConsoleCursorPosition)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYSetPos, position);
pSetConsoleCursorPosition(GetTTYHandle(), COORD {AuStaticCast<short>(position.first), AuStaticCast<short>(position.second)});
#endif
}
AUKN_SYM void TTYScrollBuffer(int Y)
{
#if defined(ENABLE_CONTTY)
if (!pScrollConsoleScreenBufferW)
{
return;
}
TTY_RECORD_FOR_FLIP(TTYScrollBuffer, Y);
CONSOLE_SCREEN_BUFFER_INFO csbi;
SMALL_RECT srctScrollRect, srctClipRect;
HANDLE hConsole;
COORD coordDest;
hConsole = GetTTYHandle();
if (!pGetConsoleScreenBufferInfo(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 = ' ';
pScrollConsoleScreenBufferW(hConsole,
&srctScrollRect,
&srctClipRect,
coordDest,
&chiFill);
#endif
}
HANDLE GetTTYHandle()
{
#if defined(ENABLE_CONTTY)
if (gConsole != INVALID_HANDLE_VALUE)
{
return gConsole;
}
return gConsole = GetStdHandle(STD_OUTPUT_HANDLE);
#else
return INVALID_HANDLE_VALUE;
#endif
}
static bool AU_NOINLINE HasFBChanged(CONSOLE_SCREEN_BUFFER_INFO &csbi)
{
#if defined(ENABLE_CONTTY)
auto &curConsole = gConsoles[(gConsoleIndex ) % 2];
if (!pGetConsoleScreenBufferInfo)
{
return false;
}
if (!pGetConsoleScreenBufferInfo(gConsoleHandle, &csbi))
{
if (!pGetConsoleScreenBufferInfo(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)};
pSetConsoleScreenBufferSize(gConsoles[0].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
pSetConsoleScreenBufferSize(gConsoles[1].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
pSetConsoleWindowInfo(gConsoles[0].h, true, &screen);
pSetConsoleWindowInfo(gConsoles[1].h, true, &screen);
return true;
}
if (((res.first == curConsole.width &&
res.second == curConsole.height)))
{
return false;
}
return true;
#else
return false;
#endif
}
bool AU_NOINLINE InitConsoles()
{
#if defined(ENABLE_CONTTY)
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!pSetConsoleScreenBufferSize ||
!pSetConsoleWindowInfo)
{
return false;
}
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 = pCreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CONSOLE_TEXTMODE_BUFFER, nullptr);
auto b = pCreateConsoleScreenBuffer(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)};
pSetConsoleScreenBufferSize(gConsoles[0].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
pSetConsoleScreenBufferSize(gConsoles[1].h, COORD {(AuInt16)res.first, (AuInt16)res.second});
pSetConsoleWindowInfo(gConsoles[0].h, true, &screen);
pSetConsoleWindowInfo(gConsoles[1].h, true, &screen);
gConsoles[0].width = gConsoles[1].width = res.first;
gConsoles[0].height = gConsoles[1].height = res.second;
return true;
#else
return false;
#endif
}
bool WarmBuffering()
{
#if defined(ENABLE_CONTTY)
return InitConsoles();
#else
return false;
#endif
}
void BeginBuffering()
{
#if defined(ENABLE_CONTTY)
if (!pSetConsoleActiveScreenBuffer)
{
return;
}
InitConsoles();
gConsoleIndex++;
gIsRecording = true;
pSetConsoleActiveScreenBuffer(gConsoleHandle = gConsoles[(gConsoleIndex + 1) % 2].h);
gConsole = gConsoles[gConsoleIndex % 2].h;
#else
#endif
}
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()
{
#if defined(ENABLE_CONTTY)
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (HasFBChanged(csbi))
{
gRecordedActions.clear();
return {};
}
return true;
#else
return false;
#endif
}
bool EndBuffering()
{
#if defined(ENABLE_CONTTY)
if (!pSetConsoleActiveScreenBuffer)
{
return false;
}
if (!IdkMan())
{
gIsRecording = false;
return false;
}
pSetConsoleActiveScreenBuffer(gConsoleHandle = gConsoles[(gConsoleIndex) % 2].h);
gConsole = gConsoles[(gConsoleIndex + 1) % 2].h;
gIsRecording = false;
RepeatRecord();
#endif
return true;
}
bool IsBuffering()
{
return gIsRecording;
}
}