From 47b29094c34bdf0aab9f213c8eb2347993e4d505 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 21 Aug 2014 22:11:38 +0200 Subject: win32: emulate some ANSI terminal escape codes We already redirect all terminal output through our own wrappers (for the sake of UTF-8), so we might as well use it to handle ANSI escape codes. This also changes behavior on UNIX: we don't retrieve some escape codes per terminfo anymore, and just hardcode them. Every terminal should understand them. The advantage is that we can pretend to have a real terminal in the normal player code, and Windows atrocities are locked away in glue code. --- common/msg.c | 28 +++++++++-------- osdep/io.c | 10 +++--- osdep/terminal-unix.c | 19 ------------ osdep/terminal-win.c | 84 +++++++++++++++++++++++++++++++++++++++++++++------ osdep/terminal.h | 10 ++---- 5 files changed, 98 insertions(+), 53 deletions(-) diff --git a/common/msg.c b/common/msg.c index d1c2530c2c..7f278b1840 100644 --- a/common/msg.c +++ b/common/msg.c @@ -158,16 +158,10 @@ static void prepare_status_line(struct mp_log_root *root, char *new_status) size_t clear_lines = MPMIN(MPMAX(new_lines, old_lines), root->blank_lines); // clear the status line itself - if (terminal_erase_to_end_of_line[0]) { - fprintf(f, "\r%s", terminal_erase_to_end_of_line); - } else { - // This code is for MS windows (no ANSI control sequences) - get_screen_size(); - fprintf(f, "\r%*s\r", screen_width - 1, ""); - } + fprintf(f, "\r\033[K"); // and clear all previous old lines for (size_t n = 1; n < clear_lines; n++) - fprintf(f, "%s\r%s", terminal_cursor_up, terminal_erase_to_end_of_line); + fprintf(f, "\033[A\r\033[K"); // skip "unused" blank lines, so that status is aligned to term bottom for (size_t n = new_lines; n < clear_lines; n++) fprintf(f, "\n"); @@ -200,10 +194,20 @@ bool mp_msg_has_status_line(struct mpv_global *global) return r; } +static void set_term_color(FILE *stream, int c) +{ + if (c == -1) { + fprintf(stream, "\033[0m"); + } else { + fprintf(stream, "\033[%d;3%dm", c >> 3, c & 7); + } +} + + static void set_msg_color(FILE* stream, int lev) { static const int v_colors[] = {9, 1, 3, -1, -1, 2, 8, 8, 8, -1}; - terminal_set_foreground_color(stream, v_colors[lev]); + set_term_color(stream, v_colors[lev]); } static void pretty_print_module(FILE* stream, const char *prefix, bool use_color, int lev) @@ -214,12 +218,12 @@ static void pretty_print_module(FILE* stream, const char *prefix, bool use_color unsigned int mod = 0; for (int i = 0; i < prefix_len; ++i) mod = mod * 33 + prefix[i]; - terminal_set_foreground_color(stream, (mod + 1) % 15 + 1); + set_term_color(stream, (mod + 1) % 15 + 1); } fprintf(stream, "%10s", prefix); if (use_color) - terminal_set_foreground_color(stream, -1); + set_term_color(stream, -1); fprintf(stream, ": "); if (use_color) set_msg_color(stream, lev); @@ -291,7 +295,7 @@ static void print_msg_on_terminal(struct mp_log *log, int lev, char *text) fprintf(stream, "%s", terminate); if (root->color) - terminal_set_foreground_color(stream, -1); + set_term_color(stream, -1); fflush(stream); } diff --git a/osdep/io.c b/osdep/io.c index 8423a6beff..cc90f145d9 100644 --- a/osdep/io.c +++ b/osdep/io.c @@ -25,6 +25,7 @@ #include "config.h" #include "osdep/io.h" +#include "osdep/terminal.h" // Set the CLOEXEC flag on the given fd. // On error, false is returned (and errno set). @@ -179,13 +180,10 @@ static int mp_vfprintf(FILE *stream, const char *format, va_list args) char *buf = talloc_array(NULL, char, len); if (buf) { - vsnprintf(buf, len, format, args); - wchar_t *out = mp_from_utf8(NULL, buf); - size_t out_len = wcslen(out); - talloc_free(buf); - done = WriteConsoleW(wstream, out, out_len, NULL, NULL); - talloc_free(out); + done = vsnprintf(buf, len, format, args); + mp_write_console_ansi(wstream, buf); } + talloc_free(buf); } else { done = vfprintf(stream, format, args); } diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c index 499ad73394..b17ede6670 100644 --- a/osdep/terminal-unix.c +++ b/osdep/terminal-unix.c @@ -54,8 +54,6 @@ static volatile int tio_orig_set; int screen_width = 80; int screen_height = 24; -char *terminal_erase_to_end_of_line = "\033[K"; -char *terminal_cursor_up = "\033[A"; typedef struct { char *cap; @@ -271,19 +269,11 @@ static int load_termcap(char *termtype){ static char term_buf[128]; char *buf_ptr = &term_buf[0]; - char *tmp; // References for terminfo/termcap codes: // http://linux.die.net/man/5/termcap // http://unixhelp.ed.ac.uk/CGI/man-cgi?terminfo+5 - tmp = tgetstr("ce", &buf_ptr); - if (tmp) - terminal_erase_to_end_of_line = tmp; - tmp = tgetstr("up", &buf_ptr); - if (tmp) - terminal_cursor_up = tmp; - screen_width = tgetnum("co"); screen_height = tgetnum("li"); if (screen_width < 1 || screen_width > 255) @@ -614,15 +604,6 @@ bool terminal_in_background(void) return isatty(2) && tcgetpgrp(2) != getpgrp(); } -void terminal_set_foreground_color(FILE *stream, int c) -{ - if (c == -1) { - fprintf(stream, "\033[0m"); - } else { - fprintf(stream, "\033[%d;3%dm", c >> 3, c & 7); - } -} - int terminal_init(void) { if (isatty(1)) diff --git a/osdep/terminal-win.c b/osdep/terminal-win.c index 19603d87d1..9b985d7378 100644 --- a/osdep/terminal-win.c +++ b/osdep/terminal-win.c @@ -33,12 +33,11 @@ #include "input/keycodes.h" #include "input/input.h" #include "terminal.h" +#include "osdep/io.h" #include "osdep/w32_keyboard.h" int screen_width = 79; int screen_height = 24; -char *terminal_erase_to_end_of_line = ""; -char *terminal_cursor_up = ""; #define hSTDOUT GetStdHandle(STD_OUTPUT_HANDLE) #define hSTDERR GetStdHandle(STD_ERROR_HANDLE) @@ -58,7 +57,7 @@ void get_screen_size(void) { CONSOLE_SCREEN_BUFFER_INFO cinfo; if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cinfo)) { - screen_width = cinfo.dwMaximumWindowSize.X; + screen_width = cinfo.dwMaximumWindowSize.X - 1; screen_height = cinfo.dwMaximumWindowSize.Y; } } @@ -177,13 +176,80 @@ bool terminal_in_background(void) return false; } -void terminal_set_foreground_color(FILE *stream, int c) +static void write_console_text(HANDLE *wstream, char *buf) { - HANDLE *wstream = stream == stderr ? hSTDERR : hSTDOUT; - if (c < 0 || c >= 8) { // reset or invalid - SetConsoleTextAttribute(wstream, stdoutAttrs); - } else { - SetConsoleTextAttribute(wstream, ansi2win32[c] | FOREGROUND_INTENSITY); + wchar_t *out = mp_from_utf8(NULL, buf); + size_t out_len = wcslen(out); + WriteConsoleW(wstream, out, out_len, NULL, NULL); + talloc_free(out); +} + +// Mutates the input argument (buf), because we're evil. +void mp_write_console_ansi(HANDLE *wstream, char *buf) +{ + while (*buf) { + char *next = strchr(buf, '\033'); + if (!next) { + write_console_text(wstream, buf); + break; + } + next[0] = '\0'; // mutate input for fun and profit + write_console_text(wstream, buf); + if (next[1] != '[') { + write_console_text(wstream, "\033"); + buf = next; + continue; + } + next += 2; + // ANSI codes generally follow this syntax: + // "\033[" [ (';' )* ] + // where are integers, and a single char command code. + // Also see: http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes + int params[2] = {-1, -1}; // 'm' might be unlimited; ignore that + int num_params = 0; + while (num_params < 2) { + char *end = next; + long p = strtol(next, &end, 10); + if (end == next) + break; + next = end; + params[num_params++] = p; + if (next[0] != ';' || !next[0]) + break; + next += 1; + } + char code = next[0]; + if (code) + next += 1; + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(wstream, &info); + switch (code) { + case 'K': { // erase to end of line + COORD at = info.dwCursorPosition; + int len = info.dwSize.X - at.X; + FillConsoleOutputCharacterW(wstream, ' ', len, at, &(DWORD){0}); + SetConsoleCursorPosition(wstream, at); + break; + } + case 'A': { // cursor up + info.dwCursorPosition.Y -= 1; + SetConsoleCursorPosition(wstream, info.dwCursorPosition); + break; + } + case 'm': { // "SGR" + for (int n = 0; n < num_params; n++) { + int p = params[n]; + if (p <= 0) { + SetConsoleTextAttribute(wstream, stdoutAttrs); + } else if (p >= 0 && p < 8) { + SetConsoleTextAttribute(wstream, + ansi2win32[p] | FOREGROUND_INTENSITY); + } + } + break; + } + } + buf = next; } } diff --git a/osdep/terminal.h b/osdep/terminal.h index 4495bfb471..491d76975b 100644 --- a/osdep/terminal.h +++ b/osdep/terminal.h @@ -33,9 +33,6 @@ struct input_ctx; extern int screen_width; extern int screen_height; -extern char *terminal_erase_to_end_of_line; -extern char *terminal_cursor_up; - /* Global initialization for terminal output. */ int terminal_init(void); @@ -45,10 +42,6 @@ void terminal_setup_getch(struct input_ctx *ictx); /* Return whether the process has been backgrounded. */ bool terminal_in_background(void); -/* Set ANSI text foreground color. c is [-1, 7], where 0-7 are colors, and - * -1 means reset to default. stream is either stdout or stderr. */ -void terminal_set_foreground_color(FILE *stream, int c); - /* Get screen-size using IOCTL call. */ void get_screen_size(void); @@ -59,4 +52,7 @@ void getch2_disable(void); /* Enable and disable STDIN line-buffering */ void getch2_poll(void); +// Windows only. +void mp_write_console_ansi(void **wstream, char *buf); + #endif /* MPLAYER_GETCH2_H */ -- cgit v1.2.3