/* cecho.c - Echo with colors. Jason Hood, 8 to 12 July, 2004. DOS version requires ANSI. */ #define PVERS "0.90" #define PDATE "12 July, 2004" #include #include #include #ifdef _WIN32 #include #ifdef __MINGW32__ int _CRT_glob = 0; #endif HANDLE console; char* GetCommandTail( void ) { char* cmdline = GetCommandLine(); int sep; if (*cmdline == '\"') { sep = '\"'; ++cmdline; } else { sep = ' '; } while (*cmdline != sep) // find the end of the program ++cmdline; if (sep == '\"') ++cmdline; return cmdline + 1; // skip the space after it } int current_attr( void ) { CONSOLE_SCREEN_BUFFER_INFO i; return (GetConsoleScreenBufferInfo( console, &i )) ? i.wAttributes : 7; } #define set_attr( attr ) SetConsoleTextAttribute( console, attr ) #else int redirected; #if defined( __DJGPP__ ) #include #include #include #include #include #include #include int _crt0_startup_flags = (_CRT0_FLAG_DISALLOW_RESPONSE_FILES | _CRT0_FLAG_KEEP_QUOTES); char** __crt0_glob_function( char *d ) { return 0; } // Use the PSP command tail if called from the command line, otherwise // concat all the arguments. Modified from redir.c. char* GetCommandTail( void ) { extern char __PROXY[]; /* defined on crt1.c */ extern size_t __PROXY_LEN; static char doscmd[129]; char* tail = doscmd + 1; char* tp = tail; int len, i; if (!getenv( __PROXY )) { movedata( _stubinfo->psp_selector, 128, _my_ds(), (int)doscmd, 128 ); tail[(unsigned char)*doscmd] = '\0'; while (*tp == ' ') tp++; if (strncmp( tp, __PROXY+1, __PROXY_LEN-1 ) != 0) return tail + 1; } // Create a single string from the passed arguments. len = 0; for (i = __crt0_argc; i > 0; --i) len += strlen( __crt0_argv[i] ) + 1; tp = tail = malloc( len ); if (tail == NULL) return __crt0_argv[1]; // better than nothing for (i = 1; i < __crt0_argc; ++i) { tp = stpcpy( tp, __crt0_argv[i] ); *tp++ = ' '; } *--tp = '\0'; return tail; } // I don't know of any way to definitively determine the current color without // actually writing a character. The video buffer always has some off-screen // lines available, so write a character there and read its color. // ScreenGetChar is bound to the screen dimensions, so I implement it directly. // sc.h #include #define dossel _go32_info_block.selector_for_linear_memory #define co80 _go32_info_block.linear_address_of_primary_screen int current_attr( void ) { int row, col; ScreenGetCursor( &row, &col ); ScreenSetCursor( ScreenRows(), 0 ); putchar( ' ' ); fflush( stdout ); ScreenSetCursor( row, col ); return _farpeekb( dossel, co80 + ScreenRows() * ScreenCols() * 2 + 1 ); } #else // assume 16-bit DOS (tested with BC3.1) #include #include char* GetCommandTail( void ) { static char tail[129]; _fmemcpy( tail, MK_FP( _psp, 0x80 ), 128 ); tail[1 + (unsigned char)*tail] = '\0'; return tail + 2; } int current_attr( void ) { asm { mov ah,0x0f // get current page in BH int 0x10 mov ah,3 // get cursor position in DX int 0x10 push dx sub ax,ax mov es,ax mov dh,[es:0x484] // last row inc dh // move off-screen mov dl,0 mov ah,2 // set cursor position int 0x10 mov al,' ' int 0x29 // fast console output mov al,8 int 0x29 mov ah,8 // get character (AL) and attribute (AH) int 0x10 mov al,ah mov ah,0 pop dx // restore original cursor position push ax mov ah,2 int 0x10 pop ax } return _AX; } #endif // Output a minimal ANSI escape sequence to set the specified attribute. void set_attr( int attr ) { static int pc_to_ansi[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; static int fg, bg, bold, blink; char ansi[16]; int code; int i, p; if (redirected) return; ansi[0] = '\033'; ansi[1] = '['; p = 2; code = 0; // Bold and blink can't be turned off individually, so turning off one // requires turning the other back on, as well as restoring the colors. i = (attr & 0x08) ? '1' : '0'; if (i != bold) { ansi[p++] = bold = i; if (bold == '0') { fg = '7'; bg = '0'; blink = '0'; } code |= 1; } i = (attr & 0x80) ? '5' : '0'; if (i != blink) { blink = i; if (blink == '0') { if (bold != '0') { if (code & 1) --p; ansi[p++] = '0'; ansi[p++] = ';'; ansi[p++] = bold; } else { ansi[p++] = '0'; } fg = '7'; bg = '0'; } else { if (code) ansi[p++] = ';'; ansi[p++] = blink; } code |= 2; } i = '0' + pc_to_ansi[attr & 0x07]; if (i != fg) { if (code) ansi[p++] = ';'; ansi[p++] = '3'; ansi[p++] = fg = i; code |= 4; } i = '0' + pc_to_ansi[(attr >> 4) & 0x07]; if (i != bg) { if (code) ansi[p++] = ';'; ansi[p++] = '4'; ansi[p++] = bg = i; //code |= 8; } if (!(attr & 0x8000) && p > 2) // 0x8000 is used to just initialise { // p == 2 means color hasn't changed ansi[p++] = 'm'; ansi[p++] = '\0'; fputs( ansi, stdout ); } } #endif #define STREQ( str1, str2 ) (strcmp( str1, str2 ) == 0) #ifndef __LCC__ // LCC-Win32 includes ctype.h automatically #define isxdigit( x ) (((x) >= '0' && (x) <= '9') ||\ (((x) | 0x20) >= 'a' && ((x) | 0x20) <= 'f')) #endif #define x2b( x ) (((x) > '9') ? ((x) | 0x20) - 'a' + 0x0a : (x) - '0') void map( int ); void help( void ); // Translate a '#' sequence into its literal character. // Returns -1 if no valid sequence found. int xlat( char** s ) { int i = -1; char* line = *s; switch (*line) { case '\0': --line; // find it again in order to stop case '#': i = '#'; break; case 'a': i = '&'; break; // and (or ampersand) case 'o': i = '|'; break; // or case 's': i = '<'; break; // smaller than case 'l': i = '>'; break; // larger than case ',': i = '\t'; break; case '_': i = '\n'; break; // same as PROMPT case 'd': i = '\a'; break; // ding! case 'h': i = '\b'; break; // as in Ctrl+H case '[': i = '\033'; break; // likewise case 'x': if (isxdigit( line[1] )) { ++line; i = x2b( *line ); if (isxdigit( line[1] )) { ++line; i = (i << 4) | x2b( *line ); } } break; #ifdef _WIN32_maybe_later case 'u': if (isxdigit( line[1] )) { int d = 0; i = 0; do { ++line; i = (i << 4) | x2b( *line ); } while (++d < 4 && (isxdigit( line[1] ))); WCHAR w[2]; CHAR c[2]; w[0] = i; w[1] = '\0'; CharToOemW( w, c ); putchar( c[0] ); } break; #endif } *s = line; return i; } // Black (Noir), Blue, Green, Cyan, Red, Magenta, Yellow, White, current // Upper case is bright, lower case is dim. const char* const colors = "nbgcrmywNBGCRMYW:"; int cur_attr; int main( int argc, char* argv[] ) { char* line; int fg, bg, exit_attr; int stopped = 0; int newline = 1; int i, c; if (argc == 1 || STREQ( argv[1], "/?" ) || STREQ( argv[1], "--help" )) { help(); return 0; } #ifdef _WIN32 console = GetStdHandle( STD_OUTPUT_HANDLE ); #else redirected = !isatty( 1 ); if (redirected) cur_attr = 0; else #endif cur_attr = current_attr(); exit_attr = cur_attr; fg = cur_attr & 0x0f; bg = cur_attr >> 4; #ifndef _WIN32 set_attr( 0x8000 | cur_attr ); // initialise the ANSI data #endif if (argc == 2 && argv[1][0] == '-' && argv[1][1] == '-') { if (strncmp( argv[1] + 2, "map", 3 ) == 0) { map( argv[1][5] ); return 0; } if (argv[1][2] >= '0' && argv[1][2] <= '9') { i = atoi( argv[1] + 2 ); if (i < 256) { set_attr( i ); return 0; } } } line = GetCommandTail(); while (*line) { if (*line != '#') { putchar( *line ); } else if (stopped) { if (line[1] == '+') { ++line; stopped = 0; } else { putchar( '#' ); } } else { ++line; switch (*line) { case '~': set_attr( cur_attr ); break; case '=': exit_attr = fg | (bg << 4); break; case ';': newline = 0; break; case '-': stopped = 1; break; case '.': fputs( line + 1, stdout ); goto done; case '\0': putchar( '#' ); goto done; default: { char* p = strchr( colors, *line ); if (p != NULL) { if (*line != ':') { fg = p - (char*)colors; } p = strchr( colors, line[1] ); if (p != NULL) { ++line; if (*line != ':') { bg = p - (char*)colors; } } set_attr( fg | (bg << 4) ); } else if (*line >= '0' && *line <= '9') { i = *line++ - '0'; if (*line >= '0' && *line <= '9') { i = i * 10 + (*line++ - '0'); } if (*line == '\0') { putchar( '#' ); line -= 2; if (i > 9) --line; } else { if (*line == '#') { ++line; c = xlat( &line ); if (c == -1) c = '#'; } else { c = *line; } while (i--) putchar( c ); } } else if ((c = xlat( &line )) != -1) { putchar( c ); } else { putchar( '#' ); putchar( *line ); } } break; } } ++line; } done: set_attr( exit_attr ); if (newline) putchar( '\n' ); return cur_attr; } // Show all the foreground colors on differing background colors. // Start from bs and stop before be. void color_map( int bs, int be ) { int f, b; int all = (bs == 0 && be == 16); for (f = 0; f < 16; ++f) { for (b = bs; b < be; ++b) { if (!all) fputs( " " , stdout ); set_attr( (b << 4) | f ); printf( " #%c%c ", colors[f], colors[b] ); if (!all) set_attr( cur_attr ); } if (!all) putchar( '\n' ); } if (all) set_attr( cur_attr ); } // Show all the extended (upper) ASCII characters. void char_map( void ) { int r, c; int a = (cur_attr & 0xf0) | 11; // current background, bright cyan fg for (r = 0x80; r < 0x90; ++r) { for (c = r; c < 0x100; c += 0x10) { set_attr( a ); printf( " #x%x %c", c, c ); a ^= 5; // alternate between bright cyan/yellow } a ^= 5; putchar( '\n' ); } set_attr( cur_attr ); } // Display a color or character map, depending on type. void map( int type ) { putchar( '\n' ); switch (type) { default: color_map( 0, 8 ); break; // normal background case 'b': color_map( 8, 16 ); break; // bright background case 'B': color_map( 0, 16 ); break; // both case 'x': char_map(); break; // extended ASCII } } void help( void ) { puts( "Color Echo by Jason Hood .\n" "Version "PVERS" ("PDATE"). Public Domain.\n" "http://misc.adoxa.cjb.net/\n" "\n" "Display text using any color you like" #ifndef _WIN32 " (requires ANSI)" #endif ".\n" "\n" "Usage: cecho text\n" " cecho --map[b|B|x]\n" " cecho --\n" "\n" "\"text\" is output as-is, with the exception of '#' sequences:\n" "\n" "fb\tforeground color and optional background color:\n" "\t n black N dark gray : current color\n" "\t r red R bright red\n" "\t g green G bright green\n" "\t b blue B bright blue\n" "\t c cyan C bright cyan\n" "\t m magenta M bright magenta\n" "\t y brown Y yellow\n" "\t w gray W white\n" "\n" "~\trestore original color\n" "=\tkeep current color on exit\n" ";\tdon't end with new line\n" "\n" "#\t#\t\t,\ttab\n" "a\t&\t\t_\tnew line\n" "o\t|\t\td\tding\n" "s\t<\t\th\tbackspace\n" "l\t>\t\t[\tescape\n" "\n" "xhh\tcharacter represented by one- or two-digit hexadecimal number\n" #ifdef _WIN32_maybe_later "uhhhh\tcharacter represented by one- to four-digit hexadecimal number\n" #endif "ddc\trepeat 'c' (which may be a '#' sequence) dd times, which may be\n" "\ta one- or two-digit decimal number\n" "\n" "-\tstop '#' processing\n" "+\tstart '#' processing (only if stopped)\n" ".\treally stop '#' processing (quote remaining text)\n" "\n" "Any character following '#' not present in the above list will be output\n" "untranslated (along with the '#').\n" "\n" "--map will display all the foreground colors with normal background colors;\n" "b will use the bright background colors; and B will display both (assumes 80\n" "columns). --mapx will display the extended ASCII characters.\n" "\n" "-- will set the color to , a decimal number between 0 and 255.\n" "The current attribute is returned when displaying \"text\"." #ifndef _WIN32 "\n\n" "Note: bright background colors might be displayed as blinking foreground." #endif ); }