compat / winansi.con commit Win32: warn if the console font doesn't support Unicode (1edeb9a)
   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 <malloc.h>
   8#include <wingdi.h>
   9#include <winreg.h>
  10
  11/*
  12 Functions to be wrapped:
  13*/
  14#undef printf
  15#undef fprintf
  16#undef fputs
  17#undef vfprintf
  18/* TODO: write */
  19
  20/*
  21 ANSI codes used by git: m, K
  22
  23 This file is git-specific. Therefore, this file does not attempt
  24 to implement any codes that are not used by git.
  25*/
  26
  27static HANDLE console;
  28static WORD plain_attr;
  29static WORD attr;
  30static int negative;
  31static FILE *last_stream = NULL;
  32
  33#ifdef __MINGW32__
  34typedef struct _CONSOLE_FONT_INFOEX {
  35        ULONG cbSize;
  36        DWORD nFont;
  37        COORD dwFontSize;
  38        UINT FontFamily;
  39        UINT FontWeight;
  40        WCHAR FaceName[LF_FACESIZE];
  41} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX;
  42#endif
  43
  44typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL,
  45                PCONSOLE_FONT_INFOEX);
  46
  47static void print_font_warning(void)
  48{
  49        warning("Your console font probably doesn\'t support Unicode. If "
  50                "you experience strange characters in the output, consider "
  51                "switching to a TrueType font such as Lucida Console!");
  52}
  53
  54static void check_truetype_font(void)
  55{
  56        static int truetype_font_checked;
  57        DWORD fontFamily = 0;
  58        PGETCURRENTCONSOLEFONTEX pGetCurrentConsoleFontEx;
  59
  60        /* don't do this twice */
  61        if (truetype_font_checked)
  62                return;
  63        truetype_font_checked = 1;
  64
  65        /* GetCurrentConsoleFontEx is available since Vista */
  66        pGetCurrentConsoleFontEx = (PGETCURRENTCONSOLEFONTEX) GetProcAddress(
  67                        GetModuleHandle("kernel32.dll"), "GetCurrentConsoleFontEx");
  68        if (pGetCurrentConsoleFontEx) {
  69                CONSOLE_FONT_INFOEX cfi;
  70                cfi.cbSize = sizeof(cfi);
  71                if (pGetCurrentConsoleFontEx(console, 0, &cfi))
  72                        fontFamily = cfi.FontFamily;
  73        } else {
  74                /* pre-Vista: check default console font in registry */
  75                HKEY hkey;
  76                if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console", 0,
  77                                KEY_READ, &hkey)) {
  78                        DWORD size = sizeof(fontFamily);
  79                        RegQueryValueExA(hkey, "FontFamily", NULL, NULL,
  80                                        (LPVOID) &fontFamily, &size);
  81                        RegCloseKey(hkey);
  82                }
  83        }
  84
  85        if (!(fontFamily & TMPF_TRUETYPE))
  86                atexit(print_font_warning);
  87}
  88
  89static int is_console(FILE *stream)
  90{
  91        CONSOLE_SCREEN_BUFFER_INFO sbi;
  92        HANDLE hcon;
  93
  94        static int initialized = 0;
  95
  96        /* use cached value if stream hasn't changed */
  97        if (stream == last_stream)
  98                return console != NULL;
  99
 100        last_stream = stream;
 101        console = NULL;
 102
 103        /* get OS handle of the stream */
 104        hcon = (HANDLE) _get_osfhandle(_fileno(stream));
 105        if (hcon == INVALID_HANDLE_VALUE)
 106                return 0;
 107
 108        /* check if its a handle to a console output screen buffer */
 109        if (!GetConsoleScreenBufferInfo(hcon, &sbi))
 110                return 0;
 111
 112        if (!initialized) {
 113                attr = plain_attr = sbi.wAttributes;
 114                negative = 0;
 115                initialized = 1;
 116        }
 117
 118        console = hcon;
 119        return 1;
 120}
 121
 122static int write_console(const char *str, size_t len)
 123{
 124        /* convert utf-8 to utf-16, write directly to console */
 125        int wlen = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0);
 126        wchar_t *wbuf = (wchar_t *) alloca(wlen * sizeof(wchar_t));
 127        MultiByteToWideChar(CP_UTF8, 0, str, len, wbuf, wlen);
 128
 129        WriteConsoleW(console, wbuf, wlen, NULL, NULL);
 130
 131        /*
 132         * if non-ascii characters are printed, check that the current console
 133         * font supports this
 134         */
 135        if (wlen != len)
 136                check_truetype_font();
 137
 138        /* return original (utf-8 encoded) length */
 139        return len;
 140}
 141
 142#define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
 143#define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
 144
 145static void set_console_attr(void)
 146{
 147        WORD attributes = attr;
 148        if (negative) {
 149                attributes &= ~FOREGROUND_ALL;
 150                attributes &= ~BACKGROUND_ALL;
 151
 152                /* This could probably use a bitmask
 153                   instead of a series of ifs */
 154                if (attr & FOREGROUND_RED)
 155                        attributes |= BACKGROUND_RED;
 156                if (attr & FOREGROUND_GREEN)
 157                        attributes |= BACKGROUND_GREEN;
 158                if (attr & FOREGROUND_BLUE)
 159                        attributes |= BACKGROUND_BLUE;
 160
 161                if (attr & BACKGROUND_RED)
 162                        attributes |= FOREGROUND_RED;
 163                if (attr & BACKGROUND_GREEN)
 164                        attributes |= FOREGROUND_GREEN;
 165                if (attr & BACKGROUND_BLUE)
 166                        attributes |= FOREGROUND_BLUE;
 167        }
 168        SetConsoleTextAttribute(console, attributes);
 169}
 170
 171static void erase_in_line(void)
 172{
 173        CONSOLE_SCREEN_BUFFER_INFO sbi;
 174        DWORD dummy; /* Needed for Windows 7 (or Vista) regression */
 175
 176        if (!console)
 177                return;
 178
 179        GetConsoleScreenBufferInfo(console, &sbi);
 180        FillConsoleOutputCharacterA(console, ' ',
 181                sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,
 182                &dummy);
 183}
 184
 185
 186static const char *set_attr(const char *str)
 187{
 188        const char *func;
 189        size_t len = strspn(str, "0123456789;");
 190        func = str + len;
 191
 192        switch (*func) {
 193        case 'm':
 194                do {
 195                        long val = strtol(str, (char **)&str, 10);
 196                        switch (val) {
 197                        case 0: /* reset */
 198                                attr = plain_attr;
 199                                negative = 0;
 200                                break;
 201                        case 1: /* bold */
 202                                attr |= FOREGROUND_INTENSITY;
 203                                break;
 204                        case 2:  /* faint */
 205                        case 22: /* normal */
 206                                attr &= ~FOREGROUND_INTENSITY;
 207                                break;
 208                        case 3:  /* italic */
 209                                /* Unsupported */
 210                                break;
 211                        case 4:  /* underline */
 212                        case 21: /* double underline */
 213                                /* Wikipedia says this flag does nothing */
 214                                /* Furthermore, mingw doesn't define this flag
 215                                attr |= COMMON_LVB_UNDERSCORE; */
 216                                break;
 217                        case 24: /* no underline */
 218                                /* attr &= ~COMMON_LVB_UNDERSCORE; */
 219                                break;
 220                        case 5:  /* slow blink */
 221                        case 6:  /* fast blink */
 222                                /* We don't have blink, but we do have
 223                                   background intensity */
 224                                attr |= BACKGROUND_INTENSITY;
 225                                break;
 226                        case 25: /* no blink */
 227                                attr &= ~BACKGROUND_INTENSITY;
 228                                break;
 229                        case 7:  /* negative */
 230                                negative = 1;
 231                                break;
 232                        case 27: /* positive */
 233                                negative = 0;
 234                                break;
 235                        case 8:  /* conceal */
 236                        case 28: /* reveal */
 237                                /* Unsupported */
 238                                break;
 239                        case 30: /* Black */
 240                                attr &= ~FOREGROUND_ALL;
 241                                break;
 242                        case 31: /* Red */
 243                                attr &= ~FOREGROUND_ALL;
 244                                attr |= FOREGROUND_RED;
 245                                break;
 246                        case 32: /* Green */
 247                                attr &= ~FOREGROUND_ALL;
 248                                attr |= FOREGROUND_GREEN;
 249                                break;
 250                        case 33: /* Yellow */
 251                                attr &= ~FOREGROUND_ALL;
 252                                attr |= FOREGROUND_RED | FOREGROUND_GREEN;
 253                                break;
 254                        case 34: /* Blue */
 255                                attr &= ~FOREGROUND_ALL;
 256                                attr |= FOREGROUND_BLUE;
 257                                break;
 258                        case 35: /* Magenta */
 259                                attr &= ~FOREGROUND_ALL;
 260                                attr |= FOREGROUND_RED | FOREGROUND_BLUE;
 261                                break;
 262                        case 36: /* Cyan */
 263                                attr &= ~FOREGROUND_ALL;
 264                                attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;
 265                                break;
 266                        case 37: /* White */
 267                                attr |= FOREGROUND_RED |
 268                                        FOREGROUND_GREEN |
 269                                        FOREGROUND_BLUE;
 270                                break;
 271                        case 38: /* Unknown */
 272                                break;
 273                        case 39: /* reset */
 274                                attr &= ~FOREGROUND_ALL;
 275                                attr |= (plain_attr & FOREGROUND_ALL);
 276                                break;
 277                        case 40: /* Black */
 278                                attr &= ~BACKGROUND_ALL;
 279                                break;
 280                        case 41: /* Red */
 281                                attr &= ~BACKGROUND_ALL;
 282                                attr |= BACKGROUND_RED;
 283                                break;
 284                        case 42: /* Green */
 285                                attr &= ~BACKGROUND_ALL;
 286                                attr |= BACKGROUND_GREEN;
 287                                break;
 288                        case 43: /* Yellow */
 289                                attr &= ~BACKGROUND_ALL;
 290                                attr |= BACKGROUND_RED | BACKGROUND_GREEN;
 291                                break;
 292                        case 44: /* Blue */
 293                                attr &= ~BACKGROUND_ALL;
 294                                attr |= BACKGROUND_BLUE;
 295                                break;
 296                        case 45: /* Magenta */
 297                                attr &= ~BACKGROUND_ALL;
 298                                attr |= BACKGROUND_RED | BACKGROUND_BLUE;
 299                                break;
 300                        case 46: /* Cyan */
 301                                attr &= ~BACKGROUND_ALL;
 302                                attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;
 303                                break;
 304                        case 47: /* White */
 305                                attr |= BACKGROUND_RED |
 306                                        BACKGROUND_GREEN |
 307                                        BACKGROUND_BLUE;
 308                                break;
 309                        case 48: /* Unknown */
 310                                break;
 311                        case 49: /* reset */
 312                                attr &= ~BACKGROUND_ALL;
 313                                attr |= (plain_attr & BACKGROUND_ALL);
 314                                break;
 315                        default:
 316                                /* Unsupported code */
 317                                break;
 318                        }
 319                        str++;
 320                } while (*(str-1) == ';');
 321
 322                set_console_attr();
 323                break;
 324        case 'K':
 325                erase_in_line();
 326                break;
 327        default:
 328                /* Unsupported code */
 329                break;
 330        }
 331
 332        return func + 1;
 333}
 334
 335static int ansi_emulate(const char *str, FILE *stream)
 336{
 337        int rv = 0;
 338        const char *pos = str;
 339
 340        fflush(stream);
 341
 342        while (*pos) {
 343                pos = strstr(str, "\033[");
 344                if (pos) {
 345                        size_t len = pos - str;
 346
 347                        if (len) {
 348                                size_t out_len = write_console(str, len);
 349                                rv += out_len;
 350                                if (out_len < len)
 351                                        return rv;
 352                        }
 353
 354                        str = pos + 2;
 355                        rv += 2;
 356
 357                        pos = set_attr(str);
 358                        rv += pos - str;
 359                        str = pos;
 360                } else {
 361                        size_t len = strlen(str);
 362                        rv += write_console(str, len);
 363                        return rv;
 364                }
 365        }
 366        return rv;
 367}
 368
 369int winansi_fputs(const char *str, FILE *stream)
 370{
 371        int rv;
 372
 373        if (!is_console(stream))
 374                return fputs(str, stream);
 375
 376        rv = ansi_emulate(str, stream);
 377
 378        if (rv >= 0)
 379                return 0;
 380        else
 381                return EOF;
 382}
 383
 384int winansi_vfprintf(FILE *stream, const char *format, va_list list)
 385{
 386        int len, rv;
 387        char small_buf[256];
 388        char *buf = small_buf;
 389        va_list cp;
 390
 391        if (!is_console(stream))
 392                goto abort;
 393
 394        va_copy(cp, list);
 395        len = vsnprintf(small_buf, sizeof(small_buf), format, cp);
 396        va_end(cp);
 397
 398        if (len > sizeof(small_buf) - 1) {
 399                buf = malloc(len + 1);
 400                if (!buf)
 401                        goto abort;
 402
 403                len = vsnprintf(buf, len + 1, format, list);
 404        }
 405
 406        rv = ansi_emulate(buf, stream);
 407
 408        if (buf != small_buf)
 409                free(buf);
 410        return rv;
 411
 412abort:
 413        rv = vfprintf(stream, format, list);
 414        return rv;
 415}
 416
 417int winansi_fprintf(FILE *stream, const char *format, ...)
 418{
 419        va_list list;
 420        int rv;
 421
 422        va_start(list, format);
 423        rv = winansi_vfprintf(stream, format, list);
 424        va_end(list);
 425
 426        return rv;
 427}
 428
 429int winansi_printf(const char *format, ...)
 430{
 431        va_list list;
 432        int rv;
 433
 434        va_start(list, format);
 435        rv = winansi_vfprintf(stdout, format, list);
 436        va_end(list);
 437
 438        return rv;
 439}