//======================================================================== // // Win32Console.cc // // This file is licensed under the GPLv2 or later // // Copyright (C) 2017, 2024 Adrian Johnson // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git // //======================================================================== #ifdef _WIN32 # include "goo/gmem.h" # include "UTF.h" # define WIN32_CONSOLE_IMPL # include "Win32Console.h" # include # include static const int BUF_SIZE = 4096; static int bufLen = 0; static char buf[BUF_SIZE]; static wchar_t wbuf[BUF_SIZE]; static bool stdoutIsConsole = true; static bool stderrIsConsole = true; static HANDLE consoleHandle = nullptr; // If all = true, flush all characters to console. // If all = false, flush up to and including last newline. // Also flush all if buffer > half full to ensure space for future // writes. static void flush(bool all = false) { int nchars = 0; if (all || bufLen > BUF_SIZE / 2) { nchars = bufLen; } else if (bufLen > 0) { // find num chars up to and including last '\n' for (nchars = bufLen; nchars > 0; --nchars) { if (buf[nchars - 1] == '\n') break; } } if (nchars > 0) { DWORD wlen = utf8ToUtf16(buf, nchars, (uint16_t *)wbuf, BUF_SIZE); WriteConsoleW(consoleHandle, wbuf, wlen, &wlen, nullptr); if (nchars < bufLen) { memmove(buf, buf + nchars, bufLen - nchars); bufLen -= nchars; } else { bufLen = 0; } } } static inline bool streamIsConsole(FILE *stream) { return ((stream == stdout && stdoutIsConsole) || (stream == stderr && stderrIsConsole)); } int win32_fprintf(FILE *stream, ...) { va_list args; int ret = 0; va_start(args, stream); const char *format = va_arg(args, const char *); if (streamIsConsole(stream)) { ret = vsnprintf(buf + bufLen, BUF_SIZE - bufLen, format, args); bufLen += ret; if (ret >= BUF_SIZE - bufLen) { // output was truncated buf[BUF_SIZE - 1] = 0; bufLen = BUF_SIZE - 1; } flush(); } else { vfprintf(stream, format, args); } va_end(args); return ret; } size_t win32_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { size_t ret = 0; if (streamIsConsole(stream)) { int n = size * nmemb; if (n > BUF_SIZE - bufLen - 1) n = BUF_SIZE - bufLen - 1; memcpy(buf + bufLen, ptr, n); bufLen += n; buf[bufLen] = 0; flush(); } else { ret = fwrite(ptr, size, nmemb, stream); } return ret; } Win32Console::Win32Console(int *argc, char **argv[]) { LPWSTR *wargv; fpos_t pos; argList = nullptr; privateArgList = nullptr; wargv = CommandLineToArgvW(GetCommandLineW(), &numArgs); if (wargv) { argList = new char *[numArgs]; privateArgList = new char *[numArgs]; for (int i = 0; i < numArgs; i++) { argList[i] = utf16ToUtf8((uint16_t *)(wargv[i])); // parseArgs will rearrange the argv list so we keep our own copy // to use for freeing all the strings privateArgList[i] = argList[i]; } LocalFree(wargv); *argc = numArgs; *argv = argList; } bufLen = 0; buf[0] = 0; wbuf[0] = 0; // check if stdout or stderr redirected // GetFileType() returns CHAR for console and special devices COMx, PRN, CON, NUL etc // fgetpos() succeeds on all CHAR devices except console and CON. stdoutIsConsole = (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_CHAR) && (fgetpos(stdout, &pos) != 0); stderrIsConsole = (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR) && (fgetpos(stderr, &pos) != 0); // Need a handle to the console. Doesn't matter if we use stdout or stderr as // long as the handle output is to the console. if (stdoutIsConsole) consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); else if (stderrIsConsole) consoleHandle = GetStdHandle(STD_ERROR_HANDLE); } Win32Console::~Win32Console() { flush(true); if (argList) { for (int i = 0; i < numArgs; i++) gfree(privateArgList[i]); delete[] argList; delete[] privateArgList; } } #endif // _WIN32