diff --git a/ANSI.c b/ANSI.c index 00811ad..ae894aa 100644 --- a/ANSI.c +++ b/ANSI.c @@ -180,12 +180,13 @@ added DECSTR & RIS; fix state problems with windowless processes. - v1.81-wip, 26 December, 2017: + v1.81-wip, 26 to 28 December, 2017: combine multiple CRs as one (to ignore all CRs before LF); don't process CR or BS during CRM; don't flush CR immediately (to catch following LF); fix CRM with all partial RM sequences; - check for the empty buffer within the critical section. + check for the empty buffer within the critical section; + palette improvements. */ #include "ansicon.h" @@ -229,7 +230,7 @@ TCHAR suffix2; // escape sequence intermediate byte int ibytes; // count of intermediate bytes int es_argc; // escape sequence args count int es_argv[MAX_ARG]; // escape sequence args -TCHAR Pt_arg[MAX_PATH*2]; // text parameter for Operating System Command +TCHAR Pt_arg[4096]; // text parameter for Operating System Command int Pt_len; BOOL shifted, G0_special, SaveG0; BOOL awm = TRUE; // autowrap mode @@ -369,6 +370,12 @@ typedef BOOL (WINAPI *PHCSBIX)( PHCSBIX GetConsoleScreenBufferInfoX, SetConsoleScreenBufferInfoX; +BOOL WINAPI GetConsoleScreenBufferInfoEx_repl( HANDLE h, + PCONSOLE_SCREEN_BUFFER_INFOX i ) +{ + return FALSE; +} + typedef struct _CONSOLE_FONT_INFOX { ULONG cbSize; @@ -426,7 +433,8 @@ typedef struct SHORT top_margin; SHORT bot_margin; COORD SavePos; // saved cursor position - COLORREF palette[16]; + COLORREF o_palette[16]; // original palette, for resetting + COLORREF x_palette[240]; // xterm 256-color palette, less 16 system colors SHORT buf_width; // buffer width prior to setting 132 columns SHORT win_width; // window width prior to setting 132 columns BYTE noclear; // don't clear the screen on column mode change @@ -439,6 +447,8 @@ PSTATE pState = &default_state; BOOL valid_state; HANDLE hMap; +#include "palette.h" + void set_ansicon( PCONSOLE_SCREEN_BUFFER_INFO ); @@ -480,26 +490,30 @@ void get_state( void ) FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); csbix.cbSize = sizeof(csbix); - if (GetConsoleScreenBufferInfoX && - GetConsoleScreenBufferInfoX( hConOut, &csbix )) + if (GetConsoleScreenBufferInfoX( hConOut, &csbix )) { Info.dwSize = csbix.dwSize; ATTR = csbix.wAttributes; WIN = csbix.srWindow; - memcpy( pState->palette, csbix.ColorTable, sizeof(csbix.ColorTable) ); + memcpy( pState->o_palette, csbix.ColorTable, sizeof(csbix.ColorTable) ); } - else if (!GetConsoleScreenBufferInfo( hConOut, &Info )) + else { - DEBUGSTR( 1, "Failed to get screen buffer info (%u) - assuming defaults", - GetLastError() ); - ATTR = 7; - WIDTH = 80; - HEIGHT = 300; - WIN.Left = 0; - WIN.Right = 79; - TOP = 0; - BOTTOM = 24; + memcpy( pState->o_palette, legacy_palette, sizeof(legacy_palette) ); + if (!GetConsoleScreenBufferInfo( hConOut, &Info )) + { + DEBUGSTR( 1, "Failed to get screen buffer info (%u) - assuming defaults", + GetLastError() ); + ATTR = 7; + WIDTH = 80; + HEIGHT = 300; + WIN.Left = 0; + WIN.Right = 79; + TOP = 0; + BOTTOM = 24; + } } + memcpy( pState->x_palette, xterm_palette, sizeof(xterm_palette) ); if (GetEnvironmentVariable( L"ANSICON_REVERSE", NULL, 0 )) { SetEnvironmentVariable( L"ANSICON_REVERSE", NULL ); @@ -985,7 +999,7 @@ void send_palette_sequence( COLORREF c ) r = GetRValue( c ); g = GetGValue( c ); b = GetBValue( c ); - if ((c & 0x0F0F0F) == ((c & 0xF0F0F0) >> 4)) + if ((c & 0x0F0F0F) == ((c >> 4) & 0x0F0F0F)) wsprintf( buf, L"#%X%X%X", r & 0xF, g & 0xF, b & 0xF ); else wsprintf( buf, L"#%02X%02X%02X", r, g, b ); @@ -1005,6 +1019,49 @@ void init_tabs( int size ) } +// Find the "distance" between two colors. +// https://www.compuphase.com/cmetric.htm +int color_distance( COLORREF c1, COLORREF c2 ) +{ + int rmean = (GetRValue( c1 ) + GetRValue( c2 )) / 2; + int r = GetRValue( c1 ) - GetRValue( c2 ); + int g = GetGValue( c1 ) - GetGValue( c2 ); + int b = GetBValue( c1 ) - GetBValue( c2 ); + return (((512 + rmean) * r * r) >> 8) + + 4 * g * g + + (((767 - rmean) * b * b) >> 8); +} + +// Find the nearest color to a system color. +int find_nearest_color( COLORREF col ) +{ + int d, d_min; + int i, idx; + CONSOLE_SCREEN_BUFFER_INFOX csbix; + const COLORREF* table; + + csbix.cbSize = sizeof(csbix); + table = (GetConsoleScreenBufferInfoX( hConOut, &csbix )) + ? csbix.ColorTable : legacy_palette; + + d_min = color_distance( col, table[0] ); + if (d_min == 0) return 0; + idx = 0; + for (i = 1; i < 16; ++i) + { + d = color_distance( col, table[i] ); + if (d < d_min) + { + if (d == 0) return i; + d_min = d; + idx = i; + } + } + + return idx; +} + + // ========== Reset void InterpretEscSeq( void ); @@ -1043,14 +1100,14 @@ void Reset( BOOL hard ) InterpretEscSeq(); screen_top = -1; csbix.cbSize = sizeof(csbix); - if (GetConsoleScreenBufferInfoX && - GetConsoleScreenBufferInfoX( hConOut, &csbix )) + if (GetConsoleScreenBufferInfoX( hConOut, &csbix )) { - memcpy( csbix.ColorTable, pState->palette, sizeof(csbix.ColorTable) ); + memcpy( csbix.ColorTable, pState->o_palette, sizeof(csbix.ColorTable) ); ++csbix.srWindow.Right; ++csbix.srWindow.Bottom; SetConsoleScreenBufferInfoX( hConOut, &csbix ); } + memcpy( pState->x_palette, xterm_palette, sizeof(xterm_palette) ); } } @@ -1206,6 +1263,10 @@ void InterpretEscSeq( void ) if (suffix2 == 0 || suffix2 == '+') switch (suffix) { case 'm': // SGR + { + BYTE b = FOREGROUND_INTENSITY; + BYTE u = BACKGROUND_INTENSITY; + if (es_argc == 0) es_argc++; // ESC[m == ESC[0m for (i = 0; i < es_argc; i++) { @@ -1232,26 +1293,42 @@ void InterpretEscSeq( void ) // only one parameter, which is divided into elements. So where // xterm does "38;2;R;G;B" it should really be "38:2:I:R:G:B" (I is // a color space identifier). - if (i+1 < es_argc) + if (++i < es_argc) { - if (es_argv[i+1] == 2) // rgb - i += 4; - else if (es_argv[i+1] == 5) // index + COLORREF col = CLR_INVALID; + int idx = -1; + int arg = es_argv[i-1]; + + if (es_argv[i] == 2) // rgb { - if (i+2 < es_argc && es_argv[i+2] < 16) + if (i+3 < es_argc) + col = RGB( es_argv[i+1], es_argv[i+2], es_argv[i+3] ); + i += 3; + } + else if (es_argv[i] == 5) // index + { + if (++i < es_argc) { - if (es_argv[i] == 38) - { - pState->sgr.foreground = es_argv[i+2]; - pState->sgr.bold = es_argv[i+2] & FOREGROUND_INTENSITY; - } - else - { - pState->sgr.background = es_argv[i+2]; - pState->sgr.underline = es_argv[i+2] & BACKGROUND_INTENSITY; - } + if (es_argv[i] < 16) + idx = es_argv[i]; + else if (es_argv[i] < 256) + col = pState->x_palette[es_argv[i] - 16]; + } + } + if (col != CLR_INVALID) + idx = attr2ansi[find_nearest_color( col )]; + if (idx != -1) + { + if (arg == 38) + { + pState->sgr.foreground = idx; + b = 0; + } + else + { + pState->sgr.background = idx; + u = 0; } - i += 2; } } } @@ -1308,20 +1385,22 @@ void InterpretEscSeq( void ) case 28: pState->sgr.concealed = 0; break; } } + b &= pState->sgr.bold; + u &= pState->sgr.underline; if (pState->sgr.concealed) { if (pState->sgr.rvideo) { attribut = foregroundcolor[pState->sgr.foreground] | backgroundcolor[pState->sgr.foreground]; - if (pState->sgr.bold) + if (b) attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; } else { attribut = foregroundcolor[pState->sgr.background] | backgroundcolor[pState->sgr.background]; - if (pState->sgr.underline) + if (u) attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; } } @@ -1329,17 +1408,16 @@ void InterpretEscSeq( void ) { attribut = foregroundcolor[pState->sgr.background] | backgroundcolor[pState->sgr.foreground]; - if (pState->sgr.bold) - attribut |= BACKGROUND_INTENSITY; - if (pState->sgr.underline) - attribut |= FOREGROUND_INTENSITY; + if (b) attribut |= BACKGROUND_INTENSITY; + if (u) attribut |= FOREGROUND_INTENSITY; } else - attribut = foregroundcolor[pState->sgr.foreground] | pState->sgr.bold - | backgroundcolor[pState->sgr.background] | pState->sgr.underline; + attribut = foregroundcolor[pState->sgr.foreground] | b + | backgroundcolor[pState->sgr.background] | u; if (pState->sgr.reverse) attribut = ((attribut >> 4) & 15) | ((attribut & 15) << 4); SetConsoleTextAttribute( hConOut, attribut ); + } return; case 'J': // ED @@ -1788,9 +1866,8 @@ void InterpretEscSeq( void ) { CONSOLE_SCREEN_BUFFER_INFOX csbix; csbix.cbSize = sizeof(csbix); - if (!GetConsoleScreenBufferInfoX || - !GetConsoleScreenBufferInfoX( hConOut, &csbix )) - return; + if (!GetConsoleScreenBufferInfoX( hConOut, &csbix )) + memcpy( csbix.ColorTable, legacy_palette, sizeof(legacy_palette) ); if (es_argv[0] == 4) { BYTE r, g, b; @@ -1800,7 +1877,7 @@ void InterpretEscSeq( void ) for (beg = Pt_arg;; beg = end + 1) { i = (int)wcstoul( beg, &end, 10 ); - if (end == beg || (*end != ';' && *end != '\0') || i >= 16) + if (end == beg || (*end != ';' && *end != '\0') || i >= 256) break; if (end[2] == ';' || end[2] == '\0') { @@ -1809,11 +1886,18 @@ void InterpretEscSeq( void ) SendSequence( L"\33]4;" ); end[1] = '\0'; SendSequence( beg ); - for (; i < 16; ++i) - { - send_palette_sequence( csbix.ColorTable[attr2ansi[i]] ); - SendSequence( (i == 15) ? L"\a" : L"," ); - } + if (i < 16) + for (; i < 16; ++i) + { + send_palette_sequence( csbix.ColorTable[attr2ansi[i]] ); + SendSequence( (i == 15) ? L"\a" : L"," ); + } + else + for (; i < 256; ++i) + { + send_palette_sequence( pState->x_palette[i - 16] ); + SendSequence( (i == 255) ? L"\a" : L"," ); + } } else if (end[1] == '?') { @@ -1825,7 +1909,8 @@ void InterpretEscSeq( void ) SendSequence( L";" ); end[1] = '\0'; SendSequence( beg ); - send_palette_sequence( csbix.ColorTable[attr2ansi[i]] ); + send_palette_sequence( (i < 16) ? csbix.ColorTable[attr2ansi[i]] + : pState->x_palette[i - 16] ); } else break; @@ -1905,8 +1990,13 @@ void InterpretEscSeq( void ) } } if (valid) - csbix.ColorTable[attr2ansi[i++]] = RGB( r, g, b ); - if (*end != ',' || i == 16) + { + if (i < 16) + csbix.ColorTable[attr2ansi[i++]] = RGB( r, g, b ); + else + pState->x_palette[i++ - 16] = RGB( r, g, b ); + } + if (*end != ',' || i == 256) { while (*end != ';' && *end != '\0') ++end; @@ -1924,25 +2014,36 @@ void InterpretEscSeq( void ) { // Reset each index, or the entire palette. if (Pt_len == 0) - memcpy( csbix.ColorTable, pState->palette, sizeof(csbix.ColorTable) ); + { + memcpy(csbix.ColorTable, pState->o_palette, sizeof(csbix.ColorTable)); + memcpy( pState->x_palette, xterm_palette, sizeof(xterm_palette) ); + } else { LPTSTR beg, end; for (beg = Pt_arg;; beg = end + 1) { i = (int)wcstoul( beg, &end, 10 ); - if (end == beg || (*end != ';' && *end != '\0') || i >= 16) + if (end == beg || (*end != ';' && *end != '\0') || i >= 256) break; - i = attr2ansi[i]; - csbix.ColorTable[i] = pState->palette[i]; + if (i < 16) + { + i = attr2ansi[i]; + csbix.ColorTable[i] = pState->o_palette[i]; + } + else + pState->x_palette[i - 16] = xterm_palette[i - 16]; if (*end == '\0') break; } } } - ++csbix.srWindow.Right; - ++csbix.srWindow.Bottom; - SetConsoleScreenBufferInfoX( hConOut, &csbix ); + if (SetConsoleScreenBufferInfoX) + { + ++csbix.srWindow.Right; + ++csbix.srWindow.Bottom; + SetConsoleScreenBufferInfoX( hConOut, &csbix ); + } } } } @@ -3709,6 +3810,8 @@ BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) hKernel = GetModuleHandleA( APIKernel ); GetConsoleScreenBufferInfoX = (PHCSBIX)GetProcAddress( hKernel, "GetConsoleScreenBufferInfoEx" ); + if (GetConsoleScreenBufferInfoX == NULL) + GetConsoleScreenBufferInfoX = GetConsoleScreenBufferInfoEx_repl; SetConsoleScreenBufferInfoX = (PHCSBIX)GetProcAddress( hKernel, "SetConsoleScreenBufferInfoEx" ); SetCurrentConsoleFontX = (PHBCFIX)GetProcAddress( diff --git a/ansicon.c b/ansicon.c index 646aafc..e50ce03 100644 --- a/ansicon.c +++ b/ansicon.c @@ -91,7 +91,7 @@ use -pu to unload from the parent. */ -#define PDATE L"26 December, 2017" +#define PDATE L"28 December, 2017" #include "ansicon.h" #include "version.h" diff --git a/palette.h b/palette.h new file mode 100644 index 0000000..74fb9e6 --- /dev/null +++ b/palette.h @@ -0,0 +1,58 @@ +// Legacy console colors for XP (later systems get the actual palette). + +static const COLORREF legacy_palette[] = +{ +0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xC0C0C0, +0x808080, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF, +}; + +// This is the Windows (10.0.15063) version of the xterm 256-color palette. + +static const COLORREF xterm_palette[] = +{ +// 16 system colors left out. + +// RGB 6x6x6 color cube. +0x000000, 0x5F0000, 0x870000, 0xAF0000, 0xD70000, 0xFF0000, +0x005F00, 0x5F5F00, 0x875F00, 0xAF5F00, 0xD75F00, 0xFF5F00, +0x008700, 0x5F8700, 0x878700, 0xAF8700, 0xD78700, 0xFF8700, +0x00AF00, 0x5FAF00, 0x87AF00, 0xAFAF00, 0xD7AF00, 0xFFAF00, +0x00D700, 0x5FD700, 0x87D700, 0xAFD700, 0xD7D700, 0xFFD700, +0x00FF00, 0x5FFF00, 0x87FF00, 0xAFFF00, 0xD7FF00, 0xFFFF00, +0x00005F, 0x5F005F, 0x87005F, 0xAF005F, 0xD7005F, 0xFF005F, +0x005F5F, 0x5F5F5F, 0x875F5F, 0xAF5F5F, 0xD75F5F, 0xFF5F5F, +0x00875F, 0x5F875F, 0x87875F, 0xAF875F, 0xD7875F, 0xFF875F, +0x00AF5F, 0x5FAF5F, 0x87AF5F, 0xAFAF5F, 0xD7AF5F, 0xFFAF5F, +0x00D75F, 0x5FD75F, 0x87D75F, 0xAFD75F, 0xD7D75F, 0xFFD75F, +0x00FF5F, 0x5FFF5F, 0x87FF5F, 0xAFFF5F, 0xD7FF5F, 0xFFFF5F, +0x000087, 0x5F0087, 0x870087, 0xAF0087, 0xD70087, 0xFF0087, +0x005F87, 0x5F5F87, 0x875F87, 0xAF5F87, 0xD75F87, 0xFF5F87, +0x008787, 0x5F8787, 0x878787, 0xAF8787, 0xD78787, 0xFF8787, +0x00AF87, 0x5FAF87, 0x87AF87, 0xAFAF87, 0xD7AF87, 0xFFAF87, +0x00D787, 0x5FD787, 0x87D787, 0xAFD787, 0xD7D787, 0xFFD787, +0x00FF87, 0x5FFF87, 0x87FF87, 0xAFFF87, 0xD7FF87, 0xFFFF87, +0x0000AF, 0x5F00AF, 0x8700AF, 0xAF00AF, 0xD700AF, 0xFF00AF, +0x005FAF, 0x5F5FAF, 0x875FAF, 0xAF5FAF, 0xD75FAF, 0xFF5FAF, +0x0087AF, 0x5F87AF, 0x8787AF, 0xAF87AF, 0xD787AF, 0xFF87AF, +0x00AFAF, 0x5FAFAF, 0x87AFAF, 0xAFAFAF, 0xD7AFAF, 0xFFAFAF, +0x00D7AF, 0x5FD7AF, 0x87D7AF, 0xAFD7AF, 0xD7D7AF, 0xFFD7AF, +0x00FFAF, 0x5FFFAF, 0x87FFAF, 0xAFFFAF, 0xD7FFAF, 0xFFFFAF, +0x0000D7, 0x5F00D7, 0x8700D7, 0xAF00D7, 0xD700D7, 0xFF00D7, +0x005FD7, 0x5F5FD7, 0x875FD7, 0xAF5FD7, 0xD75FD7, 0xFF5FD7, +0x0087D7, 0x5F87D7, 0x8787D7, 0xAF87D7, 0xD787D7, 0xFF87D7, +0x00AFDF, 0x5FAFDF, 0x87AFDF, 0xAFAFDF, 0xD7AFDF, 0xFFAFDF, // xterm uses +0x00D7DF, 0x5FD7DF, 0x87D7DF, 0xAFD7DF, 0xD7D7DF, 0xFFD7DF, // R = 0xD7 +0x00FFDF, 0x5FFFDF, 0x87FFDF, 0xAFFFDF, 0xD7FFDF, 0xFFFFDF, // here +0x0000FF, 0x5F00FF, 0x8700FF, 0xAF00FF, 0xD700FF, 0xFF00FF, +0x005FFF, 0x5F5FFF, 0x875FFF, 0xAF5FFF, 0xD75FFF, 0xFF5FFF, +0x0087FF, 0x5F87FF, 0x8787FF, 0xAF87FF, 0xD787FF, 0xFF87FF, +0x00AFFF, 0x5FAFFF, 0x87AFFF, 0xAFAFFF, 0xD7AFFF, 0xFFAFFF, +0x00D7FF, 0x5FD7FF, 0x87D7FF, 0xAFD7FF, 0xD7D7FF, 0xFFD7FF, +0x00FFFF, 0x5FFFFF, 0x87FFFF, 0xAFFFFF, 0xD7FFFF, 0xFFFFFF, + +// Grayscale, without black or white. +0x080808, 0x121212, 0x1C1C1C, 0x262626, 0x303030, 0x3A3A3A, +0x444444, 0x4E4E4E, 0x585858, 0x626262, 0x6C6C6C, 0x767676, +0x808080, 0x8A8A8A, 0x949494, 0x9E9E9E, 0xA8A8A8, 0xB2B2B2, +0xBCBCBC, 0xC6C6C6, 0xD0D0D0, 0xDADADA, 0xE4E4E4, 0xEEEEEE, +}; diff --git a/readme.txt b/readme.txt index aeb3a97..3ebdd95 100644 --- a/readme.txt +++ b/readme.txt @@ -334,10 +334,13 @@ Version History Legend: + added, - bug-fixed, * changed. - 1.81-wip - 26 December, 2017: + 1.81-wip - 28 December, 2017: - fix multiple CRs before LF (including preventing an immediate flush); - fix CR, BS and partial RM during CRM; - - fix buffer overflow caused by incorrect critical section. + - fix buffer overflow caused by incorrect critical section; + * support the entire 256-color palette; + * setting color by index or RGB will use the nearest console color; + * setting color by index will leave bold/underline unchanged. 1.80 - 24 December, 2017: - fix unloading; @@ -573,6 +576,8 @@ Acknowledgments Vincent Fatica for pointing out \e[K was not right. Nat Kuhn for pointing out the problem with report cursor position. + Thiadmer Riemersma for the nearest color algorithm. + Contact ======= @@ -595,4 +600,4 @@ Distribution ============================== -Jason Hood, 26 December, 2017. +Jason Hood, 28 December, 2017. diff --git a/sequences.txt b/sequences.txt index 4a2c4d0..243ee3f 100644 --- a/sequences.txt +++ b/sequences.txt @@ -1,8 +1,8 @@ ANSICON - Version 1.80 + Version 1.81 -This is a complete list of the ANSI escape sequences recognised by ANSICON, +This is a complete list of the ANSI/VT escape sequences recognised by ANSICON, roughly ordered by function. The initial escape character is assumed. The display consists of the buffer width and window height; add '+' before the final character to use the buffer height (e.g. "[2J" will erase the window, @@ -32,8 +32,8 @@ the Windows default beep (but only if it's not already playing). 35 foreground magenta 36 foreground cyan 37 foreground white - 38;2;# foreground & bold based on index (0-15) - 38;5;#;#;# ignored (RGB) + 38;2;# foreground based on index (0-255) + 38;5;#;#;# foreground based on RGB 39 default foreground (using current intensity) 40 background black 41 background red @@ -43,8 +43,8 @@ the Windows default beep (but only if it's not already playing). 45 background magenta 46 background cyan 47 background white - 48;2;# background & underline based on index (0-15) - 48;5;#;#;# ignored (RGB) + 48;2;# background based on index (0-255) + 48;5;#;#;# background based on RGB 49 default background (using current intensity) 90 foreground bright black 91 foreground bright red @@ -63,6 +63,11 @@ the Windows default beep (but only if it's not already playing). 106 background bright cyan 107 background bright white + Index is 0-7 for the normal colors and 8-15 for the bright; 16-231 + are a 6x6x6 color cube; and 232-255 are a grayscale ramp (without + black or white). Indices 16-255 and RGB colors will find the nearest + color from the first 16. + [J erase from cursor to the end of display [0J as above [1J erase from the start of diplay to cursor (inclusive) @@ -212,10 +217,11 @@ c hard reset: character 7 (BEL) or escape and backslash ]4;#;spec,spec...;#...ST set or query the palette: - # is the ANSI index (0-7, or 8-15 for bold/underline) + # is the ANSI index (0-7, or 8-15 for bright) spec is: ? send the current value to console input * send the current and all subsequent values + (up to 15 if index is less than that) #RGB set the color (hexadecimal) #RRGGBB set the color (hexadecimal) R,G,B set the color (decimal)