/* djgpp_sgr_support.c Copyright (C) 2010-2015 DJ Delorie, see COPYING.DJ for details. */ /* Messy DOS/DJGPP-specific code for emulating a Posix terminal driver wrt SGR (a.k.a. ANSI) color escape sequences. This has several aspects: * Redirecting output with ANSI color commands to direct screen writes. */ #include /* Screen write redirection. We need this to support colorization without requiring ANSI.SYS driver (or its work-alike) to be loaded. This function uses the DJGPP filesystem extensions mechanism. It is installed as a handler for handle-based functions (read/write/close) for the standard output (but actually only handles writes, only if the standard output is connected to the terminal, and only if user asked for colorization). When a buffer is written to the screen by low-level functions of the DJGPP C library, our handler will be called. For any request that doesn't require colored screen writes we return a zero to the caller, in which case the caller will handle the output in the usual way (by eventually calling DOS). When colorization *is* required, the buffer is written directly to the screen while converting the ANSI escape sequences into calls to DJGPP conio functions which change text attributes. A non-zero value is then returned to the caller to signal that the output has been handled. */ #include #include #include #include #include #include #include #include /* for `_dos_ds' */ #include /* for `ScreenGetCursor */ #include void *xmalloc(size_t size); void *xrealloc(void *ptr, size_t size); static int norm_blink = -1, cur_blink = -1; static unsigned char norm_attr = 0, cur_attr = 0; static int isatty_stdout = -1; static size_t leftover = 0; /* Restore the BIOS blinking bit to its original value. Called at exit. */ static void restore_blink_bit(void) { if (cur_blink != norm_blink) { if (norm_blink > 0) blinkvideo (); else intensevideo (); } } /* Write a buffer to the screen video memory. This expands the TAB characters to the appropriate number of spaces, and also does TRT with null characters and other non-printable characters, if any. */ static void screen_puts(char *buf, char *buf_end) { register char *p = buf, *q = p; int row, col; unsigned char c; while (p < buf_end) { if (*p < ' ') { switch (*p) { case '\b': case '\r': case '\n': /* nothing: cputs already does TRT with these */ break; case '\t': *p = '\0'; cputs (q); ScreenGetCursor(&row, &col); for (cputs (" "), col += 1; col % 8; col++) cputs (" "); q = p + 1; *p = '\t'; break; default: c = *p; *p = '\0'; cputs (q); cputs ("^"); putch (c | 0x40); q = p + 1; *p = c; break; } } p++; } /* Output whatever is left. */ cputs (q); } #define ESC '\033' #define IS_SGR(s) (((s)[1] == '[') && ((s)[2] == 'm')) #define IS_EL(s) (((s)[1] == '[') && ((s)[2] == 'K')) /* Screen writes redirector function. */ static int msdos_screen_write(__FSEXT_Fnumber func, int *retval, va_list rest_args) { static char *cbuf = NULL; static size_t cbuf_len = 0; /* Only dark colors mentioned here, so that bold has visible effect. */ static enum COLORS screen_color[] = { BLACK, RED, GREEN, BROWN, BLUE, MAGENTA, CYAN, LIGHTGRAY }; char *anchor, *p_next; unsigned char fg, bg; int handle; char *buf, *buf_end; size_t buflen; /* Avoid direct screen writes unless colorization was actually requested. Otherwise, we will break programs that catch I/O from their children. */ handle = va_arg(rest_args, int); if (!do_colour || func != __FSEXT_write || !(handle == STDOUT_FILENO ? isatty_stdout : isatty (handle))) return 0; buf = va_arg(rest_args, char *); if (!buf) { errno = EINVAL; *retval = -1; return 1; } /* Allocate a sufficiently large buffer to hold the output. */ buflen = va_arg(rest_args, size_t); if (!cbuf) { struct text_info txtinfo; cbuf_len = buflen + 1; cbuf = (char *)xmalloc(cbuf_len); gettextinfo (&txtinfo); norm_attr = txtinfo.attribute; /* save the original text attribute */ cur_attr = norm_attr; /* Does it normally blink when bg has its 3rd bit set? */ norm_blink = (_farpeekb(_dos_ds, 0x465) & 0x20) ? 1 : 0; cur_blink = norm_blink; } else if (buflen >= cbuf_len) { cbuf_len = buflen + 1 + leftover; cbuf = (char *)xrealloc(cbuf, cbuf_len); } memcpy(cbuf + leftover, buf, buflen); buf_end = cbuf + buflen + leftover; *buf_end = '\0'; /* Current text attributes are used as baseline. */ fg = cur_attr & 15; bg = (cur_attr >> 4) & 15; /* Walk the buffer, writing text directly to video RAM, changing color attributes when an escape sequence is seen. */ for (anchor = p_next = cbuf; (p_next = memchr(p_next, ESC, buflen - (p_next - cbuf))) != 0;) { char *p = p_next; /* If some chars seen since the last escape sequence, write it out to the screen using current text attributes. */ if (p > anchor) { *p = '\0'; /* `cputs' needs ASCIIZ string */ screen_puts(anchor, p); *p = ESC; /* restore the ESC character */ anchor = p; } /* Handle the null escape sequence (ESC-[m), which is used to restore the original color. */ if (IS_SGR(p)) { textattr(norm_attr); p += 3; anchor = p_next = p; continue; } /* Handle the erase in line to the right escape sequence (ESC-[K). */ if (IS_EL(p)) { clreol(); p += 3; anchor = p_next = p; continue; } if (p[1] == '[') /* "Esc-[" sequence */ { p += 2; /* get past "Esc-[" sequence */ p_next = p; while (*p != 'm') /* `m' ends the escape sequence */ { char *q; long code = strtol(p, &q, 10); if (!*q) { /* Incomplete escape sequence. Remember the part we've seen for the next time. */ leftover = q - anchor; if (leftover >= cbuf_len) { cbuf_len += 1 + leftover; cbuf = (char *)xrealloc(cbuf, cbuf_len); } strcpy (cbuf, anchor); *retval = buflen; /* that's a lie, but we have to! */ return 1; } /* Sanity checks: q > p unless p doesn't point to a number; SGR codes supported by ANSI.SYS are between 0 and 49; Each SGR code ends with a `;' or an `m'. If any of the above is violated, we just ignore the bogon. */ if (q == p || code > 49 || code < 0 || (*q != 'm' && *q != ';')) { p_next = q; break; } if (*q == ';') /* more codes to follow */ q++; /* Convert ANSI codes to color fore- and background. */ switch (code) { case 0: /* all attributes off */ fg = norm_attr & 15; bg = (norm_attr >> 4) & 15; break; case 1: /* intensity on */ fg |= 8; break; case 4: /* underline on */ fg |= 8; /* we can't, so make it bold instead */ break; case 5: /* blink */ if (cur_blink != 1) { blinkvideo(); /* ensure we are'nt in bright bg mode */ cur_blink = 1; } bg |= 8; break; case 7: /* reverse video */ { unsigned char t = fg; fg = bg; bg = t; /* If it was blinking before, let it blink after. */ if (fg & 8) bg |= 8; /* If the fg was bold, let the background be bold. */ if ((t & 8) && cur_blink != 0) { intensevideo(); cur_blink = 0; } } break; case 8: /* concealed on */ fg = (bg & 7) | 8; /* make fg be like bg, only bright */ break; case 30: case 31: case 32: case 33: /* foreground color */ case 34: case 35: case 36: case 37: fg = (fg & 8) | (screen_color[code - 30] & 15); break; case 40: case 41: case 42: case 43: /* background color */ case 44: case 45: case 46: case 47: bg = (bg & 8) | (screen_color[code - 40] & 15); break; case 39: /* default fg */ fg = norm_attr & 15; break; case 49: bg = (norm_attr >> 4) & 15; break; default: p_next = q; /* ignore unknown codes */ break; } p = q; } /* while loop */ if (*p == 'm' && p > p_next) { /* They don't *really* want it invisible, do they? */ if (fg == (bg & 7)) fg |= 8; /* make it concealed instead */ /* Construct the text attribute and set it. */ cur_attr = (bg << 4) | fg; textattr(cur_attr); p_next = anchor = p + 1; } else break; } else p_next++; } /* for loop */ /* Output what's left in the buffer. */ screen_puts (anchor, buf_end); leftover = 0; *retval = buflen; return 1; } /* This is called before `main' to install our STDOUT redirector. */ static void __attribute__((constructor)) djgpp_grep_startup(void) { __FSEXT_set_function(STDOUT_FILENO, msdos_screen_write); isatty_stdout = isatty(STDOUT_FILENO); atexit(restore_blink_bit); }