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. |
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 | |
63 | static inline bool streamIsConsole(FILE *stream) |
64 | { |
65 | return ((stream == stdout && stdoutIsConsole) || (stream == stderr && stderrIsConsole)); |
66 | } |
67 | |
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 | |
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 | |
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 | |
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 | |