1 //========================================================================
2 //
3 // Win32Console.cc
4 //
5 // This file is licensed under the GPLv2 or later
6 //
7 // Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
8 //
9 // To see a description of the changes please see the Changelog file that
10 // came with your tarball or type make ChangeLog if you are building from git
11 //
12 //========================================================================
13 
14 #ifdef _WIN32
15 
16 #    include "goo/gmem.h"
17 #    include "UTF.h"
18 
19 #    define WIN32_CONSOLE_IMPL
20 #    include "Win32Console.h"
21 
22 #    include <windows.h>
23 #    include <shellapi.h>
24 
25 static const int BUF_SIZE = 4096;
26 static int bufLen = 0;
27 static char buf[BUF_SIZE];
28 static wchar_t wbuf[BUF_SIZE];
29 static bool stdoutIsConsole = true;
30 static bool stderrIsConsole = true;
31 static HANDLE consoleHandle = nullptr;
32 
33 // If all = true, flush all characters to console.
34 // If all = false, flush up to and including last newline.
35 // Also flush all if buffer > half full to ensure space for future
36 // writes.
flush(bool all=false)37 static void flush(bool all = false)
38 {
39     int nchars = 0;
40 
41     if (all || bufLen > BUF_SIZE / 2) {
42         nchars = bufLen;
43     } else if (bufLen > 0) {
44         // find num chars up to and including last '\n'
45         for (nchars = bufLen; nchars > 0; --nchars) {
46             if (buf[nchars - 1] == '\n')
47                 break;
48         }
49     }
50 
51     if (nchars > 0) {
52         DWORD wlen = utf8ToUtf16(buf, (uint16_t *)wbuf, BUF_SIZE, nchars);
53         WriteConsoleW(consoleHandle, wbuf, wlen, &wlen, nullptr);
54         if (nchars < bufLen) {
55             memmove(buf, buf + nchars, bufLen - nchars);
56             bufLen -= nchars;
57         } else {
58             bufLen = 0;
59         }
60     }
61 }
62 
streamIsConsole(FILE * stream)63 static inline bool streamIsConsole(FILE *stream)
64 {
65     return ((stream == stdout && stdoutIsConsole) || (stream == stderr && stderrIsConsole));
66 }
67 
win32_fprintf(FILE * stream,...)68 int win32_fprintf(FILE *stream, ...)
69 {
70     va_list args;
71     int ret = 0;
72 
73     va_start(args, stream);
74     const char *format = va_arg(args, const char *);
75     if (streamIsConsole(stream)) {
76         ret = vsnprintf(buf + bufLen, BUF_SIZE - bufLen, format, args);
77         bufLen += ret;
78         if (ret >= BUF_SIZE - bufLen) {
79             // output was truncated
80             buf[BUF_SIZE - 1] = 0;
81             bufLen = BUF_SIZE - 1;
82         }
83         flush();
84     } else {
85         vfprintf(stream, format, args);
86     }
87     va_end(args);
88 
89     return ret;
90 }
91 
win32_fwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream)92 size_t win32_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
93 {
94     size_t ret = 0;
95 
96     if (streamIsConsole(stream)) {
97         int n = size * nmemb;
98         if (n > BUF_SIZE - bufLen - 1)
99             n = BUF_SIZE - bufLen - 1;
100         memcpy(buf + bufLen, ptr, n);
101         bufLen += n;
102         buf[bufLen] = 0;
103         flush();
104     } else {
105         ret = fwrite(ptr, size, nmemb, stream);
106     }
107 
108     return ret;
109 }
110 
Win32Console(int * argc,char ** argv[])111 Win32Console::Win32Console(int *argc, char **argv[])
112 {
113     LPWSTR *wargv;
114     fpos_t pos;
115 
116     argList = nullptr;
117     privateArgList = nullptr;
118     wargv = CommandLineToArgvW(GetCommandLineW(), &numArgs);
119     if (wargv) {
120         argList = new char *[numArgs];
121         privateArgList = new char *[numArgs];
122         for (int i = 0; i < numArgs; i++) {
123             argList[i] = utf16ToUtf8((uint16_t *)(wargv[i]));
124             // parseArgs will rearrange the argv list so we keep our own copy
125             // to use for freeing all the strings
126             privateArgList[i] = argList[i];
127         }
128         LocalFree(wargv);
129         *argc = numArgs;
130         *argv = argList;
131     }
132 
133     bufLen = 0;
134     buf[0] = 0;
135     wbuf[0] = 0;
136 
137     // check if stdout or stderr redirected
138     // GetFileType() returns CHAR for console and special devices COMx, PRN, CON, NUL etc
139     // fgetpos() succeeds on all CHAR devices except console and CON.
140 
141     stdoutIsConsole = (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_CHAR) && (fgetpos(stdout, &pos) != 0);
142 
143     stderrIsConsole = (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR) && (fgetpos(stderr, &pos) != 0);
144 
145     // Need a handle to the console. Doesn't matter if we use stdout or stderr as
146     // long as the handle output is to the console.
147     if (stdoutIsConsole)
148         consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
149     else if (stderrIsConsole)
150         consoleHandle = GetStdHandle(STD_ERROR_HANDLE);
151 }
152 
~Win32Console()153 Win32Console::~Win32Console()
154 {
155     flush(true);
156     if (argList) {
157         for (int i = 0; i < numArgs; i++)
158             gfree(privateArgList[i]);
159         delete[] argList;
160         delete[] privateArgList;
161     }
162 }
163 
164 #endif // _WIN32
165