#include "../../../line-ed/tty.h" #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 { TTY_INIT = 0x01u, TTY_INTERACTIVE = 0x02u, }; struct s_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 s_tty_vmode t_vmode; unsigned char t_mcount; }; static struct s_tty tty = {0}; static WORD ansi_colour16_fg[] = { [TTY_COLOUR16_BLACK] = 0, [TTY_COLOUR16_RED] = FOREGROUND_RED, [TTY_COLOUR16_GREEN] = FOREGROUND_GREEN, [TTY_COLOUR16_YELLOW] = FOREGROUND_RED | FOREGROUND_GREEN, [TTY_COLOUR16_BLUE] = FOREGROUND_BLUE, [TTY_COLOUR16_MAGENTA] = FOREGROUND_RED | FOREGROUND_BLUE, [TTY_COLOUR16_CYAN] = FOREGROUND_GREEN | FOREGROUND_BLUE, [TTY_COLOUR16_WHITE] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, [TTY_COLOUR16_GREY] = FOREGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_RED] = FOREGROUND_RED | FOREGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_GREEN] = FOREGROUND_GREEN | FOREGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_YELLOW] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_BLUE] = FOREGROUND_BLUE | FOREGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_MAGENTA] = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_CYAN] = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_WHITE] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, }; static WORD ansi_colour16_bg[] = { [TTY_COLOUR16_BLACK] = 0, [TTY_COLOUR16_RED] = BACKGROUND_RED, [TTY_COLOUR16_GREEN] = BACKGROUND_GREEN, [TTY_COLOUR16_YELLOW] = BACKGROUND_RED | BACKGROUND_GREEN, [TTY_COLOUR16_BLUE] = BACKGROUND_BLUE, [TTY_COLOUR16_MAGENTA] = BACKGROUND_RED | BACKGROUND_BLUE, [TTY_COLOUR16_CYAN] = BACKGROUND_GREEN | BACKGROUND_BLUE, [TTY_COLOUR16_WHITE] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE, [TTY_COLOUR16_GREY] = BACKGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_RED] = BACKGROUND_RED | BACKGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_GREEN] = BACKGROUND_GREEN | BACKGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_YELLOW] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_BLUE] = BACKGROUND_BLUE | BACKGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_MAGENTA] = BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_CYAN] = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY, [TTY_COLOUR16_BRIGHT_WHITE] = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY, }; static void init_tty(struct s_tty *tty, FILE *in, FILE *out) { HANDLE in_handle = (HANDLE)_get_osfhandle(fileno(in)); HANDLE out_handle = (HANDLE)_get_osfhandle(fileno(out)); tty->t_in = in_handle; tty->t_out = out_handle; tty->t_flags |= TTY_INIT; DWORD mode = 0; if (GetConsoleMode(in_handle, &mode)) { tty->t_flags |= TTY_INTERACTIVE; tty->t_canon_mode_in = mode; } if (GetConsoleMode(out_handle, &mode)) { tty->t_canon_mode_out = mode; } } struct s_tty *s_get_tty(void) { if (!(tty.t_flags & TTY_INIT)) { init_tty(&tty, stdin, stdout); } return &tty; } bool s_tty_is_interactive(const struct s_tty *tty) { return (tty->t_flags & TTY_INTERACTIVE) == TTY_INTERACTIVE; } void s_tty_set_raw(struct s_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); } void s_tty_set_canon(struct s_tty *tty) { SetConsoleMode(tty->t_in, tty->t_canon_mode_in); SetConsoleMode(tty->t_out, tty->t_canon_mode_out); } void s_tty_reset_vmode(struct s_tty *tty) { if (tty->t_vmode.v_fg.c_mode == TTY_COLOUR_NONE && tty->t_vmode.v_bg.c_mode == TTY_COLOUR_NONE && tty->t_vmode.v_attrib == 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 = TTY_COLOUR_NONE; tty->t_vmode.v_bg.c_mode = TTY_COLOUR_NONE; tty->t_vmode.v_attrib = TTY_ATTRIB_NORMAL; } static int compare_colour( const struct s_tty_colour *a, const struct s_tty_colour *b) { if (a->c_mode != b->c_mode) { return -1; } switch (a->c_mode) { case TTY_COLOUR_16: if (a->c_16.value != b->c_16.value) { return -1; } break; case TTY_COLOUR_256: if (a->c_256.value != b->c_256.value) { return -1; } break; case 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 s_tty_vmode *a, const struct s_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 put_ansi_attrib(struct s_tty *tty, const char *s) { if (tty->t_mcount > 0) { fputs(";", tty->t_out); } fputs(s, tty->t_out); tty->t_mcount++; } static void set_attrib(WORD *attrp, enum s_tty_attrib old, enum s_tty_attrib new) { if (old == new) { return; } WORD attrib = *attrp; /* Bold ON */ if (!(old & TTY_ATTRIB_BOLD) && new & TTY_ATTRIB_BOLD) { attrib |= FOREGROUND_INTENSITY; } /* Bold OFF */ if (old & TTY_ATTRIB_BOLD && !(new & TTY_ATTRIB_BOLD)) { attrib &= ~FOREGROUND_INTENSITY; } /* Underline ON */ if (!(old & TTY_ATTRIB_UNDERLINE) && new & TTY_ATTRIB_UNDERLINE) { attrib |= COMMON_LVB_UNDERSCORE; } /* Underline OFF */ if (old & TTY_ATTRIB_UNDERLINE && !(new & TTY_ATTRIB_UNDERLINE)) { attrib &= ~COMMON_LVB_UNDERSCORE; } /* Italic ON */ if (!(old & TTY_ATTRIB_ITALIC) && new & TTY_ATTRIB_ITALIC) { /* not supported */ } /* Italic OFF */ if (old & TTY_ATTRIB_ITALIC && !(new & TTY_ATTRIB_ITALIC)) { /* not supported */ } *attrp = attrib; } static void set_fg( WORD *attrp, const struct s_tty_colour *old, const struct s_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 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 s_tty_colour *old, const struct s_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 TTY_COLOUR_16: attrib |= ansi_colour16_bg[new->c_16.value]; break; default: attrib |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; break; } } void s_tty_set_vmode(struct s_tty *tty, const struct s_tty_vmode *vmode) { if (compare_vmode(&tty->t_vmode, vmode) == 0) { return; } tty->t_mcount = 0; WORD attrib = 0; 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); } s_keycode s_tty_read_key(struct s_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 S_KEY_EOF; } if (d.EventType != KEY_EVENT) { continue; } if (!d.Event.KeyEvent.bKeyDown) { continue; } s_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 = S_KEY_ARROW_UP; break; case VK_DOWN: key = S_KEY_ARROW_DOWN; break; case VK_LEFT: key = S_KEY_ARROW_LEFT; break; case VK_RIGHT: key = S_KEY_ARROW_RIGHT; break; case VK_BACK: key = S_KEY_BACKSPACE; break; case VK_RETURN: key = S_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 = S_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 S_KEY_EOF; } void s_tty_move_cursor_x( struct s_tty* tty, enum s_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 TTY_POS_CURSOR: cursor_pos.X = console.dwCursorPosition.X + pos; break; case TTY_POS_START: cursor_pos.X = pos; break; default: break; } SetConsoleCursorPosition(tty->t_out, cursor_pos); } void s_tty_move_cursor_y(struct s_tty *tty, enum s_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 TTY_POS_CURSOR: cursor_pos.Y = console.dwCursorPosition.Y + pos; break; case TTY_POS_START: cursor_pos.Y = pos; break; default: break; } SetConsoleCursorPosition(tty->t_out, cursor_pos); } void s_tty_clear(struct s_tty *tty, enum s_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 & TTY_CLEAR_ALL) { line_length = console.dwSize.X; all_length = line_length * console.dwSize.Y; } else if (mode & 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 & TTY_CLEAR_TO_CURSOR) { line_length = console.dwCursorPosition.X; all_length = line_length + ((console.dwCursorPosition.Y - 1) * console.dwSize.X); } else { abort(); } if (mode & TTY_CLEAR_SCREEN) { length = all_length; if ((mode & TTY_CLEAR_ALL) || (mode & TTY_CLEAR_TO_CURSOR)) { start.X = 0; start.Y = 0; } else if (mode & TTY_CLEAR_FROM_CURSOR) { start = console.dwCursorPosition; } } else if (mode & TTY_CLEAR_LINE) { length = line_length; if ((mode & TTY_CLEAR_ALL) || (mode & TTY_CLEAR_TO_CURSOR)) { start.X = 0; start.Y = console.dwCursorPosition.Y; } else if (mode & TTY_CLEAR_FROM_CURSOR) { start = console.dwCursorPosition; } } else { abort(); } DWORD nr_written = 0; FillConsoleOutputCharacterW(tty->t_out, fill, length, start, &nr_written); }