/*** 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 #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> 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(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(fgColor)]; } else if (bgColor != EAnsiColor::eEnumCount) { attrib = FOREGROUND_WHITE; } if (bgColor != EAnsiColor::eEnumCount) { attrib |= kAnsiColorBackgroundToNT[AuStaticCast(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(fgColor)]; } else { attrib = FOREGROUND_WHITE; } if (bgColor != EAnsiColor::eEnumCount) { attrib |= kAnsiColorBackgroundToNT[AuStaticCast(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 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(csbi.srWindow.Right - csbi.srWindow.Left + 1), AuStaticCast(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 position) { #if defined(ENABLE_CONTTY) if (!pSetConsoleCursorPosition) { return; } TTY_RECORD_FOR_FLIP(TTYSetPos, position); pSetConsoleCursorPosition(GetTTYHandle(), COORD {AuStaticCast(position.first), AuStaticCast(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(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 = ' '; 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(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)}; 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(csbi.srWindow.Right - csbi.srWindow.Left + 1), AuStaticCast(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 &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; } }