/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. Date: 2022-5-2 Author: Reece ***/ #include #include #include "ConsoleTTY.NT.hpp" #include #include namespace Aurora::Console::ConsoleTTY { static HANDLE gConsole {INVALID_HANDLE_VALUE}; static COORD gSavedCoord {}; static bool gIsRecording {}; static AuThreadPrimitives::SpinLock gRecordLock; static AuList> 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(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(fgColor)]; } else if (bgColor != EAnsiColor::eEnumCount) { attrib = FOREGROUND_WHITE; } if (bgColor != EAnsiColor::eEnumCount) { attrib |= kAnsiColorBackgroundToNT[AuStaticCast(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(fgColor)]; } else { attrib = FOREGROUND_WHITE; } if (bgColor != EAnsiColor::eEnumCount) { attrib |= kAnsiColorBackgroundToNT[AuStaticCast(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 TTYScreenSize() { CONSOLE_SCREEN_BUFFER_INFO csbi; HANDLE hConsole; hConsole = GetTTYHandle(); if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) { return {}; } return AuMakePair(AuStaticCast(csbi.srWindow.Right - csbi.srWindow.Left + 1), AuStaticCast(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 position) { TTY_RECORD_FOR_FLIP(TTYSetPos, position); SetConsoleCursorPosition(GetTTYHandle(), COORD {AuStaticCast(position.first), AuStaticCast(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(csbi.srWindow.Right - csbi.srWindow.Left + 1), AuStaticCast(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(csbi.srWindow.Right - csbi.srWindow.Left + 1), AuStaticCast(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(csbi.srWindow.Right - csbi.srWindow.Left + 1), AuStaticCast(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 &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; } }