blob: deac2458a3e95bf7ef0e0a6de1a66ccca9ae4945 [file] [log] [blame] [edit]
//========================================================================
//
// Win32Console.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright (C) 2017, 2024 Adrian Johnson <ajohnson@redneon.com>
//
// 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 <windows.h>
# include <shellapi.h>
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