5142: Handle file/pipe stdout and add fallback on Windows r=def- a=Robyt3

The `CWindowsConsoleLogger` uses `WriteConsoleW` which only works when the standard output handle is a console (`GetFileType` returns `FILE_TYPE_CHAR`).

When redirecting output into a file, `GetFileType` returns `FILE_TYPE_DISK`. For this case a `CWindowsFileLogger` is added that uses `WriteFile` instead. This works on raw bytes, so the UTF-8 encoded lines are written directly.

When pipeing output or when using custom terminals like Git Bash, `GetFileType` returns `FILE_TYPE_PIPE`. I can't find any documentation how to correctly interact with a pipe stdout handle. So in this case, `WriteFile` is also used, because it works for me with cmd, PowerShell and Git Bash.

Lastly, if the output device is unknown, the default `CLoggerAsync` without ANSI colors is used as fallback.

This fixes output not working in Git Bash. Closes #4422. It also fixes output redirected with Git Bash containing additional line breaks.

For consistency, the `CWindowsConsoleLogger` now writes windows line endings also.

## Checklist

- [X] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2022-05-17 22:06:21 +00:00 committed by GitHub
commit 5a8cb4f398
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -298,14 +298,20 @@ static int color_hsv_to_windows_console_color(const ColorHSVA &Hsv)
class CWindowsConsoleLogger : public ILogger
{
HANDLE m_pConsole;
public:
CWindowsConsoleLogger(HANDLE pConsole) :
m_pConsole(pConsole)
{
}
void Log(const CLogMessage *pMessage) override
{
wchar_t *pWide = (wchar_t *)malloc((pMessage->m_LineLength + 1) * sizeof(*pWide));
wchar_t *pWide = (wchar_t *)malloc((pMessage->m_LineLength + 2) * sizeof(*pWide));
const char *p = pMessage->m_aLine;
int WLen = 0;
mem_zero(pWide, pMessage->m_LineLength * sizeof(*pWide));
mem_zero(pWide, (pMessage->m_LineLength + 2) * sizeof(*pWide));
for(int Codepoint = 0; (Codepoint = str_utf8_decode(&p)); WLen++)
{
@ -325,8 +331,8 @@ public:
mem_copy(&pWide[WLen], aU16, 2);
}
pWide[WLen] = '\n';
HANDLE pConsole = GetStdHandle(STD_OUTPUT_HANDLE);
pWide[WLen++] = '\r';
pWide[WLen++] = '\n';
int Color = 15;
if(pMessage->m_HaveColor)
{
@ -336,11 +342,26 @@ public:
Rgba.b = pMessage->m_Color.b / 255.0;
Color = color_hsv_to_windows_console_color(color_cast<ColorHSVA>(Rgba));
}
SetConsoleTextAttribute(pConsole, Color);
WriteConsoleW(pConsole, pWide, WLen + 1, NULL, NULL);
SetConsoleTextAttribute(m_pConsole, Color);
WriteConsoleW(m_pConsole, pWide, WLen, NULL, NULL);
free(pWide);
}
};
class CWindowsFileLogger : public ILogger
{
HANDLE m_pFile;
public:
CWindowsFileLogger(HANDLE pFile) :
m_pFile(pFile)
{
}
void Log(const CLogMessage *pMessage) override
{
WriteFile(m_pFile, pMessage->m_aLine, pMessage->m_LineLength, NULL, NULL);
WriteFile(m_pFile, "\r\n", 2, NULL, NULL);
}
};
#endif
std::unique_ptr<ILogger> log_logger_stdout()
@ -350,7 +371,14 @@ std::unique_ptr<ILogger> log_logger_stdout()
// https://github.com/termstandard/colors/tree/65bf0cd1ece7c15fa33a17c17528b02c99f1ae0b#checking-for-colorterm
return std::unique_ptr<ILogger>(new CLoggerAsync(io_stdout(), getenv("NO_COLOR") == nullptr, false));
#else
return std::unique_ptr<ILogger>(new CWindowsConsoleLogger());
HANDLE pOutput = GetStdHandle(STD_OUTPUT_HANDLE);
switch(GetFileType(pOutput))
{
case FILE_TYPE_CHAR: return std::unique_ptr<ILogger>(new CWindowsConsoleLogger(pOutput));
case FILE_TYPE_PIPE: // fall through, writing to pipe works the same as writing to a file
case FILE_TYPE_DISK: return std::unique_ptr<ILogger>(new CWindowsFileLogger(pOutput));
default: return std::unique_ptr<ILogger>(new CLoggerAsync(io_stdout(), false, false));
}
#endif
}