#include #include "../../tty.h" #include #include #include #include #include #define ANSI_BOLD_ON "1" #define ANSI_BOLD_OFF "22" #define ANSI_ITALIC_ON "3" #define ANSI_ITALIC_OFF "23" #define ANSI_UNDERLINE_ON "4" #define ANSI_UNDERLINE_OFF "24" #define ANSI_DEFAULTCOLOUR_FG "39" #define ANSI_DEFAULTCOLOUR_BG "49" #define ANSI_256COLOUR_FG "38;5" #define ANSI_256COLOUR_BG "48;5" #define ANSI_TRUECOLOUR_FG "38;2" #define ANSI_TRUECOLOUR_BG "48;2" enum tty_flags { B_TTY_INIT = 0x01u, B_TTY_INTERACTIVE = 0x02u, }; struct b_tty { HANDLE t_in, t_out; DWORD t_canon_mode_in, t_canon_mode_out; enum s_key_code t_repeat_key; unsigned int t_repeat_count; enum tty_flags t_flags; struct b_tty_vmode t_vmode; struct tty_format_buf t_format_buf; }; static struct b_tty std = {0}; static struct b_tty err = {0}; static WORD ansi_colour16_fg[] = { [B_TTY_COLOUR16_BLACK] = 0, [B_TTY_COLOUR16_RED] = FOREGROUND_RED, [B_TTY_COLOUR16_GREEN] = FOREGROUND_GREEN, [B_TTY_COLOUR16_YELLOW] = FOREGROUND_RED | FOREGROUND_GREEN, [B_TTY_COLOUR16_BLUE] = FOREGROUND_BLUE, [B_TTY_COLOUR16_MAGENTA] = FOREGROUND_RED | FOREGROUND_BLUE, [B_TTY_COLOUR16_CYAN] = FOREGROUND_GREEN | FOREGROUND_BLUE, [B_TTY_COLOUR16_WHITE] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, [B_TTY_COLOUR16_BRIGHT_BLACK] = FOREGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_RED] = FOREGROUND_RED | FOREGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_GREEN] = FOREGROUND_GREEN | FOREGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_YELLOW] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_BLUE] = FOREGROUND_BLUE | FOREGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_MAGENTA] = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_CYAN] = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_WHITE] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, }; static WORD ansi_colour16_bg[] = { [B_TTY_COLOUR16_BLACK] = 0, [B_TTY_COLOUR16_RED] = BACKGROUND_RED, [B_TTY_COLOUR16_GREEN] = BACKGROUND_GREEN, [B_TTY_COLOUR16_YELLOW] = BACKGROUND_RED | BACKGROUND_GREEN, [B_TTY_COLOUR16_BLUE] = BACKGROUND_BLUE, [B_TTY_COLOUR16_MAGENTA] = BACKGROUND_RED | BACKGROUND_BLUE, [B_TTY_COLOUR16_CYAN] = BACKGROUND_GREEN | BACKGROUND_BLUE, [B_TTY_COLOUR16_WHITE] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE, [B_TTY_COLOUR16_BRIGHT_BLACK] = BACKGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_RED] = BACKGROUND_RED | BACKGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_GREEN] = BACKGROUND_GREEN | BACKGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_YELLOW] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_BLUE] = BACKGROUND_BLUE | BACKGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_MAGENTA] = BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_CYAN] = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY, [B_TTY_COLOUR16_BRIGHT_WHITE] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY, }; static void init_tty(struct b_tty *tty, FILE *in, FILE *out) { HANDLE in_handle = INVALID_HANDLE_VALUE; HANDLE out_handle = INVALID_HANDLE_VALUE; if (in) { in_handle = (HANDLE)_get_osfhandle(fileno(in)); } if (out) { out_handle = (HANDLE)_get_osfhandle(fileno(out)); } tty->t_in = in_handle; tty->t_out = out_handle; tty->t_flags |= B_TTY_INIT; CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; DWORD mode = 0; if (in && GetConsoleScreenBufferInfo(in_handle, &csbi)) { GetConsoleMode(in_handle, &mode); tty->t_canon_mode_in = mode; tty->t_flags |= B_TTY_INTERACTIVE; } if (out && GetConsoleScreenBufferInfo(out_handle, &csbi)) { GetConsoleMode(out_handle, &mode); tty->t_canon_mode_out = mode; tty->t_flags |= B_TTY_INTERACTIVE; } } struct b_tty *z__b_tty_get_std(void) { if (!(std.t_flags & B_TTY_INIT)) { init_tty(&std, stdin, stdout); } return &std; } struct b_tty *z__b_tty_get_err(void) { if (!(err.t_flags & B_TTY_INIT)) { init_tty(&err, NULL, stderr); } return &err; } struct tty_format_buf *z__b_tty_get_format_buf(struct b_tty *tty) { return &tty->t_format_buf; } void z__b_tty_putc(struct b_tty* tty, char c) { DWORD x; WriteConsoleA(tty->t_out, &c, 1, &x, NULL); } bool b_tty_is_interactive(const struct b_tty *tty) { return (tty->t_flags & B_TTY_INTERACTIVE) == B_TTY_INTERACTIVE; } static void tty_set_raw(struct b_tty *tty) { DWORD mode = tty->t_canon_mode_in; mode &= ~(ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_WINDOW_INPUT); mode |= (ENABLE_PROCESSED_INPUT); SetConsoleMode(tty->t_in, mode); mode = tty->t_canon_mode_out; //mode &= ~(ENABLE_PROCESSED_OUTPUT); SetConsoleMode(tty->t_out, mode); } static void tty_set_canon(struct b_tty *tty) { SetConsoleMode(tty->t_in, tty->t_canon_mode_in); SetConsoleMode(tty->t_out, tty->t_canon_mode_out); } void b_tty_set_mode(struct b_tty* tty, enum b_tty_mode mode) { switch (mode) { case B_TTY_CANONICAL: tty_set_canon(tty); break; case B_TTY_RAW: tty_set_raw(tty); break; default: break; } } void b_tty_reset_vmode(struct b_tty *tty) { if (tty->t_vmode.v_fg.c_mode == B_TTY_COLOUR_NONE && tty->t_vmode.v_bg.c_mode == B_TTY_COLOUR_NONE && tty->t_vmode.v_attrib == B_TTY_ATTRIB_NORMAL) { return; } WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; SetConsoleTextAttribute(tty->t_out, attrib); memset(&tty->t_vmode, 0x0, sizeof tty->t_vmode); tty->t_vmode.v_fg.c_mode = B_TTY_COLOUR_NONE; tty->t_vmode.v_bg.c_mode = B_TTY_COLOUR_NONE; tty->t_vmode.v_attrib = B_TTY_ATTRIB_NORMAL; } static int compare_colour( const struct b_tty_colour *a, const struct b_tty_colour *b) { if (a->c_mode != b->c_mode) { return -1; } switch (a->c_mode) { case B_TTY_COLOUR_16: if (a->c_16.value != b->c_16.value) { return -1; } break; case B_TTY_COLOUR_256: if (a->c_256.value != b->c_256.value) { return -1; } break; case B_TTY_COLOUR_TRUE: if (a->c_true.r != b->c_true.r) { return -1; } if (a->c_true.g != b->c_true.g) { return -1; } if (a->c_true.b != b->c_true.b) { return -1; } break; default: break; } return 0; } static int compare_vmode(const struct b_tty_vmode *a, const struct b_tty_vmode *b) { if (a->v_attrib != b->v_attrib) { return -1; } if (compare_colour(&a->v_fg, &b->v_fg) != 0) { return -1; } if (compare_colour(&a->v_bg, &b->v_bg) != 0) { return -1; } return 0; } static void set_attrib(WORD *attrp, enum b_tty_attrib old, enum b_tty_attrib new) { if (old == new) { return; } WORD attrib = *attrp; /* Bold ON */ if (!(old & B_TTY_ATTRIB_BOLD) && new & B_TTY_ATTRIB_BOLD) { attrib |= FOREGROUND_INTENSITY; } /* Bold OFF */ if (old & B_TTY_ATTRIB_BOLD && !(new & B_TTY_ATTRIB_BOLD)) { attrib &= ~FOREGROUND_INTENSITY; } /* Underline ON */ if (!(old & B_TTY_ATTRIB_UNDERLINE) && new & B_TTY_ATTRIB_UNDERLINE) { attrib |= COMMON_LVB_UNDERSCORE; } /* Underline OFF */ if (old & B_TTY_ATTRIB_UNDERLINE && !(new & B_TTY_ATTRIB_UNDERLINE)) { attrib &= ~COMMON_LVB_UNDERSCORE; } /* Italic ON */ if (!(old & B_TTY_ATTRIB_ITALIC) && new & B_TTY_ATTRIB_ITALIC) { /* not supported */ } /* Italic OFF */ if (old & B_TTY_ATTRIB_ITALIC && !(new & B_TTY_ATTRIB_ITALIC)) { /* not supported */ } *attrp = attrib; } static void set_fg( WORD *attrp, const struct b_tty_colour *old, const struct b_tty_colour *new) { if (compare_colour(old, new) == 0) { return; } WORD attrib = *attrp; attrib &= ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); switch (new->c_mode) { case B_TTY_COLOUR_16: attrib |= ansi_colour16_fg[new->c_16.value]; break; default: attrib |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break; } *attrp = attrib; } static void set_bg( WORD *attrp, const struct b_tty_colour *old, const struct b_tty_colour *new) { if (compare_colour(old, new) == 0) { return; } WORD attrib = *attrp; attrib &= ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); switch (new->c_mode) { case B_TTY_COLOUR_16: attrib |= ansi_colour16_bg[new->c_16.value]; break; default: attrib |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; break; } } void b_tty_set_vmode(struct b_tty *tty, const struct b_tty_vmode *vmode) { if (compare_vmode(&tty->t_vmode, vmode) == 0) { return; } WORD attrib = 0; CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; GetConsoleScreenBufferInfo(tty->t_out, &csbi); attrib = csbi.wAttributes; set_attrib(&attrib, tty->t_vmode.v_attrib, vmode->v_attrib); set_fg(&attrib, &tty->t_vmode.v_fg, &vmode->v_fg); set_bg(&attrib, &tty->t_vmode.v_bg, &vmode->v_bg); SetConsoleTextAttribute(tty->t_out, attrib); memcpy(&tty->t_vmode, vmode, sizeof *vmode); } enum b_status b_tty_get_dimensions(struct b_tty* tty, unsigned int* w, unsigned int* h) { if (!(tty->t_flags & B_TTY_INTERACTIVE)) { return B_ERR_NOT_SUPPORTED; } CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; GetConsoleScreenBufferInfo(tty->t_out, &csbi); if (w) { *w = csbi.srWindow.Right - csbi.srWindow.Left; } if (h) { *h = csbi.srWindow.Bottom - csbi.srWindow.Top; } return B_SUCCESS; } enum b_status b_tty_get_cursor_position(struct b_tty *tty, unsigned int *x, unsigned int *y) { if (!(tty->t_flags & B_TTY_INTERACTIVE)) { return B_ERR_NOT_SUPPORTED; } CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; GetConsoleScreenBufferInfo(tty->t_out, &csbi); if (x) { *x = csbi.dwCursorPosition.X; } if (y) { *y = csbi.dwCursorPosition.Y; } return B_SUCCESS; } b_keycode b_tty_read_key(struct b_tty *tty) { if (tty->t_repeat_count > 0) { tty->t_repeat_count--; return tty->t_repeat_key; } INPUT_RECORD d; HANDLE in = tty->t_in; BOOL status = TRUE; CONSOLE_READCONSOLE_CONTROL ctrl = {0}; DWORD nr_read = 0; while (1) { status = ReadConsoleInputA(in, &d, 1, &nr_read); if (status == FALSE) { return B_KEY_EOF; } if (d.EventType != KEY_EVENT) { continue; } if (!d.Event.KeyEvent.bKeyDown) { continue; } b_keycode key = 0; switch (d.Event.KeyEvent.wVirtualKeyCode) { case VK_CONTROL: case VK_RCONTROL: case VK_SHIFT: case VK_RSHIFT: case VK_MENU: continue; case VK_UP: key = B_KEY_ARROW_UP; break; case VK_DOWN: key = B_KEY_ARROW_DOWN; break; case VK_LEFT: key = B_KEY_ARROW_LEFT; break; case VK_RIGHT: key = B_KEY_ARROW_RIGHT; break; case VK_BACK: key = B_KEY_BACKSPACE; break; case VK_RETURN: key = B_KEY_RETURN; break; default: if (d.Event.KeyEvent.uChar.UnicodeChar == 0) { continue; } key = d.Event.KeyEvent.uChar.AsciiChar; break; } if (d.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) { key = B_TTY_CTRL_KEY('a' + key - 1); } if (d.Event.KeyEvent.wRepeatCount > 1) { tty->t_repeat_count = d.Event.KeyEvent.wRepeatCount - 1; tty->t_repeat_key = key; } return key; } return B_KEY_EOF; } void b_tty_move_cursor_x( struct b_tty* tty, enum b_tty_position_base base, int pos) { CONSOLE_SCREEN_BUFFER_INFO console = {0}; GetConsoleScreenBufferInfo(tty->t_out, &console); COORD cursor_pos; cursor_pos.Y = console.dwCursorPosition.Y; switch (base) { case B_TTY_POS_CURSOR: cursor_pos.X = console.dwCursorPosition.X + pos; break; case B_TTY_POS_START: cursor_pos.X = pos; break; default: break; } SetConsoleCursorPosition(tty->t_out, cursor_pos); } void b_tty_move_cursor_y(struct b_tty *tty, enum b_tty_position_base base, int pos) { CONSOLE_SCREEN_BUFFER_INFO console = {0}; GetConsoleScreenBufferInfo(tty->t_out, &console); COORD cursor_pos; cursor_pos.X = console.dwCursorPosition.X; switch (base) { case B_TTY_POS_CURSOR: cursor_pos.Y = console.dwCursorPosition.Y + pos; break; case B_TTY_POS_START: cursor_pos.Y = pos; break; default: break; } SetConsoleCursorPosition(tty->t_out, cursor_pos); } void b_tty_clear(struct b_tty *tty, enum b_tty_clear_mode mode) { CONSOLE_SCREEN_BUFFER_INFO console = {0}; GetConsoleScreenBufferInfo(tty->t_out, &console); WCHAR fill = L' '; DWORD length = 0; COORD start; DWORD all_length = 0, line_length = 0; if (mode & B_TTY_CLEAR_ALL) { line_length = console.dwSize.X; all_length = line_length * console.dwSize.Y; } else if (mode & B_TTY_CLEAR_FROM_CURSOR) { line_length = console.dwSize.X - console.dwCursorPosition.X + 1; all_length = line_length + ((console.dwSize.Y - console.dwCursorPosition.Y) * console.dwSize.X); } else if (mode & B_TTY_CLEAR_TO_CURSOR) { line_length = console.dwCursorPosition.X; all_length = line_length + ((console.dwCursorPosition.Y - 1) * console.dwSize.X); } else { abort(); } if (mode & B_TTY_CLEAR_SCREEN) { length = all_length; if ((mode & B_TTY_CLEAR_ALL) || (mode & B_TTY_CLEAR_TO_CURSOR)) { start.X = 0; start.Y = 0; } else if (mode & B_TTY_CLEAR_FROM_CURSOR) { start = console.dwCursorPosition; } } else if (mode & B_TTY_CLEAR_LINE) { length = line_length; if ((mode & B_TTY_CLEAR_ALL) || (mode & B_TTY_CLEAR_TO_CURSOR)) { start.X = 0; start.Y = console.dwCursorPosition.Y; } else if (mode & B_TTY_CLEAR_FROM_CURSOR) { start = console.dwCursorPosition; } } else { abort(); } DWORD nr_written = 0; FillConsoleOutputCharacterW(tty->t_out, fill, length, start, &nr_written); }