OSDN Git Service

Merge branch 'dp/autoconf-curl-ssl' into maint
[git-core/git.git] / compat / winansi.c
1 /*
2  * Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
3  */
4
5 #undef NOGDI
6 #include "../git-compat-util.h"
7 #include <wingdi.h>
8 #include <winreg.h>
9
10 /*
11  ANSI codes used by git: m, K
12
13  This file is git-specific. Therefore, this file does not attempt
14  to implement any codes that are not used by git.
15 */
16
17 static HANDLE console;
18 static WORD plain_attr;
19 static WORD attr;
20 static int negative;
21 static int non_ascii_used = 0;
22 static HANDLE hthread, hread, hwrite;
23 static HANDLE hconsole1, hconsole2;
24
25 #ifdef __MINGW32__
26 #if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 5
27 typedef struct _CONSOLE_FONT_INFOEX {
28         ULONG cbSize;
29         DWORD nFont;
30         COORD dwFontSize;
31         UINT FontFamily;
32         UINT FontWeight;
33         WCHAR FaceName[LF_FACESIZE];
34 } CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX;
35 #endif
36 #endif
37
38 typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL,
39                 PCONSOLE_FONT_INFOEX);
40
41 static void warn_if_raster_font(void)
42 {
43         DWORD fontFamily = 0;
44         PGETCURRENTCONSOLEFONTEX pGetCurrentConsoleFontEx;
45
46         /* don't bother if output was ascii only */
47         if (!non_ascii_used)
48                 return;
49
50         /* GetCurrentConsoleFontEx is available since Vista */
51         pGetCurrentConsoleFontEx = (PGETCURRENTCONSOLEFONTEX) GetProcAddress(
52                         GetModuleHandle("kernel32.dll"),
53                         "GetCurrentConsoleFontEx");
54         if (pGetCurrentConsoleFontEx) {
55                 CONSOLE_FONT_INFOEX cfi;
56                 cfi.cbSize = sizeof(cfi);
57                 if (pGetCurrentConsoleFontEx(console, 0, &cfi))
58                         fontFamily = cfi.FontFamily;
59         } else {
60                 /* pre-Vista: check default console font in registry */
61                 HKEY hkey;
62                 if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console",
63                                 0, KEY_READ, &hkey)) {
64                         DWORD size = sizeof(fontFamily);
65                         RegQueryValueExA(hkey, "FontFamily", NULL, NULL,
66                                         (LPVOID) &fontFamily, &size);
67                         RegCloseKey(hkey);
68                 }
69         }
70
71         if (!(fontFamily & TMPF_TRUETYPE)) {
72                 const wchar_t *msg = L"\nWarning: Your console font probably "
73                         L"doesn\'t support Unicode. If you experience strange "
74                         L"characters in the output, consider switching to a "
75                         L"TrueType font such as Consolas!\n";
76                 DWORD dummy;
77                 WriteConsoleW(console, msg, wcslen(msg), &dummy, NULL);
78         }
79 }
80
81 static int is_console(int fd)
82 {
83         CONSOLE_SCREEN_BUFFER_INFO sbi;
84         HANDLE hcon;
85
86         static int initialized = 0;
87
88         /* get OS handle of the file descriptor */
89         hcon = (HANDLE) _get_osfhandle(fd);
90         if (hcon == INVALID_HANDLE_VALUE)
91                 return 0;
92
93         /* check if its a device (i.e. console, printer, serial port) */
94         if (GetFileType(hcon) != FILE_TYPE_CHAR)
95                 return 0;
96
97         /* check if its a handle to a console output screen buffer */
98         if (!GetConsoleScreenBufferInfo(hcon, &sbi))
99                 return 0;
100
101         /* initialize attributes */
102         if (!initialized) {
103                 console = hcon;
104                 attr = plain_attr = sbi.wAttributes;
105                 negative = 0;
106                 initialized = 1;
107         }
108
109         return 1;
110 }
111
112 #define BUFFER_SIZE 4096
113 #define MAX_PARAMS 16
114
115 static void write_console(unsigned char *str, size_t len)
116 {
117         /* only called from console_thread, so a static buffer will do */
118         static wchar_t wbuf[2 * BUFFER_SIZE + 1];
119         DWORD dummy;
120
121         /* convert utf-8 to utf-16 */
122         int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
123
124         /* write directly to console */
125         WriteConsoleW(console, wbuf, wlen, &dummy, NULL);
126
127         /* remember if non-ascii characters are printed */
128         if (wlen != len)
129                 non_ascii_used = 1;
130 }
131
132 #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
133 #define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
134
135 static void set_console_attr(void)
136 {
137         WORD attributes = attr;
138         if (negative) {
139                 attributes &= ~FOREGROUND_ALL;
140                 attributes &= ~BACKGROUND_ALL;
141
142                 /* This could probably use a bitmask
143                    instead of a series of ifs */
144                 if (attr & FOREGROUND_RED)
145                         attributes |= BACKGROUND_RED;
146                 if (attr & FOREGROUND_GREEN)
147                         attributes |= BACKGROUND_GREEN;
148                 if (attr & FOREGROUND_BLUE)
149                         attributes |= BACKGROUND_BLUE;
150
151                 if (attr & BACKGROUND_RED)
152                         attributes |= FOREGROUND_RED;
153                 if (attr & BACKGROUND_GREEN)
154                         attributes |= FOREGROUND_GREEN;
155                 if (attr & BACKGROUND_BLUE)
156                         attributes |= FOREGROUND_BLUE;
157         }
158         SetConsoleTextAttribute(console, attributes);
159 }
160
161 static void erase_in_line(void)
162 {
163         CONSOLE_SCREEN_BUFFER_INFO sbi;
164         DWORD dummy; /* Needed for Windows 7 (or Vista) regression */
165
166         if (!console)
167                 return;
168
169         GetConsoleScreenBufferInfo(console, &sbi);
170         FillConsoleOutputCharacterA(console, ' ',
171                 sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,
172                 &dummy);
173 }
174
175 static void set_attr(char func, const int *params, int paramlen)
176 {
177         int i;
178         switch (func) {
179         case 'm':
180                 for (i = 0; i < paramlen; i++) {
181                         switch (params[i]) {
182                         case 0: /* reset */
183                                 attr = plain_attr;
184                                 negative = 0;
185                                 break;
186                         case 1: /* bold */
187                                 attr |= FOREGROUND_INTENSITY;
188                                 break;
189                         case 2:  /* faint */
190                         case 22: /* normal */
191                                 attr &= ~FOREGROUND_INTENSITY;
192                                 break;
193                         case 3:  /* italic */
194                                 /* Unsupported */
195                                 break;
196                         case 4:  /* underline */
197                         case 21: /* double underline */
198                                 /* Wikipedia says this flag does nothing */
199                                 /* Furthermore, mingw doesn't define this flag
200                                 attr |= COMMON_LVB_UNDERSCORE; */
201                                 break;
202                         case 24: /* no underline */
203                                 /* attr &= ~COMMON_LVB_UNDERSCORE; */
204                                 break;
205                         case 5:  /* slow blink */
206                         case 6:  /* fast blink */
207                                 /* We don't have blink, but we do have
208                                    background intensity */
209                                 attr |= BACKGROUND_INTENSITY;
210                                 break;
211                         case 25: /* no blink */
212                                 attr &= ~BACKGROUND_INTENSITY;
213                                 break;
214                         case 7:  /* negative */
215                                 negative = 1;
216                                 break;
217                         case 27: /* positive */
218                                 negative = 0;
219                                 break;
220                         case 8:  /* conceal */
221                         case 28: /* reveal */
222                                 /* Unsupported */
223                                 break;
224                         case 30: /* Black */
225                                 attr &= ~FOREGROUND_ALL;
226                                 break;
227                         case 31: /* Red */
228                                 attr &= ~FOREGROUND_ALL;
229                                 attr |= FOREGROUND_RED;
230                                 break;
231                         case 32: /* Green */
232                                 attr &= ~FOREGROUND_ALL;
233                                 attr |= FOREGROUND_GREEN;
234                                 break;
235                         case 33: /* Yellow */
236                                 attr &= ~FOREGROUND_ALL;
237                                 attr |= FOREGROUND_RED | FOREGROUND_GREEN;
238                                 break;
239                         case 34: /* Blue */
240                                 attr &= ~FOREGROUND_ALL;
241                                 attr |= FOREGROUND_BLUE;
242                                 break;
243                         case 35: /* Magenta */
244                                 attr &= ~FOREGROUND_ALL;
245                                 attr |= FOREGROUND_RED | FOREGROUND_BLUE;
246                                 break;
247                         case 36: /* Cyan */
248                                 attr &= ~FOREGROUND_ALL;
249                                 attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;
250                                 break;
251                         case 37: /* White */
252                                 attr |= FOREGROUND_RED |
253                                         FOREGROUND_GREEN |
254                                         FOREGROUND_BLUE;
255                                 break;
256                         case 38: /* Unknown */
257                                 break;
258                         case 39: /* reset */
259                                 attr &= ~FOREGROUND_ALL;
260                                 attr |= (plain_attr & FOREGROUND_ALL);
261                                 break;
262                         case 40: /* Black */
263                                 attr &= ~BACKGROUND_ALL;
264                                 break;
265                         case 41: /* Red */
266                                 attr &= ~BACKGROUND_ALL;
267                                 attr |= BACKGROUND_RED;
268                                 break;
269                         case 42: /* Green */
270                                 attr &= ~BACKGROUND_ALL;
271                                 attr |= BACKGROUND_GREEN;
272                                 break;
273                         case 43: /* Yellow */
274                                 attr &= ~BACKGROUND_ALL;
275                                 attr |= BACKGROUND_RED | BACKGROUND_GREEN;
276                                 break;
277                         case 44: /* Blue */
278                                 attr &= ~BACKGROUND_ALL;
279                                 attr |= BACKGROUND_BLUE;
280                                 break;
281                         case 45: /* Magenta */
282                                 attr &= ~BACKGROUND_ALL;
283                                 attr |= BACKGROUND_RED | BACKGROUND_BLUE;
284                                 break;
285                         case 46: /* Cyan */
286                                 attr &= ~BACKGROUND_ALL;
287                                 attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;
288                                 break;
289                         case 47: /* White */
290                                 attr |= BACKGROUND_RED |
291                                         BACKGROUND_GREEN |
292                                         BACKGROUND_BLUE;
293                                 break;
294                         case 48: /* Unknown */
295                                 break;
296                         case 49: /* reset */
297                                 attr &= ~BACKGROUND_ALL;
298                                 attr |= (plain_attr & BACKGROUND_ALL);
299                                 break;
300                         default:
301                                 /* Unsupported code */
302                                 break;
303                         }
304                 }
305                 set_console_attr();
306                 break;
307         case 'K':
308                 erase_in_line();
309                 break;
310         default:
311                 /* Unsupported code */
312                 break;
313         }
314 }
315
316 enum {
317         TEXT = 0, ESCAPE = 033, BRACKET = '['
318 };
319
320 static DWORD WINAPI console_thread(LPVOID unused)
321 {
322         unsigned char buffer[BUFFER_SIZE];
323         DWORD bytes;
324         int start, end = 0, c, parampos = 0, state = TEXT;
325         int params[MAX_PARAMS];
326
327         while (1) {
328                 /* read next chunk of bytes from the pipe */
329                 if (!ReadFile(hread, buffer + end, BUFFER_SIZE - end, &bytes,
330                                 NULL)) {
331                         /* exit if pipe has been closed or disconnected */
332                         if (GetLastError() == ERROR_PIPE_NOT_CONNECTED ||
333                                         GetLastError() == ERROR_BROKEN_PIPE)
334                                 break;
335                         /* ignore other errors */
336                         continue;
337                 }
338
339                 /* scan the bytes and handle ANSI control codes */
340                 bytes += end;
341                 start = end = 0;
342                 while (end < bytes) {
343                         c = buffer[end++];
344                         switch (state) {
345                         case TEXT:
346                                 if (c == ESCAPE) {
347                                         /* print text seen so far */
348                                         if (end - 1 > start)
349                                                 write_console(buffer + start,
350                                                         end - 1 - start);
351
352                                         /* then start parsing escape sequence */
353                                         start = end - 1;
354                                         memset(params, 0, sizeof(params));
355                                         parampos = 0;
356                                         state = ESCAPE;
357                                 }
358                                 break;
359
360                         case ESCAPE:
361                                 /* continue if "\033[", otherwise bail out */
362                                 state = (c == BRACKET) ? BRACKET : TEXT;
363                                 break;
364
365                         case BRACKET:
366                                 /* parse [0-9;]* into array of parameters */
367                                 if (c >= '0' && c <= '9') {
368                                         params[parampos] *= 10;
369                                         params[parampos] += c - '0';
370                                 } else if (c == ';') {
371                                         /*
372                                          * next parameter, bail out if out of
373                                          * bounds
374                                          */
375                                         parampos++;
376                                         if (parampos >= MAX_PARAMS)
377                                                 state = TEXT;
378                                 } else {
379                                         /*
380                                          * end of escape sequence, change
381                                          * console attributes
382                                          */
383                                         set_attr(c, params, parampos + 1);
384                                         start = end;
385                                         state = TEXT;
386                                 }
387                                 break;
388                         }
389                 }
390
391                 /* print remaining text unless parsing an escape sequence */
392                 if (state == TEXT && end > start) {
393                         /* check for incomplete UTF-8 sequences and fix end */
394                         if (buffer[end - 1] >= 0x80) {
395                                 if (buffer[end -1] >= 0xc0)
396                                         end--;
397                                 else if (end - 1 > start &&
398                                                 buffer[end - 2] >= 0xe0)
399                                         end -= 2;
400                                 else if (end - 2 > start &&
401                                                 buffer[end - 3] >= 0xf0)
402                                         end -= 3;
403                         }
404
405                         /* print remaining complete UTF-8 sequences */
406                         if (end > start)
407                                 write_console(buffer + start, end - start);
408
409                         /* move remaining bytes to the front */
410                         if (end < bytes)
411                                 memmove(buffer, buffer + end, bytes - end);
412                         end = bytes - end;
413                 } else {
414                         /* all data has been consumed, mark buffer empty */
415                         end = 0;
416                 }
417         }
418
419         /* check if the console font supports unicode */
420         warn_if_raster_font();
421
422         CloseHandle(hread);
423         return 0;
424 }
425
426 static void winansi_exit(void)
427 {
428         /* flush all streams */
429         _flushall();
430
431         /* signal console thread to exit */
432         FlushFileBuffers(hwrite);
433         DisconnectNamedPipe(hwrite);
434
435         /* wait for console thread to copy remaining data */
436         WaitForSingleObject(hthread, INFINITE);
437
438         /* cleanup handles... */
439         CloseHandle(hwrite);
440         CloseHandle(hthread);
441 }
442
443 static void die_lasterr(const char *fmt, ...)
444 {
445         va_list params;
446         va_start(params, fmt);
447         errno = err_win_to_posix(GetLastError());
448         die_errno(fmt, params);
449         va_end(params);
450 }
451
452 static HANDLE duplicate_handle(HANDLE hnd)
453 {
454         HANDLE hresult, hproc = GetCurrentProcess();
455         if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE,
456                         DUPLICATE_SAME_ACCESS))
457                 die_lasterr("DuplicateHandle(%li) failed",
458                         (long) (intptr_t) hnd);
459         return hresult;
460 }
461
462
463 /*
464  * Make MSVCRT's internal file descriptor control structure accessible
465  * so that we can tweak OS handles and flags directly (we need MSVCRT
466  * to treat our pipe handle as if it were a console).
467  *
468  * We assume that the ioinfo structure (exposed by MSVCRT.dll via
469  * __pioinfo) starts with the OS handle and the flags. The exact size
470  * varies between MSVCRT versions, so we try different sizes until
471  * toggling the FDEV bit of _pioinfo(1)->osflags is reflected in
472  * isatty(1).
473  */
474 typedef struct {
475         HANDLE osfhnd;
476         char osflags;
477 } ioinfo;
478
479 extern __declspec(dllimport) ioinfo *__pioinfo[];
480
481 static size_t sizeof_ioinfo = 0;
482
483 #define IOINFO_L2E 5
484 #define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E)
485
486 #define FPIPE 0x08
487 #define FDEV  0x40
488
489 static inline ioinfo* _pioinfo(int fd)
490 {
491         return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] +
492                         (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
493 }
494
495 static int init_sizeof_ioinfo(void)
496 {
497         int istty, wastty;
498         /* don't init twice */
499         if (sizeof_ioinfo)
500                 return sizeof_ioinfo >= 256;
501
502         sizeof_ioinfo = sizeof(ioinfo);
503         wastty = isatty(1);
504         while (sizeof_ioinfo < 256) {
505                 /* toggle FDEV flag, check isatty, then toggle back */
506                 _pioinfo(1)->osflags ^= FDEV;
507                 istty = isatty(1);
508                 _pioinfo(1)->osflags ^= FDEV;
509                 /* return if we found the correct size */
510                 if (istty != wastty)
511                         return 0;
512                 sizeof_ioinfo += sizeof(void*);
513         }
514         error("Tweaking file descriptors doesn't work with this MSVCRT.dll");
515         return 1;
516 }
517
518 static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
519 {
520         ioinfo *pioinfo;
521         HANDLE old_handle;
522
523         /* init ioinfo size if we haven't done so */
524         if (init_sizeof_ioinfo())
525                 return INVALID_HANDLE_VALUE;
526
527         /* get ioinfo pointer and change the handles */
528         pioinfo = _pioinfo(fd);
529         old_handle = pioinfo->osfhnd;
530         pioinfo->osfhnd = new_handle;
531         return old_handle;
532 }
533
534 #ifdef DETECT_MSYS_TTY
535
536 #include <winternl.h>
537 #include <ntstatus.h>
538
539 static void detect_msys_tty(int fd)
540 {
541         ULONG result;
542         BYTE buffer[1024];
543         POBJECT_NAME_INFORMATION nameinfo = (POBJECT_NAME_INFORMATION) buffer;
544         PWSTR name;
545
546         /* check if fd is a pipe */
547         HANDLE h = (HANDLE) _get_osfhandle(fd);
548         if (GetFileType(h) != FILE_TYPE_PIPE)
549                 return;
550
551         /* get pipe name */
552         if (!NT_SUCCESS(NtQueryObject(h, ObjectNameInformation,
553                         buffer, sizeof(buffer) - 2, &result)))
554                 return;
555         name = nameinfo->Name.Buffer;
556         name[nameinfo->Name.Length] = 0;
557
558         /* check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX') */
559         if (!wcsstr(name, L"msys-") || !wcsstr(name, L"-pty"))
560                 return;
561
562         /* init ioinfo size if we haven't done so */
563         if (init_sizeof_ioinfo())
564                 return;
565
566         /* set FDEV flag, reset FPIPE flag */
567         _pioinfo(fd)->osflags &= ~FPIPE;
568         _pioinfo(fd)->osflags |= FDEV;
569 }
570
571 #endif
572
573 void winansi_init(void)
574 {
575         int con1, con2;
576         char name[32];
577
578         /* check if either stdout or stderr is a console output screen buffer */
579         con1 = is_console(1);
580         con2 = is_console(2);
581         if (!con1 && !con2) {
582 #ifdef DETECT_MSYS_TTY
583                 /* check if stdin / stdout / stderr are MSYS2 pty pipes */
584                 detect_msys_tty(0);
585                 detect_msys_tty(1);
586                 detect_msys_tty(2);
587 #endif
588                 return;
589         }
590
591         /* create a named pipe to communicate with the console thread */
592         xsnprintf(name, sizeof(name), "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId());
593         hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND,
594                 PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);
595         if (hwrite == INVALID_HANDLE_VALUE)
596                 die_lasterr("CreateNamedPipe failed");
597
598         hread = CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
599         if (hread == INVALID_HANDLE_VALUE)
600                 die_lasterr("CreateFile for named pipe failed");
601
602         /* start console spool thread on the pipe's read end */
603         hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL);
604         if (hthread == INVALID_HANDLE_VALUE)
605                 die_lasterr("CreateThread(console_thread) failed");
606
607         /* schedule cleanup routine */
608         if (atexit(winansi_exit))
609                 die_errno("atexit(winansi_exit) failed");
610
611         /* redirect stdout / stderr to the pipe */
612         if (con1)
613                 hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite));
614         if (con2)
615                 hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite));
616 }
617
618 /*
619  * Returns the real console handle if stdout / stderr is a pipe redirecting
620  * to the console. Allows spawn / exec to pass the console to the next process.
621  */
622 HANDLE winansi_get_osfhandle(int fd)
623 {
624         HANDLE hnd = (HANDLE) _get_osfhandle(fd);
625         if (isatty(fd) && GetFileType(hnd) == FILE_TYPE_PIPE) {
626                 if (fd == 1 && hconsole1)
627                         return hconsole1;
628                 else if (fd == 2 && hconsole2)
629                         return hconsole2;
630         }
631         return hnd;
632 }