Merge branch 'jc/maint-github-actions-update'
[git.git] / compat / winansi.c
blob3abe8dd5a2711b5268cb5efe623d8c027f0d3d83
1 /*
2 * Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
3 */
5 #undef NOGDI
7 #include "../git-compat-util.h"
8 #include <wingdi.h>
9 #include <winreg.h>
10 #include "win32.h"
11 #include "win32/lazyload.h"
13 static int fd_is_interactive[3] = { 0, 0, 0 };
14 #define FD_CONSOLE 0x1
15 #define FD_SWAPPED 0x2
16 #define FD_MSYS 0x4
19 ANSI codes used by git: m, K
21 This file is git-specific. Therefore, this file does not attempt
22 to implement any codes that are not used by git.
25 static HANDLE console;
26 static WORD plain_attr;
27 static WORD attr;
28 static int negative;
29 static int non_ascii_used = 0;
30 static HANDLE hthread, hread, hwrite;
31 static HANDLE hconsole1, hconsole2;
33 #ifdef __MINGW32__
34 #if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 5
35 typedef struct _CONSOLE_FONT_INFOEX {
36 ULONG cbSize;
37 DWORD nFont;
38 COORD dwFontSize;
39 UINT FontFamily;
40 UINT FontWeight;
41 WCHAR FaceName[LF_FACESIZE];
42 } CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX;
43 #endif
44 #endif
46 static void warn_if_raster_font(void)
48 DWORD fontFamily = 0;
49 DECLARE_PROC_ADDR(kernel32.dll, BOOL, WINAPI,
50 GetCurrentConsoleFontEx, HANDLE, BOOL,
51 PCONSOLE_FONT_INFOEX);
53 /* don't bother if output was ascii only */
54 if (!non_ascii_used)
55 return;
57 /* GetCurrentConsoleFontEx is available since Vista */
58 if (INIT_PROC_ADDR(GetCurrentConsoleFontEx)) {
59 CONSOLE_FONT_INFOEX cfi;
60 cfi.cbSize = sizeof(cfi);
61 if (GetCurrentConsoleFontEx(console, 0, &cfi))
62 fontFamily = cfi.FontFamily;
63 } else {
64 /* pre-Vista: check default console font in registry */
65 HKEY hkey;
66 if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console",
67 0, KEY_READ, &hkey)) {
68 DWORD size = sizeof(fontFamily);
69 RegQueryValueExA(hkey, "FontFamily", NULL, NULL,
70 (LPVOID) &fontFamily, &size);
71 RegCloseKey(hkey);
75 if (!(fontFamily & TMPF_TRUETYPE)) {
76 const wchar_t *msg = L"\nWarning: Your console font probably "
77 L"doesn\'t support Unicode. If you experience strange "
78 L"characters in the output, consider switching to a "
79 L"TrueType font such as Consolas!\n";
80 DWORD dummy;
81 WriteConsoleW(console, msg, wcslen(msg), &dummy, NULL);
85 static int is_console(int fd)
87 CONSOLE_SCREEN_BUFFER_INFO sbi;
88 DWORD mode;
89 HANDLE hcon;
91 static int initialized = 0;
93 /* get OS handle of the file descriptor */
94 hcon = (HANDLE) _get_osfhandle(fd);
95 if (hcon == INVALID_HANDLE_VALUE)
96 return 0;
98 /* check if its a device (i.e. console, printer, serial port) */
99 if (GetFileType(hcon) != FILE_TYPE_CHAR)
100 return 0;
102 /* check if its a handle to a console output screen buffer */
103 if (!fd) {
104 if (!GetConsoleMode(hcon, &mode))
105 return 0;
107 * This code path is only reached if there is no console
108 * attached to stdout/stderr, i.e. we will not need to output
109 * any text to any console, therefore we might just as well
110 * use black as foreground color.
112 sbi.wAttributes = 0;
113 } else if (!GetConsoleScreenBufferInfo(hcon, &sbi))
114 return 0;
116 if (fd >= 0 && fd <= 2)
117 fd_is_interactive[fd] |= FD_CONSOLE;
119 /* initialize attributes */
120 if (!initialized) {
121 console = hcon;
122 attr = plain_attr = sbi.wAttributes;
123 negative = 0;
124 initialized = 1;
127 return 1;
130 #define BUFFER_SIZE 4096
131 #define MAX_PARAMS 16
133 static void write_console(unsigned char *str, size_t len)
135 /* only called from console_thread, so a static buffer will do */
136 static wchar_t wbuf[2 * BUFFER_SIZE + 1];
137 DWORD dummy;
139 /* convert utf-8 to utf-16 */
140 int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
141 if (wlen < 0) {
142 wchar_t *err = L"[invalid]";
143 WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
144 return;
147 /* write directly to console */
148 WriteConsoleW(console, wbuf, wlen, &dummy, NULL);
150 /* remember if non-ascii characters are printed */
151 if (wlen != len)
152 non_ascii_used = 1;
155 #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
156 #define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
158 static void set_console_attr(void)
160 WORD attributes = attr;
161 if (negative) {
162 attributes &= ~FOREGROUND_ALL;
163 attributes &= ~BACKGROUND_ALL;
165 /* This could probably use a bitmask
166 instead of a series of ifs */
167 if (attr & FOREGROUND_RED)
168 attributes |= BACKGROUND_RED;
169 if (attr & FOREGROUND_GREEN)
170 attributes |= BACKGROUND_GREEN;
171 if (attr & FOREGROUND_BLUE)
172 attributes |= BACKGROUND_BLUE;
174 if (attr & BACKGROUND_RED)
175 attributes |= FOREGROUND_RED;
176 if (attr & BACKGROUND_GREEN)
177 attributes |= FOREGROUND_GREEN;
178 if (attr & BACKGROUND_BLUE)
179 attributes |= FOREGROUND_BLUE;
181 SetConsoleTextAttribute(console, attributes);
184 static void erase_in_line(void)
186 CONSOLE_SCREEN_BUFFER_INFO sbi;
187 DWORD dummy; /* Needed for Windows 7 (or Vista) regression */
189 if (!console)
190 return;
192 GetConsoleScreenBufferInfo(console, &sbi);
193 FillConsoleOutputCharacterA(console, ' ',
194 sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,
195 &dummy);
198 static void set_attr(char func, const int *params, int paramlen)
200 int i;
201 switch (func) {
202 case 'm':
203 for (i = 0; i < paramlen; i++) {
204 switch (params[i]) {
205 case 0: /* reset */
206 attr = plain_attr;
207 negative = 0;
208 break;
209 case 1: /* bold */
210 attr |= FOREGROUND_INTENSITY;
211 break;
212 case 2: /* faint */
213 case 22: /* normal */
214 attr &= ~FOREGROUND_INTENSITY;
215 break;
216 case 3: /* italic */
217 /* Unsupported */
218 break;
219 case 4: /* underline */
220 case 21: /* double underline */
221 /* Wikipedia says this flag does nothing */
222 /* Furthermore, mingw doesn't define this flag
223 attr |= COMMON_LVB_UNDERSCORE; */
224 break;
225 case 24: /* no underline */
226 /* attr &= ~COMMON_LVB_UNDERSCORE; */
227 break;
228 case 5: /* slow blink */
229 case 6: /* fast blink */
230 /* We don't have blink, but we do have
231 background intensity */
232 attr |= BACKGROUND_INTENSITY;
233 break;
234 case 25: /* no blink */
235 attr &= ~BACKGROUND_INTENSITY;
236 break;
237 case 7: /* negative */
238 negative = 1;
239 break;
240 case 27: /* positive */
241 negative = 0;
242 break;
243 case 8: /* conceal */
244 case 28: /* reveal */
245 /* Unsupported */
246 break;
247 case 30: /* Black */
248 attr &= ~FOREGROUND_ALL;
249 break;
250 case 31: /* Red */
251 attr &= ~FOREGROUND_ALL;
252 attr |= FOREGROUND_RED;
253 break;
254 case 32: /* Green */
255 attr &= ~FOREGROUND_ALL;
256 attr |= FOREGROUND_GREEN;
257 break;
258 case 33: /* Yellow */
259 attr &= ~FOREGROUND_ALL;
260 attr |= FOREGROUND_RED | FOREGROUND_GREEN;
261 break;
262 case 34: /* Blue */
263 attr &= ~FOREGROUND_ALL;
264 attr |= FOREGROUND_BLUE;
265 break;
266 case 35: /* Magenta */
267 attr &= ~FOREGROUND_ALL;
268 attr |= FOREGROUND_RED | FOREGROUND_BLUE;
269 break;
270 case 36: /* Cyan */
271 attr &= ~FOREGROUND_ALL;
272 attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;
273 break;
274 case 37: /* White */
275 attr |= FOREGROUND_RED |
276 FOREGROUND_GREEN |
277 FOREGROUND_BLUE;
278 break;
279 case 38: /* Unknown */
280 break;
281 case 39: /* reset */
282 attr &= ~FOREGROUND_ALL;
283 attr |= (plain_attr & FOREGROUND_ALL);
284 break;
285 case 40: /* Black */
286 attr &= ~BACKGROUND_ALL;
287 break;
288 case 41: /* Red */
289 attr &= ~BACKGROUND_ALL;
290 attr |= BACKGROUND_RED;
291 break;
292 case 42: /* Green */
293 attr &= ~BACKGROUND_ALL;
294 attr |= BACKGROUND_GREEN;
295 break;
296 case 43: /* Yellow */
297 attr &= ~BACKGROUND_ALL;
298 attr |= BACKGROUND_RED | BACKGROUND_GREEN;
299 break;
300 case 44: /* Blue */
301 attr &= ~BACKGROUND_ALL;
302 attr |= BACKGROUND_BLUE;
303 break;
304 case 45: /* Magenta */
305 attr &= ~BACKGROUND_ALL;
306 attr |= BACKGROUND_RED | BACKGROUND_BLUE;
307 break;
308 case 46: /* Cyan */
309 attr &= ~BACKGROUND_ALL;
310 attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;
311 break;
312 case 47: /* White */
313 attr |= BACKGROUND_RED |
314 BACKGROUND_GREEN |
315 BACKGROUND_BLUE;
316 break;
317 case 48: /* Unknown */
318 break;
319 case 49: /* reset */
320 attr &= ~BACKGROUND_ALL;
321 attr |= (plain_attr & BACKGROUND_ALL);
322 break;
323 default:
324 /* Unsupported code */
325 break;
328 set_console_attr();
329 break;
330 case 'K':
331 erase_in_line();
332 break;
333 default:
334 /* Unsupported code */
335 break;
339 enum {
340 TEXT = 0, ESCAPE = 033, BRACKET = '['
343 static DWORD WINAPI console_thread(LPVOID unused)
345 unsigned char buffer[BUFFER_SIZE];
346 DWORD bytes;
347 int start, end = 0, c, parampos = 0, state = TEXT;
348 int params[MAX_PARAMS];
350 while (1) {
351 /* read next chunk of bytes from the pipe */
352 if (!ReadFile(hread, buffer + end, BUFFER_SIZE - end, &bytes,
353 NULL)) {
354 /* exit if pipe has been closed or disconnected */
355 if (GetLastError() == ERROR_PIPE_NOT_CONNECTED ||
356 GetLastError() == ERROR_BROKEN_PIPE)
357 break;
358 /* ignore other errors */
359 continue;
362 /* scan the bytes and handle ANSI control codes */
363 bytes += end;
364 start = end = 0;
365 while (end < bytes) {
366 c = buffer[end++];
367 switch (state) {
368 case TEXT:
369 if (c == ESCAPE) {
370 /* print text seen so far */
371 if (end - 1 > start)
372 write_console(buffer + start,
373 end - 1 - start);
375 /* then start parsing escape sequence */
376 start = end - 1;
377 memset(params, 0, sizeof(params));
378 parampos = 0;
379 state = ESCAPE;
381 break;
383 case ESCAPE:
384 /* continue if "\033[", otherwise bail out */
385 state = (c == BRACKET) ? BRACKET : TEXT;
386 break;
388 case BRACKET:
389 /* parse [0-9;]* into array of parameters */
390 if (c >= '0' && c <= '9') {
391 params[parampos] *= 10;
392 params[parampos] += c - '0';
393 } else if (c == ';') {
395 * next parameter, bail out if out of
396 * bounds
398 parampos++;
399 if (parampos >= MAX_PARAMS)
400 state = TEXT;
401 } else {
403 * end of escape sequence, change
404 * console attributes
406 set_attr(c, params, parampos + 1);
407 start = end;
408 state = TEXT;
410 break;
414 /* print remaining text unless parsing an escape sequence */
415 if (state == TEXT && end > start) {
416 /* check for incomplete UTF-8 sequences and fix end */
417 if (buffer[end - 1] >= 0x80) {
418 if (buffer[end -1] >= 0xc0)
419 end--;
420 else if (end - 1 > start &&
421 buffer[end - 2] >= 0xe0)
422 end -= 2;
423 else if (end - 2 > start &&
424 buffer[end - 3] >= 0xf0)
425 end -= 3;
428 /* print remaining complete UTF-8 sequences */
429 if (end > start)
430 write_console(buffer + start, end - start);
432 /* move remaining bytes to the front */
433 if (end < bytes)
434 memmove(buffer, buffer + end, bytes - end);
435 end = bytes - end;
436 } else {
437 /* all data has been consumed, mark buffer empty */
438 end = 0;
442 /* check if the console font supports unicode */
443 warn_if_raster_font();
445 CloseHandle(hread);
446 return 0;
449 static void winansi_exit(void)
451 /* flush all streams */
452 _flushall();
454 /* signal console thread to exit */
455 FlushFileBuffers(hwrite);
456 DisconnectNamedPipe(hwrite);
458 /* wait for console thread to copy remaining data */
459 WaitForSingleObject(hthread, INFINITE);
461 /* cleanup handles... */
462 CloseHandle(hwrite);
463 CloseHandle(hthread);
466 static void die_lasterr(const char *fmt, ...)
468 va_list params;
469 va_start(params, fmt);
470 errno = err_win_to_posix(GetLastError());
471 die_errno(fmt, params);
472 va_end(params);
475 #undef dup2
476 int winansi_dup2(int oldfd, int newfd)
478 int ret = dup2(oldfd, newfd);
480 if (!ret && newfd >= 0 && newfd <= 2)
481 fd_is_interactive[newfd] = oldfd < 0 || oldfd > 2 ?
482 0 : fd_is_interactive[oldfd];
484 return ret;
487 static HANDLE duplicate_handle(HANDLE hnd)
489 HANDLE hresult, hproc = GetCurrentProcess();
490 if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE,
491 DUPLICATE_SAME_ACCESS))
492 die_lasterr("DuplicateHandle(%li) failed",
493 (long) (intptr_t) hnd);
494 return hresult;
497 static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
500 * Create a copy of the original handle associated with fd
501 * because the original will get closed when we dup2().
503 HANDLE handle = (HANDLE)_get_osfhandle(fd);
504 HANDLE duplicate = duplicate_handle(handle);
506 /* Create a temp fd associated with the already open "new_handle". */
507 int new_fd = _open_osfhandle((intptr_t)new_handle, O_BINARY);
509 assert((fd == 1) || (fd == 2));
512 * Use stock dup2() to re-bind fd to the new handle. Note that
513 * this will implicitly close(1) and close both fd=1 and the
514 * originally associated handle. It will open a new fd=1 and
515 * call DuplicateHandle() on the handle associated with new_fd.
516 * It is because of this implicit close() that we created the
517 * copy of the original.
519 * Note that we need to update the cached console handle to the
520 * duplicated one because the dup2() call will implicitly close
521 * the original one.
523 * Note that dup2() when given target := {0,1,2} will also
524 * call SetStdHandle(), so we don't need to worry about that.
526 if (console == handle)
527 console = duplicate;
528 dup2(new_fd, fd);
530 /* Close the temp fd. This explicitly closes "new_handle"
531 * (because it has been associated with it).
533 close(new_fd);
535 if (fd == 2)
536 setvbuf(stderr, NULL, _IONBF, BUFSIZ);
537 fd_is_interactive[fd] |= FD_SWAPPED;
539 return duplicate;
542 #ifdef DETECT_MSYS_TTY
544 #include <winternl.h>
546 #if defined(_MSC_VER)
548 typedef struct _OBJECT_NAME_INFORMATION
550 UNICODE_STRING Name;
551 WCHAR NameBuffer[FLEX_ARRAY];
552 } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
554 #define ObjectNameInformation 1
556 #else
557 #include <ntstatus.h>
558 #endif
560 static void detect_msys_tty(int fd)
562 ULONG result;
563 BYTE buffer[1024];
564 POBJECT_NAME_INFORMATION nameinfo = (POBJECT_NAME_INFORMATION) buffer;
565 PWSTR name;
567 /* check if fd is a pipe */
568 HANDLE h = (HANDLE) _get_osfhandle(fd);
569 if (GetFileType(h) != FILE_TYPE_PIPE)
570 return;
572 /* get pipe name */
573 if (!NT_SUCCESS(NtQueryObject(h, ObjectNameInformation,
574 buffer, sizeof(buffer) - 2, &result)))
575 return;
576 name = nameinfo->Name.Buffer;
577 name[nameinfo->Name.Length / sizeof(*name)] = 0;
580 * Check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX')
581 * or a cygwin pty pipe ('cygwin-XXXX-ptyN-XX')
583 if ((!wcsstr(name, L"msys-") && !wcsstr(name, L"cygwin-")) ||
584 !wcsstr(name, L"-pty"))
585 return;
587 if (fd == 2)
588 setvbuf(stderr, NULL, _IONBF, BUFSIZ);
589 fd_is_interactive[fd] |= FD_MSYS;
592 #endif
595 * Wrapper for isatty(). Most calls in the main git code
596 * call isatty(1 or 2) to see if the instance is interactive
597 * and should: be colored, show progress, paginate output.
598 * We lie and give results for what the descriptor WAS at
599 * startup (and ignore any pipe redirection we internally
600 * do).
602 #undef isatty
603 int winansi_isatty(int fd)
605 if (fd >= 0 && fd <= 2)
606 return fd_is_interactive[fd] != 0;
607 return isatty(fd);
610 void winansi_init(void)
612 int con1, con2;
613 wchar_t name[32];
615 /* check if either stdout or stderr is a console output screen buffer */
616 con1 = is_console(1);
617 con2 = is_console(2);
619 /* Also compute console bit for fd 0 even though we don't need the result here. */
620 is_console(0);
622 if (!con1 && !con2) {
623 #ifdef DETECT_MSYS_TTY
624 /* check if stdin / stdout / stderr are MSYS2 pty pipes */
625 detect_msys_tty(0);
626 detect_msys_tty(1);
627 detect_msys_tty(2);
628 #endif
629 return;
632 /* create a named pipe to communicate with the console thread */
633 if (swprintf(name, ARRAY_SIZE(name) - 1, L"\\\\.\\pipe\\winansi%lu",
634 GetCurrentProcessId()) < 0)
635 die("Could not initialize winansi pipe name");
636 hwrite = CreateNamedPipeW(name, PIPE_ACCESS_OUTBOUND,
637 PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);
638 if (hwrite == INVALID_HANDLE_VALUE)
639 die_lasterr("CreateNamedPipe failed");
641 hread = CreateFileW(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
642 if (hread == INVALID_HANDLE_VALUE)
643 die_lasterr("CreateFile for named pipe failed");
645 /* start console spool thread on the pipe's read end */
646 hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL);
647 if (hthread == INVALID_HANDLE_VALUE)
648 die_lasterr("CreateThread(console_thread) failed");
650 /* schedule cleanup routine */
651 if (atexit(winansi_exit))
652 die_errno("atexit(winansi_exit) failed");
654 /* redirect stdout / stderr to the pipe */
655 if (con1)
656 hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite));
657 if (con2)
658 hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite));
662 * Returns the real console handle if stdout / stderr is a pipe redirecting
663 * to the console. Allows spawn / exec to pass the console to the next process.
665 HANDLE winansi_get_osfhandle(int fd)
667 HANDLE ret;
669 if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
670 return hconsole1;
671 if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
672 return hconsole2;
674 ret = (HANDLE)_get_osfhandle(fd);
677 * There are obviously circumstances under which _get_osfhandle()
678 * returns (HANDLE)-2. This is not documented anywhere, but that is so
679 * clearly an invalid handle value that we can just work around this
680 * and return the correct value for invalid handles.
682 return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret;