Log: Enable virtual terminal processing on Win32

This way we don't need to set the registry value.
This commit is contained in:
Connor McLaughlin 2021-01-09 00:32:44 +10:00
parent b0398f5aa7
commit 9951921a6d

View file

@ -135,7 +135,7 @@ static void FormatLogMessageForDisplay(const char* channelName, const char* func
#include <io.h> #include <io.h>
#define STDOUT_FILENO (_fileno(stdout)) #define STDOUT_FILENO (_fileno(stdout))
#define STDERR_FILENO (_fileno(stderr)) #define STDERR_FILENO (_fileno(stderr))
#define write(fd, buf,count) _write(fd,buf,(int)count) #define write(fd, buf, count) _write(fd, buf, (int)count)
#endif #endif
static void StandardOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, static void StandardOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName,
@ -158,7 +158,8 @@ static void StandardOutputLogCallback(void* pUserParam, const char* channelName,
"\033[0;34m", // TRACE "\033[0;34m", // TRACE
}; };
if (int outputFd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO; outputFd >= 0) { if (int outputFd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO; outputFd >= 0)
{
write(outputFd, colorCodes[level], std::strlen(colorCodes[level])); write(outputFd, colorCodes[level], std::strlen(colorCodes[level]));
Log::FormatLogMessageForDisplay( Log::FormatLogMessageForDisplay(
@ -171,7 +172,6 @@ static void StandardOutputLogCallback(void* pUserParam, const char* channelName,
} }
} }
#if defined(_WIN32) #if defined(_WIN32)
static bool s_msw_console_allocated = false; static bool s_msw_console_allocated = false;
static HANDLE s_msw_prev_stdin = {}; static HANDLE s_msw_prev_stdin = {};
@ -180,7 +180,7 @@ static HANDLE s_msw_prev_stderr = {};
#include <fcntl.h> #include <fcntl.h>
void msw_ReopenStandardPipes() static void msw_ReopenStandardPipes()
{ {
if (s_msw_console_allocated) if (s_msw_console_allocated)
return; return;
@ -195,9 +195,18 @@ void msw_ReopenStandardPipes()
// open outputs as binary to suppress Windows newline corruption (\r mess) // open outputs as binary to suppress Windows newline corruption (\r mess)
std::FILE* fp; std::FILE* fp;
if (!s_msw_prev_stdin ) { freopen_s(&fp, "CONIN$", "r" , stdin ); } if (!s_msw_prev_stdin)
if (!s_msw_prev_stdout ) { freopen_s(&fp, "CONOUT$", "wb", stdout); } {
if (!s_msw_prev_stderr ) { freopen_s(&fp, "CONOUT$", "wb", stderr); } freopen_s(&fp, "CONIN$", "r", stdin);
}
if (!s_msw_prev_stdout)
{
freopen_s(&fp, "CONOUT$", "wb", stdout);
}
if (!s_msw_prev_stderr)
{
freopen_s(&fp, "CONOUT$", "wb", stderr);
}
// Windows Console Oddities - The only way to get windows built-in console is to render UTF chars from // Windows Console Oddities - The only way to get windows built-in console is to render UTF chars from
// the correct alt. fonts is to set either _O_U8TEXT or _O_U16TEXT. However, this imposes a requirement // the correct alt. fonts is to set either _O_U8TEXT or _O_U16TEXT. However, this imposes a requirement
@ -213,7 +222,7 @@ void msw_ReopenStandardPipes()
//_setmode(_fileno(stderr), _O_U8TEXT); //_setmode(_fileno(stderr), _O_U8TEXT);
} }
void msw_FreeLegacyConsole() static void msw_FreeLegacyConsole()
{ {
if (!s_msw_console_allocated) if (!s_msw_console_allocated)
return; return;
@ -221,27 +230,45 @@ void msw_FreeLegacyConsole()
s_msw_console_allocated = false; s_msw_console_allocated = false;
// restore previous handles prior to creating the console. // restore previous handles prior to creating the console.
::SetStdHandle(STD_INPUT_HANDLE , s_msw_prev_stdin ); ::SetStdHandle(STD_INPUT_HANDLE, s_msw_prev_stdin);
::SetStdHandle(STD_OUTPUT_HANDLE, s_msw_prev_stdout); ::SetStdHandle(STD_OUTPUT_HANDLE, s_msw_prev_stdout);
::SetStdHandle(STD_ERROR_HANDLE , s_msw_prev_stderr); ::SetStdHandle(STD_ERROR_HANDLE, s_msw_prev_stderr);
::FreeConsole(); ::FreeConsole();
} }
bool msw_AttachLegacyConsole() static bool msw_AttachLegacyConsole()
{ {
if (::AttachConsole(ATTACH_PARENT_PROCESS)) if (::AttachConsole(ATTACH_PARENT_PROCESS))
return true; return true;
// ERROR_ACCESS_DENIED means a windows Console is already attached. // ERROR_ACCESS_DENIED means a windows Console is already attached.
if (auto err = ::GetLastError(); err == ERROR_ACCESS_DENIED) { if (auto err = ::GetLastError(); err == ERROR_ACCESS_DENIED)
{
return true; return true;
} }
return false; return false;
} }
static bool msw_EnableVirtualTerminalProcessing()
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if (!hConsole)
return false;
DWORD old_mode;
if (!GetConsoleMode(hConsole, &old_mode))
return false;
// already enabled?
if (old_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
return true;
return SetConsoleMode(hConsole, old_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
// Creates an old-fashioned console window. // Creates an old-fashioned console window.
bool msw_AllocLegacyConsole() static bool msw_AllocLegacyConsole()
{ {
// A potentially fancy solution which I haven't had time to experiment with yet is to spawn our own // A potentially fancy solution which I haven't had time to experiment with yet is to spawn our own
// terminal application and bind our standard pipes to it, instead of using AllocConsole(). This would // terminal application and bind our standard pipes to it, instead of using AllocConsole(). This would
@ -249,13 +276,15 @@ bool msw_AllocLegacyConsole()
// than the windows legacy console (but would also depend on the user having them installed and PATH // than the windows legacy console (but would also depend on the user having them installed and PATH
// accessible, so definitely not without annoying caveats) --jstine // accessible, so definitely not without annoying caveats) --jstine
if (!::AllocConsole()) { if (!::AllocConsole())
{
// Console could fail to allocate on an Appveyor/Jenkins environment, for example, because // Console could fail to allocate on an Appveyor/Jenkins environment, for example, because
// when being run as a service the console may be unable to bind itself to a user login session. // when being run as a service the console may be unable to bind itself to a user login session.
// It may also fail if a console is already allocated <-- this is a problem since in this case // It may also fail if a console is already allocated <-- this is a problem since in this case
// we still want to set // we still want to set
if (auto err = ::GetLastError(); err == ERROR_ACCESS_DENIED) { if (auto err = ::GetLastError(); err == ERROR_ACCESS_DENIED)
{
// ERROR_ACCESS_DENIED means a windows Console is already attached. // ERROR_ACCESS_DENIED means a windows Console is already attached.
// whatever the console is, who knows, so let's early-out, and not mess with its font settings. // whatever the console is, who knows, so let's early-out, and not mess with its font settings.
return true; return true;
@ -265,8 +294,8 @@ bool msw_AllocLegacyConsole()
return true; return true;
} }
static void msw_DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, static void msw_DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName,
const char* message) LOGLEVEL level, const char* message)
{ {
FormatLogMessageForDisplay( FormatLogMessageForDisplay(
channelName, functionName, level, message, [](const char* text, void*) { OutputDebugStringA(text); }, nullptr); channelName, functionName, level, message, [](const char* text, void*) { OutputDebugStringA(text); }, nullptr);
@ -276,8 +305,8 @@ static void msw_DebugOutputLogCallback(void* pUserParam, const char* channelName
#endif #endif
#if defined(__ANDROID__) #if defined(__ANDROID__)
static void android_DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, static void android_DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName,
const char* message) LOGLEVEL level, const char* message)
{ {
static const int logPriority[LOGLEVEL_COUNT] = { static const int logPriority[LOGLEVEL_COUNT] = {
ANDROID_LOG_INFO, // NONE ANDROID_LOG_INFO, // NONE
@ -311,7 +340,6 @@ static void DebugOutputLogCallback(void* pUserParam, const char* channelName, co
#endif #endif
} }
void SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEVEL LevelFilter) void SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEVEL LevelFilter)
{ {
s_consoleOutputChannelFilter = (ChannelFilter != NULL) ? ChannelFilter : ""; s_consoleOutputChannelFilter = (ChannelFilter != NULL) ? ChannelFilter : "";
@ -328,7 +356,8 @@ void SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEVEL Le
UnregisterCallback(StandardOutputLogCallback, NULL); UnregisterCallback(StandardOutputLogCallback, NULL);
#if defined(_WIN32) && !defined(_CONSOLE) #if defined(_WIN32) && !defined(_CONSOLE)
if (Enabled) { if (Enabled)
{
// Windows Console behavior is very tricky, and depends on: // Windows Console behavior is very tricky, and depends on:
// - Whether the application is built with defined(_CONSOLE) or not. // - Whether the application is built with defined(_CONSOLE) or not.
// - Whether the application is started via a Microsoft shell (Cmd.exe) or a Unix'y shell // - Whether the application is started via a Microsoft shell (Cmd.exe) or a Unix'y shell
@ -356,12 +385,15 @@ void SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEVEL Le
// it, even for console apps, because actually we DON'T want the console window popping up // it, even for console apps, because actually we DON'T want the console window popping up
// every time we run some console app in the background. --jstine // every time we run some console app in the background. --jstine
s_msw_prev_stdin = ::GetStdHandle(STD_INPUT_HANDLE ); s_msw_prev_stdin = ::GetStdHandle(STD_INPUT_HANDLE);
s_msw_prev_stdout = ::GetStdHandle(STD_OUTPUT_HANDLE); s_msw_prev_stdout = ::GetStdHandle(STD_OUTPUT_HANDLE);
s_msw_prev_stderr = ::GetStdHandle(STD_ERROR_HANDLE ); s_msw_prev_stderr = ::GetStdHandle(STD_ERROR_HANDLE);
if (!s_msw_prev_stdout || !s_msw_prev_stdin) { if (!s_msw_prev_stdout || !s_msw_prev_stdin)
if (msw_AttachLegacyConsole() || msw_AllocLegacyConsole()) { {
if (msw_AttachLegacyConsole() || msw_AllocLegacyConsole())
{
msw_EnableVirtualTerminalProcessing();
msw_ReopenStandardPipes(); msw_ReopenStandardPipes();
} }
} }