]> git.xonotic.org Git - xonotic/gmqcc.git/blob - con.c
Fix teh bugs, thanks div0
[xonotic/gmqcc.git] / con.c
1 /*
2  * Copyright (C) 2012
3  *     Dale Weiler
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include "gmqcc.h"
24
25 /*
26  * isatty/STDERR_FILENO/STDOUT_FILNO
27  * + some other things likewise.
28  */
29 #ifndef _WIN32
30 #include <unistd.h>
31 #endif
32
33 #define GMQCC_IS_STDOUT(X) ((X) == stdout)
34 #define GMQCC_IS_STDERR(X) ((X) == stderr)
35 #define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X))
36
37 typedef struct {
38     FILE *handle_err;
39     FILE *handle_out;
40     
41     int   color_err;
42     int   color_out;
43 } con_t;
44
45 /*
46  * Doing colored output on windows is fucking stupid.  The linux way is
47  * the real way. So we emulate it on windows :)
48  */
49 #ifdef _WIN32
50 #define WIN32_LEAN_AND_MEAN
51 #include <windows.h>
52
53 /*
54  * Windows doesn't have constants for FILENO, sadly but the docs tell
55  * use the constant values.
56  */
57 #undef  STDERR_FILENO
58 #undef  STDOUT_FILENO
59 #define STDERR_FILENO 2
60 #define STDOUT_FILENO 1
61
62 /*
63  * Windows and it's posix underscore bullshit.  We simply fix this
64  * with yay, another macro :P
65  */
66 #define isatty _isatty
67  
68 enum {
69     RESET = 0,
70     BOLD  = 1,
71     BLACK = 30,
72     RED,
73     GREEN,
74     YELLOW,
75     BLUE,
76     MAGENTA,
77     CYAN,
78     GRAY,
79     WHITE
80 };
81
82 enum {
83     WBLACK,
84     WBLUE,
85     WGREEN   = 2,
86     WRED     = 4,
87     WINTENSE = 8,
88     WCYAN    = WBLUE  | WGREEN,
89     WMAGENTA = WBLUE  | WRED,
90     WYELLOW  = WGREEN | WRED,
91     WWHITE   = WBLUE  | WGREEN | WRED
92 }
93
94 static const ansi2win[] = {
95     WBLACK,
96     WRED,
97     WGREEN,
98     WYELLOW,
99     WBLUE,
100     WMAGENTA,
101     WCYAN,
102     WWHITE
103 };
104
105 static void win_fputs(char *str, FILE *f) {
106     /* state for translate */
107     int acolor;
108     int wcolor;
109     int icolor;
110     
111     int state;
112     int place;
113     
114     /* attributes */
115     int intense  =  -1;
116     int colors[] = {-1, -1 };
117     int colorpos = 1;
118     
119     CONSOLE_SCREEN_BUFFER_INFO cinfo;
120     GetConsoleScreenBufferInfo(
121         (h == stdout) ?
122             GetStdHandle(STD_OUTPUT_HANDLE) :
123             GetStdHandle(STD_ERROR_HANDLE), &cinfo
124     );
125     icolor = cinfo.wAttributes;
126     
127     while (*str) {
128         if (*str == '\e')
129             state = '\e';
130         else if (state == '\e' && *str == '[')
131             state = '[';
132         else if (state == '[') {
133             if (*str != 'm') {
134                 colors[colorpos] = *str;
135                 colorpos--;
136             } else {
137                 int find;
138                 int mult;
139                 for (find = colorpos + 1, acolor = 0, mult = 1; find < 2; find++) {
140                     acolor += (colors[find] - 48) * mult;
141                     mult   *= 10;
142                 }
143                 
144                 /* convert to windows color */
145                 if (acolor == BOLD)
146                     intense = WINTENSE;
147                 else if (acolor == RESET) {
148                     intense = WBLACK;
149                     wcolor  = icolor;
150                 }
151                 else if (BLACK < acolor && acolor <= WHITE)
152                     wcolor = ansi2win[acolor - 30];
153                 else if (acolor == 90) {
154                     /* special gray really white man */
155                     wcolor  = WWHITE;
156                     intense = WBLACK;
157                 }
158                 
159                 SetConsoleTextattribute(
160                     (h == stdout) ?
161                     GetStdHandle(STD_OUTPUT_HANDLE) :
162                     GetStdHandle(STD_ERROR_HANDLE),
163                     
164                     wcolor | intense | (icolor & 0xF0)
165                 );
166                 colorpos =  1;
167                 state    = -1;
168             }
169         } else {
170             fputc(*str, h);
171         }
172     }
173     /* restore */
174     SetConsoleTextAttribute(
175         (h == stdout) ?
176         GetStdHandle(STD_OUTPUT_HANDLE) :
177         GetStdHandle(STD_ERROR_HANDLE),
178         icolor
179     );
180 }
181 #endif
182
183 /*
184  * We use standard files as default. These can be changed at any time
185  * with con_change(F, F)
186  */
187 static con_t console;
188
189 /*
190  * Enables color on output if supported.
191  * NOTE: The support for checking colors is NULL.  On windows this will
192  * always work, on *nix it depends if the term has colors.
193  * 
194  * NOTE: This prevents colored output to piped stdout/err via isatty
195  * checks.
196  */
197 static void con_enablecolor() {
198     if (console.handle_err == stderr || console.handle_err == stdout)
199         console.color_err = !!(isatty(STDERR_FILENO));
200     if (console.handle_out == stderr || console.handle_out == stdout)
201         console.color_out = !!(isatty(STDOUT_FILENO));
202         
203     #ifndef _WIN32
204     {
205         char buf[4] = {0, 0, 0, 0};
206         
207         /*
208          * This is such a hack.  But I'm not linking in any libraries to
209          * do this stupidity.  It's insane there is simply not a ttyhascolor
210          * in unistd.h
211          */
212         FILE *tput = popen("tput colors", "r");
213         if  (!tput) {
214             /*
215              * disable colors since we can't determine without tput
216              * which should be guranteed on all *nix OSes
217              */
218             console.color_err = 0;
219             console.color_out = 0;
220             
221             return;
222         }
223         
224         /*
225          * Handle to tput was a success, lets read in the amount of
226          * color support.  It should be at minimal 8.
227          */
228         fread(buf, 1, sizeof(buf)-1, tput);
229         
230         if (atoi(buf) < 8) {
231             console.color_err = 0;
232             console.color_out = 0;
233         }
234         
235         /*
236          * We made it this far, which means we support colors in the
237          * terminal.
238          */
239         fclose(tput);
240     }
241     #endif
242 }
243
244 /*
245  * Does a write to the handle with the format string and list of
246  * arguments.  This colorizes for windows as well via translate
247  * step.
248  */
249 static int con_write(FILE *handle, const char *fmt, va_list va) {
250     int      ln;
251     #ifndef _WIN32
252     ln = vfprintf(handle, fmt, va);
253     #else
254     {
255         char *data = NULL;
256         ln   = _vscprintf(fmt, va);
257         data = malloc(ln + 1);
258         data[ln] = 0;
259         vsprintf(data, fmt, va);
260         if (GMQCC_IS_DEFINE(handle))
261             ln = win_fputs(data, handle);
262         else
263             ln = fputs(data, handle);
264         free(data);
265     }
266     #endif
267     return ln;
268 }
269
270 /**********************************************************************
271  * EXPOSED INTERFACE BEGINS
272  *********************************************************************/
273
274 void con_close() {
275     if (!GMQCC_IS_DEFINE(console.handle_err))
276         fclose(console.handle_err);
277     if (!GMQCC_IS_DEFINE(console.handle_out))
278         fclose(console.handle_out);
279 }
280
281 void con_color(int state) {
282     if (state)
283         con_enablecolor();
284     else {
285         console.color_err = 0;
286         console.color_out = 0;
287     }
288 }
289
290 void con_init() {
291     console.handle_err = stderr;
292     console.handle_out = stdout;
293     con_enablecolor();
294 }
295
296 void con_reset() {
297     con_close();
298     con_init ();
299 }
300
301 /*
302  * This is clever, say you want to change the console to use two
303  * files for out/err.  You pass in two strings, it will properly
304  * close the existing handles (if they're not std* handles) and
305  * open them.  Now say you want TO use stdout and stderr, this
306  * allows you to do that so long as you cast them to (char*).
307  * Say you need stdout for out, but want a file for error, you can
308  * do this too, just cast the stdout for (char*) and stick to a
309  * string for the error file.
310  */
311 int con_change(const char *out, const char *err) {
312     con_close();
313     
314     if (GMQCC_IS_DEFINE((FILE*)out)) {
315         console.handle_out = (((FILE*)err) == stdout) ? stdout : stderr;
316         con_enablecolor();
317     } else if (!(console.handle_out = fopen(out, "w"))) return 0;
318     
319     if (GMQCC_IS_DEFINE((FILE*)err)) {
320         console.handle_err = (((FILE*)err) == stdout) ? stdout : stderr;
321         con_enablecolor();
322     } else if (!(console.handle_err = fopen(err, "w"))) return 0;
323     
324     return 1;
325 }
326
327 int con_verr(const char *fmt, va_list va) {
328     return con_write(console.handle_err, fmt, va);
329 }
330 int con_vout(const char *fmt, va_list va) {
331     return con_write(console.handle_out, fmt, va);
332 }
333
334 int con_err(const char *fmt, ...) {
335     va_list  va;
336     int      ln = 0;
337     va_start(va, fmt);
338     con_verr(fmt, va);
339     va_end  (va);
340     return   ln;
341 }
342 int con_out(const char *fmt, ...) {
343     va_list  va;
344     int      ln = 0;
345     va_start(va, fmt);
346     con_vout(fmt, va);
347     va_end  (va);
348     return   ln;
349 }