From dafa74b1b9f7dd2dca91642a88dba48b160dd520 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Wed, 20 Nov 2024 22:12:36 +0000 Subject: [PATCH] term: extend tty interface with more features --- term-test/printing.c | 14 +- term/include/blue/term.h | 41 +- term/include/blue/term/print.h | 45 ++ term/include/blue/term/tty.h | 179 ++++++ term/paragraph.c | 30 +- term/print.c | 283 +--------- term/printf.c | 996 +++++++++++++++++++++++++++++++++ term/printf.h | 51 ++ term/sys/darwin/tty.c | 461 +++++++++++++++ term/sys/linux/tty.c | 461 +++++++++++++++ term/sys/windows/tty.c | 570 +++++++++++++++++++ term/tty.c | 330 +++++++++++ term/tty.h | 19 + 13 files changed, 3163 insertions(+), 317 deletions(-) create mode 100644 term/include/blue/term/print.h create mode 100644 term/include/blue/term/tty.h create mode 100644 term/printf.c create mode 100644 term/printf.h create mode 100644 term/sys/darwin/tty.c create mode 100644 term/sys/linux/tty.c create mode 100644 term/sys/windows/tty.c create mode 100644 term/tty.c create mode 100644 term/tty.h diff --git a/term-test/printing.c b/term-test/printing.c index 91724bd..bdc4e37 100644 --- a/term-test/printing.c +++ b/term-test/printing.c @@ -1,5 +1,7 @@ -#include +#include +#include #include +#include #define F_GREEN "[green]" #define F_YELLOW "[yellow]" @@ -52,9 +54,9 @@ static const char *text2 int main(void) { - const char *s = "[magenta,uline]Hello, [bright]world![reset]"; - b_fputs(s, stdout); - fputc('\n', stdout); + const char *s = "[magenta,uline]Hello, [bright_magenta]world![reset]"; + b_puts(s); + b_putc('\n'); b_string *str = b_string_create_from_cstr(s); size_t len = b_string_get_size(str, B_STRLEN_IGNORE_MOD); @@ -65,11 +67,13 @@ int main(void) format.p_right_margin = 5; format.p_flags = B_PARAGRAPH_DOUBLE_LINE_BREAK; - b_print_paragraph(text, stdout, &format); + b_print_paragraph(text, b_stdtty, &format); b_i("An informational message\n\nWith multiple lines"); b_warn("A warning message\nWith multiple lines"); b_err("An error message\nWith multiple lines"); + b_printf("[red]formatting ignored: '%s'[reset]\n[dark_grey]dark text[reset]\n", "[blue]wow![reset]"); + return 0; } diff --git a/term/include/blue/term.h b/term/include/blue/term.h index e4aaba3..595452c 100644 --- a/term/include/blue/term.h +++ b/term/include/blue/term.h @@ -1,43 +1,10 @@ -#ifndef BLUELIB_PRINT_H_ -#define BLUELIB_PRINT_H_ +#ifndef BLUELIB_TERM_H_ +#define BLUELIB_TERM_H_ #include #include -#define b_i(...) b_print(B_PRINT_I, __VA_ARGS__) -#define b_warn(...) b_print(B_PRINT_WARN, __VA_ARGS__) -#define b_err(...) b_print(B_PRINT_ERR, __VA_ARGS__) - -typedef enum b_paragraph_format_flags { - B_PARAGRAPH_DONT_INDENT_FIRST_LINE = 0x01u, - B_PARAGRAPH_HYPHENATE = 0x02u, - B_PARAGRAPH_DOUBLE_LINE_BREAK = 0x04u, - B_PARAGRAPH_MULTI_LINE_BREAK = 0x08u, -} b_paragraph_format_flags; - -typedef struct b_paragraph_format { - b_paragraph_format_flags p_flags; - - unsigned int p_left_margin; - unsigned int p_right_margin; - unsigned int p_line_length; -} b_paragraph_format; - -typedef enum b_print_format { - B_PRINT_NORMAL = 0, - B_PRINT_I, - B_PRINT_WARN, - B_PRINT_ERR, -} b_print_format; - -BLUE_API b_status b_term_get_dimensions(FILE *fp, unsigned int *w, unsigned int *h); - -BLUE_API b_status b_print(b_print_format format, const char *str, ...); -BLUE_API b_status b_print_paragraph( - const char *str, FILE *fp, b_paragraph_format *format); - -BLUE_API int b_fputs(const char *str, FILE *fp); -BLUE_API int b_printf(const char *format, ...); -BLUE_API int b_fprintf(FILE *fp, const char *format, ...); +#include +#include #endif diff --git a/term/include/blue/term/print.h b/term/include/blue/term/print.h new file mode 100644 index 0000000..9627d20 --- /dev/null +++ b/term/include/blue/term/print.h @@ -0,0 +1,45 @@ +#ifndef BLUELIB_TERM_PRINT_H_ +#define BLUELIB_TERM_PRINT_H_ + +#include +#include +#include +#include + +#define b_i(...) b_print(B_PRINT_I, __VA_ARGS__) +#define b_warn(...) b_print(B_PRINT_WARN, __VA_ARGS__) +#define b_err(...) b_print(B_PRINT_ERR, __VA_ARGS__) + +struct b_tty; + +typedef enum b_paragraph_format_flags { + B_PARAGRAPH_DONT_INDENT_FIRST_LINE = 0x01u, + B_PARAGRAPH_HYPHENATE = 0x02u, + B_PARAGRAPH_DOUBLE_LINE_BREAK = 0x04u, + B_PARAGRAPH_MULTI_LINE_BREAK = 0x08u, +} b_paragraph_format_flags; + +typedef struct b_paragraph_format { + b_paragraph_format_flags p_flags; + + unsigned int p_left_margin; + unsigned int p_right_margin; + unsigned int p_line_length; +} b_paragraph_format; + +typedef enum b_print_format { + B_PRINT_NORMAL = 0, + B_PRINT_I, + B_PRINT_WARN, + B_PRINT_ERR, +} b_print_format; + +BLUE_API b_status b_print(b_print_format format, const char *str, ...); +BLUE_API b_status b_print_paragraph( + const char *str, struct b_tty *tty, b_paragraph_format *format); + +BLUE_API int b_putc(char c); +BLUE_API int b_puts(const char* s); +BLUE_API int b_printf(const char* format, ...); + +#endif \ No newline at end of file diff --git a/term/include/blue/term/tty.h b/term/include/blue/term/tty.h new file mode 100644 index 0000000..aa4b508 --- /dev/null +++ b/term/include/blue/term/tty.h @@ -0,0 +1,179 @@ +#ifndef BLUELIB_TERM_TTY_H_ +#define BLUELIB_TERM_TTY_H_ + +#include +#include +#include +#include + +#define b_stdtty (z__b_tty_get_std()) +#define b_stderr (z__b_tty_get_err()) + +#define B_TTY_CTRL_KEY(c) ((c) | B_MOD_CTRL) + +#define B_MAKE_VMODE(fg, bg, a) \ + { \ + .v_fg = fg, .v_bg = bg, .v_attrib = (a) \ + } + +#define B_MAKE_COLOUR_DEFAULT(v) \ + { \ + .c_mode = TTY_COLOUR_NONE, \ + } + +#define B_MAKE_COLOUR_16(v) \ + { \ + .c_mode = TTY_COLOUR_16, .c_16 = {.value = (v) } \ + } + +#define B_MAKE_COLOUR_256(v) \ + { \ + .c_mode = TTY_COLOUR_256, .c_256 = {.value = (v) } \ + } + +#define B_MAKE_COLOUR_TRUE(cr, cg, cb) \ + { \ + .c_mode = TTY_COLOUR_TRUE, .c_true \ + = {.r = (cr), \ + .g = (cg), \ + .b = (cb) } \ + } + +typedef struct b_tty b_tty; +typedef uint32_t b_keycode; + +/* codepoints U+F0000 to U+FFFFD are reserved areas in Unicode, free for + * application use. store special keycodes here */ +enum { + B_KEY_ARROW_LEFT = 0xF0000, + B_KEY_ARROW_RIGHT, + B_KEY_ARROW_UP, + B_KEY_ARROW_DOWN, + B_KEY_BACKSPACE, + B_KEY_RETURN, + + B_MOD_CTRL = 0x10000000, + + B_KEY_EOF = 0xFFFFFFFF, +}; + +typedef enum b_tty_colour16 { + B_TTY_COLOUR16_BLACK = 0, + B_TTY_COLOUR16_RED, + B_TTY_COLOUR16_GREEN, + B_TTY_COLOUR16_YELLOW, + B_TTY_COLOUR16_BLUE, + B_TTY_COLOUR16_MAGENTA, + B_TTY_COLOUR16_CYAN, + B_TTY_COLOUR16_WHITE, + B_TTY_COLOUR16_BRIGHT_BLACK, + B_TTY_COLOUR16_BRIGHT_RED, + B_TTY_COLOUR16_BRIGHT_GREEN, + B_TTY_COLOUR16_BRIGHT_YELLOW, + B_TTY_COLOUR16_BRIGHT_BLUE, + B_TTY_COLOUR16_BRIGHT_MAGENTA, + B_TTY_COLOUR16_BRIGHT_CYAN, + B_TTY_COLOUR16_BRIGHT_WHITE, +} b_tty_colour16; + +typedef enum b_tty_colour_mode { + B_TTY_COLOUR_NONE = 0, + B_TTY_COLOUR_16, + B_TTY_COLOUR_256, + B_TTY_COLOUR_TRUE, +} b_tty_colour_mode; + +typedef enum b_tty_position_base { + B_TTY_POS_START, + B_TTY_POS_CURSOR, +} b_tty_position_base; + +typedef enum b_tty_attrib { + B_TTY_ATTRIB_NORMAL = 0x00u, + B_TTY_ATTRIB_BOLD = 0x01u, + B_TTY_ATTRIB_UNDERLINE = 0x02u, + B_TTY_ATTRIB_ITALIC = 0x04u, + B_TTY_ATTRIB_INVERT = 0x08u, +} b_tty_attrib; + +typedef enum b_tty_clear_mode { + B_TTY_CLEAR_LINE = 0x01u, + B_TTY_CLEAR_SCREEN = 0x02, + + B_TTY_CLEAR_TO_CURSOR = 0x0100u, + B_TTY_CLEAR_FROM_CURSOR = 0x0200u, + B_TTY_CLEAR_ALL = 0x0400u, +} b_tty_clear_mode; + +typedef enum b_tty_mode { + B_TTY_CANONICAL = 0x00u, + B_TTY_RAW = 0x01u, +} b_tty_mode; + +typedef enum b_tty_print_flags { + B_TTY_DISABLE_FORMATTING = 0x01u, + B_TTY_DISABLE_INTERPOLATED_FORMATTING = 0x02u, +} b_tty_print_flags; + +typedef struct b_tty_colour { + enum b_tty_colour_mode c_mode; + + union { + struct { + uint8_t value; + } c_16; + + struct { + uint8_t value; + } c_256; + + struct { + uint8_t r; + uint8_t g; + uint8_t b; + } c_true; + }; +} b_tty_colour; + +typedef struct b_tty_vmode { + enum b_tty_attrib v_attrib; + struct b_tty_colour v_fg; + struct b_tty_colour v_bg; +} b_tty_vmode; + +BLUE_API b_tty *z__b_tty_get_std(void); +BLUE_API b_tty *z__b_tty_get_err(void); + +static inline unsigned int b_keycode_get_key(b_keycode v) +{ + return v & 0x0FFFFFFF; +} + +BLUE_API bool b_tty_is_interactive(const struct b_tty *tty); +BLUE_API void b_tty_set_mode(struct b_tty *tty, enum b_tty_mode mode); +BLUE_API void b_tty_set_vmode(struct b_tty *tty, const struct b_tty_vmode *vmode); +BLUE_API void b_tty_reset_vmode(struct b_tty *tty); + +BLUE_API enum b_status b_tty_get_dimensions( + struct b_tty *tty, unsigned int *w, unsigned int *h); +BLUE_API enum b_status b_tty_get_cursor_position( + struct b_tty *tty, unsigned int *x, unsigned int *y); + +BLUE_API b_keycode b_tty_read_key(struct b_tty *tty); + +BLUE_API void b_tty_move_cursor_x( + struct b_tty *tty, enum b_tty_position_base base, int pos); +BLUE_API void b_tty_move_cursor_y( + struct b_tty *tty, enum b_tty_position_base base, int pos); +BLUE_API void b_tty_clear(struct b_tty *tty, enum b_tty_clear_mode mode); + +BLUE_API int b_tty_putc( + struct b_tty *tty, enum b_tty_print_flags flags, char c); +BLUE_API int b_tty_puts( + struct b_tty *tty, enum b_tty_print_flags flags, const char *s); +BLUE_API int b_tty_printf( + struct b_tty *tty, enum b_tty_print_flags flags, const char *s, ...); +BLUE_API int b_tty_vprintf( + struct b_tty *tty, enum b_tty_print_flags flags, const char *s, va_list args); + +#endif \ No newline at end of file diff --git a/term/paragraph.c b/term/paragraph.c index 99367ad..c0c7cc7 100644 --- a/term/paragraph.c +++ b/term/paragraph.c @@ -1,7 +1,7 @@ #include "print.h" #include -#include +#include #include #include #include @@ -10,17 +10,17 @@ #define DEFAULT_PARAGRAPH_WIDTH 160 static void indent( - struct b_paragraph_format *format, FILE *fp, unsigned int margin, bool tty) + struct b_paragraph_format *format, struct b_tty *tty, unsigned int margin) { unsigned int x = 0; while (x < margin) { - fputs(" ", fp); + b_tty_puts(tty, 0, " "); x++; } } static unsigned int extract_line( - const char **sp, unsigned int line_length, b_string *out, + const char **sp, unsigned int line_length, struct b_string *out, struct b_paragraph_format *format) { const char *start = *sp; @@ -96,10 +96,10 @@ static unsigned int extract_line( } static b_status print_paragraph_tty( - const char *str, FILE *fp, struct b_paragraph_format *format) + const char *str, struct b_tty *tty, struct b_paragraph_format *format) { unsigned int w = 0, h = 0; - z__b_stream_dimensions(fp, &w, &h); + b_tty_get_dimensions(tty, &w, &h); if (!w) { w = DEFAULT_PARAGRAPH_WIDTH; @@ -119,7 +119,7 @@ static b_status print_paragraph_tty( } if (!(format->p_flags & B_PARAGRAPH_DONT_INDENT_FIRST_LINE)) { - indent(format, fp, left_margin, true); + indent(format, tty, left_margin); } b_string *line = b_string_create(); @@ -128,7 +128,7 @@ static b_status print_paragraph_tty( while (str) { if (*str == '\n') { if (format->p_flags & B_PARAGRAPH_DOUBLE_LINE_BREAK) { - fputc('\n', fp); + b_tty_putc(tty, 0, '\n'); } str++; @@ -136,7 +136,7 @@ static b_status print_paragraph_tty( while (*str == '\n') { if (format->p_flags & B_PARAGRAPH_MULTI_LINE_BREAK) { - fputc('\n', fp); + b_tty_putc(tty, 0, '\n'); } str++; @@ -151,11 +151,11 @@ static b_status print_paragraph_tty( } if (need_indent) { - indent(format, fp, left_margin, false); + indent(format, tty, left_margin); } - b_fputs(b_string_ptr(line), fp); - fputc('\n', fp); + b_tty_puts(tty, 0, b_string_ptr(line)); + b_tty_putc(tty, 0, '\n'); need_indent = true; } @@ -171,11 +171,7 @@ static b_status print_paragraph_file( } b_status b_print_paragraph( - const char *str, FILE *fp, struct b_paragraph_format *format) + const char *str, struct b_tty *fp, struct b_paragraph_format *format) { - if (z__b_stream_is_tty(fp)) { - return print_paragraph_tty(str, fp, format); - } - return print_paragraph_tty(str, fp, format); } diff --git a/term/print.c b/term/print.c index c3504de..d9094f6 100644 --- a/term/print.c +++ b/term/print.c @@ -1,7 +1,7 @@ #include "print.h" #include -#include +#include #include #include #include @@ -25,218 +25,7 @@ #define F_RESET "[reset]" -typedef b_status (*print_function)(FILE *fp, const char *s); - -#define MOD_HASH_BLACK 0x4b5dd0abbc6fc1e4 -#define MOD_HASH_RED 0x89e9be1960f4c21c -#define MOD_HASH_GREEN 0x0f40f029637fecbc -#define MOD_HASH_YELLOW 0x8346a574925e75a9 -#define MOD_HASH_BLUE 0xc5ccd29bc2dda64d -#define MOD_HASH_MAGENTA 0x6c90e772edbc8708 -#define MOD_HASH_CYAN 0x70ae2e90c1bce27a -#define MOD_HASH_WHITE 0xced973885856e206 - -#define MOD_HASH_BG_BLACK 0xd87a8f2d9d394432 -#define MOD_HASH_BG_RED 0x145b1e4366c7d7aa -#define MOD_HASH_BG_GREEN 0xa00b8541d3b1e55a -#define MOD_HASH_BG_YELLOW 0x98b030fd86e3b3cf -#define MOD_HASH_BG_BLUE 0xa15529109506b5df -#define MOD_HASH_BG_MAGENTA 0x86dbda99bcc86222 -#define MOD_HASH_BG_CYAN 0xf16a3104cf61a098 -#define MOD_HASH_BG_WHITE 0x3408c46ab5836674 - -#define MOD_HASH_BOLD 0xcd32ea9bc6b26ff6 -#define MOD_HASH_BRIGHT 0xb5cca637f5a2b385 -#define MOD_HASH_ULINE 0x141fe741e9f8c22a -#define MOD_HASH_ITALIC 0x69d5e5f057d8992d -#define MOD_HASH_INVERT 0xab4ab85ddd6232e1 -#define MOD_HASH_RESET 0x0f136ff2c086b760 - -#define COMPARE_MOD_NAME(ss, sd, hs, hd) ((hs) == (hd) && !strcmp(ss, sd)) - -b_status b_term_get_dimensions(FILE *fp, unsigned int *w, unsigned int *h) -{ - return z__b_stream_dimensions(fp, w, h) == 0 ? B_SUCCESS - : B_ERR_NOT_SUPPORTED; -} - -static void apply_modifier(FILE *fp, const char *modifier, unsigned int *mod_flagp) -{ - uint64_t mod_hash = b_hash_string(modifier); - unsigned int mod_flags = *mod_flagp; - - if (COMPARE_MOD_NAME(modifier, "black", mod_hash, MOD_HASH_BLACK)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BLACK; - } - - if (COMPARE_MOD_NAME(modifier, "red", mod_hash, MOD_HASH_RED)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_RED; - } - - if (COMPARE_MOD_NAME(modifier, "green", mod_hash, MOD_HASH_GREEN)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_GREEN; - } - - if (COMPARE_MOD_NAME(modifier, "yellow", mod_hash, MOD_HASH_YELLOW)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_RED | Z__B_STREAM_MOD_GREEN; - } - - if (COMPARE_MOD_NAME(modifier, "blue", mod_hash, MOD_HASH_BLUE)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "magenta", mod_hash, MOD_HASH_MAGENTA)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_RED | Z__B_STREAM_MOD_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "cyan", mod_hash, MOD_HASH_CYAN)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_GREEN | Z__B_STREAM_MOD_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "white", mod_hash, MOD_HASH_WHITE)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_FG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_RED | Z__B_STREAM_MOD_GREEN - | Z__B_STREAM_MOD_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "bg_black", mod_hash, MOD_HASH_BG_BLACK)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_BLACK; - } - - if (COMPARE_MOD_NAME(modifier, "bg_red", mod_hash, MOD_HASH_BG_RED)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_RED; - } - - if (COMPARE_MOD_NAME(modifier, "bg_green", mod_hash, MOD_HASH_BG_GREEN)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_GREEN; - } - - if (COMPARE_MOD_NAME(modifier, "bg_yellow", mod_hash, MOD_HASH_BG_YELLOW)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_RED | Z__B_STREAM_MOD_BG_GREEN; - } - - if (COMPARE_MOD_NAME(modifier, "bg_blue", mod_hash, MOD_HASH_BG_BLUE)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "bg_magenta", mod_hash, MOD_HASH_BG_MAGENTA)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_RED | Z__B_STREAM_MOD_BG_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "bg_cyan", mod_hash, MOD_HASH_BG_CYAN)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_GREEN | Z__B_STREAM_MOD_BG_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "bg_white", mod_hash, MOD_HASH_BG_WHITE)) { - mod_flags = Z__B_STREAM_MOD_CLEAR_BG_COLOUR(mod_flags); - mod_flags |= Z__B_STREAM_MOD_BG_RED | Z__B_STREAM_MOD_BG_GREEN - | Z__B_STREAM_MOD_BG_BLUE; - } - - if (COMPARE_MOD_NAME(modifier, "bold", mod_hash, MOD_HASH_BOLD)) { - mod_flags |= Z__B_STREAM_MOD_BOLD; - } - - if (COMPARE_MOD_NAME(modifier, "bright", mod_hash, MOD_HASH_BRIGHT)) { - mod_flags |= Z__B_STREAM_MOD_BRIGHT; - } - - if (COMPARE_MOD_NAME(modifier, "uline", mod_hash, MOD_HASH_ULINE)) { - mod_flags |= Z__B_STREAM_MOD_ULINE; - } - - if (COMPARE_MOD_NAME(modifier, "italic", mod_hash, MOD_HASH_ITALIC)) { - mod_flags |= Z__B_STREAM_MOD_ITALIC; - } - - if (COMPARE_MOD_NAME(modifier, "invert", mod_hash, MOD_HASH_INVERT)) { - mod_flags |= Z__B_STREAM_MOD_INVERT; - } - - if (COMPARE_MOD_NAME(modifier, "reset", mod_hash, MOD_HASH_RESET)) { - mod_flags = Z__B_STREAM_MOD_RESET; - } - - *mod_flagp = mod_flags; -} - -static int print(FILE *fp, const char *str) -{ - int len = 0; - char ctrl_buf[128]; - unsigned int ctrl_len = 0; - - for (size_t i = 0; str[i];) { - char c = str[i++]; - - if (c != '[') { - fputc(c, fp); - len++; - continue; - } - - c = str[i++]; - - if (c == '[') { - fputc(c, fp); - len++; - continue; - } - - ctrl_buf[ctrl_len++] = c; - unsigned int mod_flags = 0; - - while (true) { - c = str[i++]; - - if (isspace(c)) { - continue; - } - - if (c == '\0') { - break; - } - - if (c == ']') { - ctrl_buf[ctrl_len] = '\0'; - apply_modifier(fp, ctrl_buf, &mod_flags); - ctrl_len = 0; - ctrl_buf[0] = '\0'; - break; - } - - if (c == ',') { - ctrl_buf[ctrl_len] = '\0'; - apply_modifier(fp, ctrl_buf, &mod_flags); - ctrl_len = 0; - ctrl_buf[0] = '\0'; - continue; - } - - if (ctrl_len < sizeof ctrl_buf - 1) { - ctrl_buf[ctrl_len++] = c; - } - } - - z__b_stream_set_modifier(fp, mod_flags); - } - - return len; -} +typedef b_status (*print_function)(struct b_tty *tty, const char *s); static int print_no_ansi(FILE *fp, const char *str) { @@ -257,52 +46,52 @@ static int print_no_ansi(FILE *fp, const char *str) return out; } -static b_status print_normal(FILE *fp, const char *s) +static b_status print_normal(struct b_tty *tty, const char *s) { return B_SUCCESS; } -static b_status print_i(FILE *fp, const char *s) +static b_status print_i(struct b_tty *tty, const char *s) { - b_paragraph_format format = {0}; + struct b_paragraph_format format = {0}; format.p_left_margin = 5; format.p_right_margin = 5; format.p_flags = B_PARAGRAPH_DONT_INDENT_FIRST_LINE | B_PARAGRAPH_MULTI_LINE_BREAK; // printf(F_CYN_BG F_BLACK " i " F_RESET " "); - b_fprintf(fp, F_CYN_BOLD " i:" F_RESET " "); - b_print_paragraph(s, fp, &format); + b_tty_puts(tty, 0, F_CYN_BOLD " i:" F_RESET " "); + b_print_paragraph(s, tty, &format); return B_SUCCESS; } -static b_status print_warn(FILE *fp, const char *s) +static b_status print_warn(struct b_tty *tty, const char *s) { - b_paragraph_format format = {0}; + struct b_paragraph_format format = {0}; format.p_left_margin = 5; format.p_right_margin = 5; format.p_flags = B_PARAGRAPH_DONT_INDENT_FIRST_LINE | B_PARAGRAPH_MULTI_LINE_BREAK; // printf(F_YEL_BG F_BLACK " Warn " F_RESET " "); - b_fprintf(fp, F_YEL_BOLD " !!" F_RESET " "); - b_print_paragraph(s, fp, &format); + b_tty_puts(tty, 0, F_YEL_BOLD " !!" F_RESET " "); + b_print_paragraph(s, tty, &format); return B_SUCCESS; } -static b_status print_err(FILE *fp, const char *s) +static b_status print_err(struct b_tty *tty, const char *s) { - b_paragraph_format format = {0}; + struct b_paragraph_format format = {0}; format.p_left_margin = 5; format.p_right_margin = 5; format.p_flags = B_PARAGRAPH_DONT_INDENT_FIRST_LINE | B_PARAGRAPH_MULTI_LINE_BREAK; // printf(F_RED_BG F_BLACK " Err " F_RESET " "); - b_fprintf(fp, F_RED_BOLD "Err:" F_RESET " "); - b_print_paragraph(s, fp, &format); + b_tty_puts(tty, 0, F_RED_BOLD "Err:" F_RESET " "); + b_print_paragraph(s, tty, &format); return B_SUCCESS; } @@ -334,47 +123,25 @@ b_status b_print(b_print_format format, const char *str, ...) return B_ERR_INVALID_ARGUMENT; } - return printer(stdout, buf); + return printer(b_stdtty, buf); } -static int b_vfprintf(FILE *fp, const char *format, va_list arg) +int b_putc(char c) { - char str[1024]; - - vsnprintf(str, sizeof str, format, arg); - - if (z__b_stream_is_tty(fp)) { - return print(fp, str); - } - - return print_no_ansi(fp, str); + return b_tty_putc(b_stdtty, 0, c); } -int b_fputs(const char *str, FILE *fp) +int b_puts(const char* s) { - if (z__b_stream_is_tty(fp)) { - return print(fp, str); - } - - return print_no_ansi(fp, str); + return b_tty_puts(b_stdtty, 0, s); } -int b_printf(const char *format, ...) +int b_printf(const char* format, ...) { va_list arg; va_start(arg, format); - int len = b_vfprintf(stdout, format, arg); + int x = b_tty_vprintf( + b_stdtty, B_TTY_DISABLE_INTERPOLATED_FORMATTING, format, arg); va_end(arg); - - return len; -} - -int b_fprintf(FILE *fp, const char *format, ...) -{ - va_list arg; - va_start(arg, format); - int len = b_vfprintf(fp, format, arg); - va_end(arg); - - return len; -} + return x; +} \ No newline at end of file diff --git a/term/printf.c b/term/printf.c new file mode 100644 index 0000000..8baa9d1 --- /dev/null +++ b/term/printf.c @@ -0,0 +1,996 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include "printf.h" + +#include +#include +#include + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + +struct out_tty_args { + struct b_tty *tty; + enum b_tty_print_flags flags; + bool format_ch; +}; + +// output function type +typedef void (*out_fct_type)( + char character, struct out_tty_args *args); + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void *arg); + void *arg; +} out_fct_wrap_type; + +// internal buffer output +static inline void _out_buffer( + char character, void *buffer, size_t idx, size_t maxlen) +{ + if (idx < maxlen) { + ((char *)buffer)[idx] = character; + } +} + +// internal null output +static inline void _out_null(char character, void *buffer, size_t idx, size_t maxlen) +{ + (void)character; + (void)buffer; + (void)idx; + (void)maxlen; +} + +// internal _putchar wrapper +static inline void _out_char(char character, void *buffer, size_t idx, size_t maxlen) +{ + (void)buffer; + (void)idx; + (void)maxlen; + if (character) { + _putchar(character); + } +} + +// internal output function wrapper +static inline void _out_fct(char character, void *buffer, size_t idx, size_t maxlen) +{ + (void)idx; + (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type *)buffer) + ->fct(character, ((out_fct_wrap_type *)buffer)->arg); + } +} + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char *str, size_t maxsize) +{ + const char *s; + for (s = str; *s && maxsize--; ++s) + ; + return (unsigned int)(s - str); +} + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char **str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev( + out_fct_type out, struct out_tty_args *args, + const char *buf, size_t len, unsigned int width, unsigned int flags) +{ + size_t idx = 0; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', args); + } + } + + // reverse string + while (len) { + out(buf[--len], args); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx < width) { + out(' ', args); + idx++; + } + } + + return idx; +} + +// internal itoa format +static size_t _ntoa_format( + out_fct_type out, struct out_tty_args *args, char *buf, + size_t len, bool negative, unsigned int base, unsigned int prec, + unsigned int width, unsigned int flags) +{ + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) + && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) + && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len + && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) + && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } else if ( + (base == 16U) && (flags & FLAGS_UPPERCASE) + && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, args, buf, len, width, flags); +} + +// internal itoa for 'long' type +static size_t _ntoa_long( + out_fct_type out, struct out_tty_args *args, + unsigned long value, bool negative, unsigned long base, + unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 + ? '0' + digit + : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format( + out, args, buf, len, negative, + (unsigned int)base, prec, width, flags); +} + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long( + out_fct_type out, struct out_tty_args *args, + unsigned long long value, bool negative, unsigned long long base, + unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 + ? '0' + digit + : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format( + out, args, buf, len, negative, + (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa( + out_fct_type out, struct out_tty_args *args, double value, + unsigned int prec, unsigned int width, unsigned int flags); +#endif + +// internal ftoa for fixed decimal floating point +static size_t _ftoa( + out_fct_type out, struct out_tty_args *args, double value, + unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] + = {1, 10, 100, 1000, 10000, + 100000, 1000000, 10000000, 100000000, 1000000000}; + + // test for special values + if (value != value) + return _out_rev(out, args, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, args, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev( + out, args, + (flags & FLAGS_PLUS) ? "fni+" : "fni", + (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- + // which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, args, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if (diff < 0.5) { + } else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, args, buf, len, width, flags); +} + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa( + out_fct_type out, struct out_tty_args *args, double value, + unsigned int prec, unsigned int width, unsigned int flags) +{ + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, args, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) + | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + size_t idx = _ftoa(out, args, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', args); + // output the exponent value + idx = _ntoa_long( + out, args, + (expval < 0) ? -expval : expval, expval < 0, 10, 0, + minwidth - 1, FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx < width) { + out(' ', args); + idx++; + } + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + +#define set_format_ch(args) ((args)->format_ch = true) +#define unset_format_ch(args) ((args)->format_ch = false) + +// internal vsnprintf +static int _vsnprintf( + out_fct_type out, struct out_tty_args *args, const char *format, + va_list va) +{ + unsigned int flags, width, precision, n; + size_t idx = 0U; + + while (*format) { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + set_format_ch(args); + out(*format, args); + unset_format_ch(args); + format++; + continue; + } else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': + flags |= FLAGS_ZEROPAD; + format++; + n = 1U; + break; + case '-': + flags |= FLAGS_LEFT; + format++; + n = 1U; + break; + case '+': + flags |= FLAGS_PLUS; + format++; + n = 1U; + break; + case ' ': + flags |= FLAGS_SPACE; + format++; + n = 1U; + break; + case '#': + flags |= FLAGS_HASH; + format++; + n = 1U; + break; + default: + n = 0U; + break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l': + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h': + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't': + flags + |= (sizeof(ptrdiff_t) == sizeof(long) + ? FLAGS_LONG + : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j': + flags + |= (sizeof(intmax_t) == sizeof(long) + ? FLAGS_LONG + : FLAGS_LONG_LONG); + format++; + break; + case 'z': + flags + |= (sizeof(size_t) == sizeof(long) + ? FLAGS_LONG + : FLAGS_LONG_LONG); + format++; + break; + default: + break; + } + + // evaluate specifier + switch (*format) { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } else if (*format == 'o') { + base = 8U; + } else if (*format == 'b') { + base = 2U; + } else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value + = va_arg(va, long long); + idx = _ntoa_long_long( + out, args, + (unsigned long long)(value > 0 ? value + : 0 - value), + value < 0, base, precision, + width, flags); +#endif + } else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long( + out, args, + (unsigned long)(value > 0 ? value + : 0 - value), + value < 0, base, precision, + width, flags); + } else { + const int value + = (flags & FLAGS_CHAR) + ? (char)va_arg(va, int) + : (flags & FLAGS_SHORT) + ? (short int)va_arg(va, int) + : va_arg(va, int); + idx = _ntoa_long( + out, args, + (unsigned int)(value > 0 ? value + : 0 - value), + value < 0, base, precision, + width, flags); + } + } else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long( + out, args, + va_arg(va, unsigned long long), + false, base, precision, width, + flags); +#endif + } else if (flags & FLAGS_LONG) { + idx = _ntoa_long( + out, args, + va_arg(va, unsigned long), false, + base, precision, width, flags); + } else { + const unsigned int value + = (flags & FLAGS_CHAR) + ? (unsigned char)va_arg( + va, unsigned int) + : (flags & FLAGS_SHORT) + ? (unsigned short int)va_arg( + va, unsigned int) + : va_arg(va, unsigned int); + idx = _ntoa_long( + out, args, value, + false, base, precision, width, + flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f': + case 'F': + if (*format == 'F') + flags |= FLAGS_UPPERCASE; + idx = _ftoa( + out, args, va_arg(va, double), + precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g') || (*format == 'G')) + flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E') || (*format == 'G')) + flags |= FLAGS_UPPERCASE; + idx = _etoa( + out, args, va_arg(va, double), + precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c': { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', args); + } + } + // char output + out((char)va_arg(va, int), args); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', args); + } + } + format++; + break; + } + + case 's': { + const char *p = va_arg(va, char *); + unsigned int l = _strnlen_s( + p, precision ? precision : (size_t)-1); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', args); + } + } + // string output + while ((*p != 0) + && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), args); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', args); + } + } + format++; + break; + } + + case 'p': { + width = sizeof(void *) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long( + out, args, + (uintptr_t)va_arg(va, void *), false, + 16U, precision, width, flags); + } else { +#endif + idx = _ntoa_long( + out, args, + (unsigned long)((uintptr_t)va_arg( + va, void *)), + false, 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%': + set_format_ch(args); + out('%', args); + unset_format_ch(args); + format++; + break; + + default: + set_format_ch(args); + out(*format, args); + unset_format_ch(args); + format++; + break; + } + } + + // termination + out((char)0, args); + + // return written chars without terminating \0 + return (int)idx; +} + +/////////////////////////////////////////////////////////////////////////////// + +static void out_tty(char c, struct out_tty_args *args) +{ + if (c == 0) { + return; + } + + enum b_tty_print_flags flags = args->flags; + + if (!args->format_ch && (flags & B_TTY_DISABLE_INTERPOLATED_FORMATTING)) { + flags |= B_TTY_DISABLE_FORMATTING; + } + + b_tty_putc(args->tty, flags, c); +} + +int b_tty_vprintf(struct b_tty *tty, enum b_tty_print_flags flags, const char *format, va_list args) +{ + struct out_tty_args tty_args = { + .flags = flags, + .tty = tty, + }; + + const int ret = _vsnprintf( + out_tty, &tty_args, format, args); + return ret; +} diff --git a/term/printf.h b/term/printf.h new file mode 100644 index 0000000..1f49ec5 --- /dev/null +++ b/term/printf.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + + +#endif // _PRINTF_H_ diff --git a/term/sys/darwin/tty.c b/term/sys/darwin/tty.c new file mode 100644 index 0000000..e2ca52f --- /dev/null +++ b/term/sys/darwin/tty.c @@ -0,0 +1,461 @@ +#include "../../../line-ed/tty.h" + +#include +#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 { + TTY_INIT = 0x01u, + TTY_INTERACTIVE = 0x02u, +}; + +struct s_tty { + FILE *t_in, *t_out; + enum tty_flags t_flags; + struct termios t_ios_raw, t_ios_default; + struct s_tty_vmode t_vmode; + unsigned char t_mcount; +}; + +static struct s_tty tty = {}; + +static const char *ansi_colour16_fg[] = { + [TTY_COLOUR16_BLACK] = "30", + [TTY_COLOUR16_RED] = "31", + [TTY_COLOUR16_GREEN] = "32", + [TTY_COLOUR16_YELLOW] = "33", + [TTY_COLOUR16_BLUE] = "34", + [TTY_COLOUR16_MAGENTA] = "35", + [TTY_COLOUR16_CYAN] = "36", + [TTY_COLOUR16_WHITE] = "37", + [TTY_COLOUR16_GREY] = "90", + [TTY_COLOUR16_BRIGHT_RED] = "91", + [TTY_COLOUR16_BRIGHT_GREEN] = "92", + [TTY_COLOUR16_BRIGHT_YELLOW] = "93", + [TTY_COLOUR16_BRIGHT_BLUE] = "94", + [TTY_COLOUR16_BRIGHT_MAGENTA] = "95", + [TTY_COLOUR16_BRIGHT_CYAN] = "96", + [TTY_COLOUR16_BRIGHT_WHITE] = "97", +}; + +static const char *ansi_colour16_bg[] = { + [TTY_COLOUR16_BLACK] = "40", + [TTY_COLOUR16_RED] = "41", + [TTY_COLOUR16_GREEN] = "42", + [TTY_COLOUR16_YELLOW] = "43", + [TTY_COLOUR16_BLUE] = "44", + [TTY_COLOUR16_MAGENTA] = "45", + [TTY_COLOUR16_CYAN] = "46", + [TTY_COLOUR16_WHITE] = "47", + [TTY_COLOUR16_GREY] = "100", + [TTY_COLOUR16_BRIGHT_RED] = "101", + [TTY_COLOUR16_BRIGHT_GREEN] = "102", + [TTY_COLOUR16_BRIGHT_YELLOW] = "103", + [TTY_COLOUR16_BRIGHT_BLUE] = "104", + [TTY_COLOUR16_BRIGHT_MAGENTA] = "105", + [TTY_COLOUR16_BRIGHT_CYAN] = "106", + [TTY_COLOUR16_BRIGHT_WHITE] = "107", +}; + +static void init_tty(struct s_tty *tty, FILE *in, FILE *out) +{ + tty->t_in = in; + tty->t_out = out; + + int fd = fileno(in); + + if (isatty(fd)) { + tty->t_flags |= TTY_INTERACTIVE; + } + + tcgetattr(fd, &tty->t_ios_default); + memcpy(&tty->t_ios_raw, &tty->t_ios_default, sizeof tty->t_ios_raw); + + tty->t_ios_raw.c_iflag &= ~(ICRNL | IXON); + tty->t_ios_raw.c_oflag &= ~(OPOST); + tty->t_ios_raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); + + tty->t_flags |= TTY_INIT; +} + +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) +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_raw); +} + +void s_tty_set_canon(struct s_tty *tty) +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_default); +} + +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; + } + + fprintf(tty->t_out, "\033[0;39;49m"); + + 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( + struct s_tty *tty, enum s_tty_attrib old, enum s_tty_attrib new) +{ + if (old == new) { + return; + } + + /* Bold ON */ + if (!(old & TTY_ATTRIB_BOLD) && new &TTY_ATTRIB_BOLD) { + put_ansi_attrib(tty, ANSI_BOLD_ON); + } + + /* Bold OFF */ + if (old & TTY_ATTRIB_BOLD && !(new &TTY_ATTRIB_BOLD)) { + put_ansi_attrib(tty, ANSI_BOLD_OFF); + } + + /* Underline ON */ + if (!(old & TTY_ATTRIB_UNDERLINE) && new &TTY_ATTRIB_UNDERLINE) { + put_ansi_attrib(tty, ANSI_UNDERLINE_ON); + } + + /* Underline OFF */ + if (old & TTY_ATTRIB_UNDERLINE && !(new &TTY_ATTRIB_UNDERLINE)) { + put_ansi_attrib(tty, ANSI_UNDERLINE_OFF); + } + + /* Italic ON */ + if (!(old & TTY_ATTRIB_ITALIC) && new &TTY_ATTRIB_ITALIC) { + put_ansi_attrib(tty, ANSI_ITALIC_ON); + } + + /* Italic OFF */ + if (old & TTY_ATTRIB_ITALIC && !(new &TTY_ATTRIB_ITALIC)) { + put_ansi_attrib(tty, ANSI_ITALIC_OFF); + } +} + +static void set_fg( + struct s_tty *tty, const struct s_tty_colour *old, + const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + char buf[8]; + + switch (new->c_mode) { + case TTY_COLOUR_NONE: + put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_FG); + break; + case TTY_COLOUR_16: + put_ansi_attrib(tty, ansi_colour16_fg[new->c_16.value]); + break; + case TTY_COLOUR_256: + put_ansi_attrib(tty, ANSI_256COLOUR_FG); + snprintf(buf, sizeof buf, "%u", new->c_256.value); + put_ansi_attrib(tty, buf); + break; + case TTY_COLOUR_TRUE: + put_ansi_attrib(tty, ANSI_TRUECOLOUR_FG); + snprintf(buf, sizeof buf, "%u", new->c_true.r); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.g); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.b); + put_ansi_attrib(tty, buf); + break; + default: + break; + } +} + +static void set_bg( + struct s_tty *tty, const struct s_tty_colour *old, + const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + char buf[8]; + + switch (new->c_mode) { + case TTY_COLOUR_NONE: + put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_BG); + break; + case TTY_COLOUR_16: + put_ansi_attrib(tty, ansi_colour16_bg[new->c_16.value]); + break; + case TTY_COLOUR_256: + put_ansi_attrib(tty, ANSI_256COLOUR_BG); + snprintf(buf, sizeof buf, "%u", new->c_256.value); + put_ansi_attrib(tty, buf); + break; + case TTY_COLOUR_TRUE: + put_ansi_attrib(tty, ANSI_TRUECOLOUR_BG); + snprintf(buf, sizeof buf, "%u", new->c_true.r); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.g); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.b); + put_ansi_attrib(tty, buf); + break; + default: + 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; + + fprintf(tty->t_out, "\033["); + + set_attrib(tty, tty->t_vmode.v_attrib, vmode->v_attrib); + set_fg(tty, &tty->t_vmode.v_fg, &vmode->v_fg); + set_bg(tty, &tty->t_vmode.v_bg, &vmode->v_bg); + + fprintf(tty->t_out, "m"); + + memcpy(&tty->t_vmode, vmode, sizeof *vmode); +} + +s_keycode s_tty_read_key(struct s_tty *tty) +{ + char c; + int v; + int fd = fileno(tty->t_in); + + while (1) { + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + if (c == '\r' || c == '\n') { + return S_KEY_RETURN; + } + + if (c == '\b' || c == 0x7F) { + return S_KEY_BACKSPACE; + } + + if (c >= 1 && c <= 26) { + return S_TTY_CTRL_KEY(c + 'a' - 1); + } + + if (c != 0x1b) { + return c; + } + + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + if (c != '[') { + continue; + } + + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + switch (c) { + case 'A': + return S_KEY_ARROW_UP; + case 'B': + return S_KEY_ARROW_DOWN; + case 'C': + return S_KEY_ARROW_RIGHT; + case 'D': + return S_KEY_ARROW_LEFT; + default: + continue; + } + } + + return c; +} + +void s_tty_move_cursor_x(struct s_tty *tty, enum s_tty_position_base base, int pos) +{ + if (base == TTY_POS_CURSOR && pos < 0 && pos >= -4) { + for (int i = 0; i > pos; i--) { + fputc('\b', tty->t_out); + } + + return; + } + + if (base == TTY_POS_START) { + if (pos == 0) { + fputs("\033[G", tty->t_out); + } else { + fprintf(tty->t_out, "\033[%dG", pos + 1); + } + } else { + if (pos > 1) { + fprintf(tty->t_out, "\033[%dC", pos); + } else if (pos == 1) { + fputs("\033[C", tty->t_out); + } else if (pos == -1) { + fputs("\033[D", tty->t_out); + } else if (pos < -1) { + fprintf(tty->t_out, "\033[%dD", -pos); + } + } +} + +void s_tty_move_cursor_y(struct s_tty *tty, enum s_tty_position_base base, int pos) +{ + if (base == TTY_POS_START) { + /* we don't need this functionality right now */ + abort(); + } + + if (pos > 1) { + fprintf(tty->t_out, "\033[%dB", pos); + } else if (pos == 1) { + fputs("\033[B", tty->t_out); + } else if (pos == -1) { + fputs("\033[A", tty->t_out); + } else if (pos < -1) { + fprintf(tty->t_out, "\033[%dA", -pos); + } +} + +void s_tty_clear(struct s_tty *tty, enum s_tty_clear_mode mode) +{ + const char *arg; + if (mode & TTY_CLEAR_ALL) { + arg = "2"; + } else if (mode & TTY_CLEAR_TO_CURSOR) { + arg = "1"; + } else if (mode & TTY_CLEAR_FROM_CURSOR) { + arg = ""; + } else { + abort(); + } + + if (mode & TTY_CLEAR_SCREEN) { + fprintf(tty->t_out, "\033[%sJ", arg); + } else if (mode & TTY_CLEAR_LINE) { + fprintf(tty->t_out, "\033[%sK", arg); + } else { + abort(); + } +} diff --git a/term/sys/linux/tty.c b/term/sys/linux/tty.c new file mode 100644 index 0000000..e2ca52f --- /dev/null +++ b/term/sys/linux/tty.c @@ -0,0 +1,461 @@ +#include "../../../line-ed/tty.h" + +#include +#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 { + TTY_INIT = 0x01u, + TTY_INTERACTIVE = 0x02u, +}; + +struct s_tty { + FILE *t_in, *t_out; + enum tty_flags t_flags; + struct termios t_ios_raw, t_ios_default; + struct s_tty_vmode t_vmode; + unsigned char t_mcount; +}; + +static struct s_tty tty = {}; + +static const char *ansi_colour16_fg[] = { + [TTY_COLOUR16_BLACK] = "30", + [TTY_COLOUR16_RED] = "31", + [TTY_COLOUR16_GREEN] = "32", + [TTY_COLOUR16_YELLOW] = "33", + [TTY_COLOUR16_BLUE] = "34", + [TTY_COLOUR16_MAGENTA] = "35", + [TTY_COLOUR16_CYAN] = "36", + [TTY_COLOUR16_WHITE] = "37", + [TTY_COLOUR16_GREY] = "90", + [TTY_COLOUR16_BRIGHT_RED] = "91", + [TTY_COLOUR16_BRIGHT_GREEN] = "92", + [TTY_COLOUR16_BRIGHT_YELLOW] = "93", + [TTY_COLOUR16_BRIGHT_BLUE] = "94", + [TTY_COLOUR16_BRIGHT_MAGENTA] = "95", + [TTY_COLOUR16_BRIGHT_CYAN] = "96", + [TTY_COLOUR16_BRIGHT_WHITE] = "97", +}; + +static const char *ansi_colour16_bg[] = { + [TTY_COLOUR16_BLACK] = "40", + [TTY_COLOUR16_RED] = "41", + [TTY_COLOUR16_GREEN] = "42", + [TTY_COLOUR16_YELLOW] = "43", + [TTY_COLOUR16_BLUE] = "44", + [TTY_COLOUR16_MAGENTA] = "45", + [TTY_COLOUR16_CYAN] = "46", + [TTY_COLOUR16_WHITE] = "47", + [TTY_COLOUR16_GREY] = "100", + [TTY_COLOUR16_BRIGHT_RED] = "101", + [TTY_COLOUR16_BRIGHT_GREEN] = "102", + [TTY_COLOUR16_BRIGHT_YELLOW] = "103", + [TTY_COLOUR16_BRIGHT_BLUE] = "104", + [TTY_COLOUR16_BRIGHT_MAGENTA] = "105", + [TTY_COLOUR16_BRIGHT_CYAN] = "106", + [TTY_COLOUR16_BRIGHT_WHITE] = "107", +}; + +static void init_tty(struct s_tty *tty, FILE *in, FILE *out) +{ + tty->t_in = in; + tty->t_out = out; + + int fd = fileno(in); + + if (isatty(fd)) { + tty->t_flags |= TTY_INTERACTIVE; + } + + tcgetattr(fd, &tty->t_ios_default); + memcpy(&tty->t_ios_raw, &tty->t_ios_default, sizeof tty->t_ios_raw); + + tty->t_ios_raw.c_iflag &= ~(ICRNL | IXON); + tty->t_ios_raw.c_oflag &= ~(OPOST); + tty->t_ios_raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); + + tty->t_flags |= TTY_INIT; +} + +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) +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_raw); +} + +void s_tty_set_canon(struct s_tty *tty) +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_default); +} + +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; + } + + fprintf(tty->t_out, "\033[0;39;49m"); + + 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( + struct s_tty *tty, enum s_tty_attrib old, enum s_tty_attrib new) +{ + if (old == new) { + return; + } + + /* Bold ON */ + if (!(old & TTY_ATTRIB_BOLD) && new &TTY_ATTRIB_BOLD) { + put_ansi_attrib(tty, ANSI_BOLD_ON); + } + + /* Bold OFF */ + if (old & TTY_ATTRIB_BOLD && !(new &TTY_ATTRIB_BOLD)) { + put_ansi_attrib(tty, ANSI_BOLD_OFF); + } + + /* Underline ON */ + if (!(old & TTY_ATTRIB_UNDERLINE) && new &TTY_ATTRIB_UNDERLINE) { + put_ansi_attrib(tty, ANSI_UNDERLINE_ON); + } + + /* Underline OFF */ + if (old & TTY_ATTRIB_UNDERLINE && !(new &TTY_ATTRIB_UNDERLINE)) { + put_ansi_attrib(tty, ANSI_UNDERLINE_OFF); + } + + /* Italic ON */ + if (!(old & TTY_ATTRIB_ITALIC) && new &TTY_ATTRIB_ITALIC) { + put_ansi_attrib(tty, ANSI_ITALIC_ON); + } + + /* Italic OFF */ + if (old & TTY_ATTRIB_ITALIC && !(new &TTY_ATTRIB_ITALIC)) { + put_ansi_attrib(tty, ANSI_ITALIC_OFF); + } +} + +static void set_fg( + struct s_tty *tty, const struct s_tty_colour *old, + const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + char buf[8]; + + switch (new->c_mode) { + case TTY_COLOUR_NONE: + put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_FG); + break; + case TTY_COLOUR_16: + put_ansi_attrib(tty, ansi_colour16_fg[new->c_16.value]); + break; + case TTY_COLOUR_256: + put_ansi_attrib(tty, ANSI_256COLOUR_FG); + snprintf(buf, sizeof buf, "%u", new->c_256.value); + put_ansi_attrib(tty, buf); + break; + case TTY_COLOUR_TRUE: + put_ansi_attrib(tty, ANSI_TRUECOLOUR_FG); + snprintf(buf, sizeof buf, "%u", new->c_true.r); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.g); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.b); + put_ansi_attrib(tty, buf); + break; + default: + break; + } +} + +static void set_bg( + struct s_tty *tty, const struct s_tty_colour *old, + const struct s_tty_colour *new) +{ + if (compare_colour(old, new) == 0) { + return; + } + + char buf[8]; + + switch (new->c_mode) { + case TTY_COLOUR_NONE: + put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_BG); + break; + case TTY_COLOUR_16: + put_ansi_attrib(tty, ansi_colour16_bg[new->c_16.value]); + break; + case TTY_COLOUR_256: + put_ansi_attrib(tty, ANSI_256COLOUR_BG); + snprintf(buf, sizeof buf, "%u", new->c_256.value); + put_ansi_attrib(tty, buf); + break; + case TTY_COLOUR_TRUE: + put_ansi_attrib(tty, ANSI_TRUECOLOUR_BG); + snprintf(buf, sizeof buf, "%u", new->c_true.r); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.g); + put_ansi_attrib(tty, buf); + snprintf(buf, sizeof buf, "%u", new->c_true.b); + put_ansi_attrib(tty, buf); + break; + default: + 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; + + fprintf(tty->t_out, "\033["); + + set_attrib(tty, tty->t_vmode.v_attrib, vmode->v_attrib); + set_fg(tty, &tty->t_vmode.v_fg, &vmode->v_fg); + set_bg(tty, &tty->t_vmode.v_bg, &vmode->v_bg); + + fprintf(tty->t_out, "m"); + + memcpy(&tty->t_vmode, vmode, sizeof *vmode); +} + +s_keycode s_tty_read_key(struct s_tty *tty) +{ + char c; + int v; + int fd = fileno(tty->t_in); + + while (1) { + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + if (c == '\r' || c == '\n') { + return S_KEY_RETURN; + } + + if (c == '\b' || c == 0x7F) { + return S_KEY_BACKSPACE; + } + + if (c >= 1 && c <= 26) { + return S_TTY_CTRL_KEY(c + 'a' - 1); + } + + if (c != 0x1b) { + return c; + } + + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + if (c != '[') { + continue; + } + + v = read(fd, &c, 1); + if (v < 1) { + return S_KEY_EOF; + } + + switch (c) { + case 'A': + return S_KEY_ARROW_UP; + case 'B': + return S_KEY_ARROW_DOWN; + case 'C': + return S_KEY_ARROW_RIGHT; + case 'D': + return S_KEY_ARROW_LEFT; + default: + continue; + } + } + + return c; +} + +void s_tty_move_cursor_x(struct s_tty *tty, enum s_tty_position_base base, int pos) +{ + if (base == TTY_POS_CURSOR && pos < 0 && pos >= -4) { + for (int i = 0; i > pos; i--) { + fputc('\b', tty->t_out); + } + + return; + } + + if (base == TTY_POS_START) { + if (pos == 0) { + fputs("\033[G", tty->t_out); + } else { + fprintf(tty->t_out, "\033[%dG", pos + 1); + } + } else { + if (pos > 1) { + fprintf(tty->t_out, "\033[%dC", pos); + } else if (pos == 1) { + fputs("\033[C", tty->t_out); + } else if (pos == -1) { + fputs("\033[D", tty->t_out); + } else if (pos < -1) { + fprintf(tty->t_out, "\033[%dD", -pos); + } + } +} + +void s_tty_move_cursor_y(struct s_tty *tty, enum s_tty_position_base base, int pos) +{ + if (base == TTY_POS_START) { + /* we don't need this functionality right now */ + abort(); + } + + if (pos > 1) { + fprintf(tty->t_out, "\033[%dB", pos); + } else if (pos == 1) { + fputs("\033[B", tty->t_out); + } else if (pos == -1) { + fputs("\033[A", tty->t_out); + } else if (pos < -1) { + fprintf(tty->t_out, "\033[%dA", -pos); + } +} + +void s_tty_clear(struct s_tty *tty, enum s_tty_clear_mode mode) +{ + const char *arg; + if (mode & TTY_CLEAR_ALL) { + arg = "2"; + } else if (mode & TTY_CLEAR_TO_CURSOR) { + arg = "1"; + } else if (mode & TTY_CLEAR_FROM_CURSOR) { + arg = ""; + } else { + abort(); + } + + if (mode & TTY_CLEAR_SCREEN) { + fprintf(tty->t_out, "\033[%sJ", arg); + } else if (mode & TTY_CLEAR_LINE) { + fprintf(tty->t_out, "\033[%sK", arg); + } else { + abort(); + } +} diff --git a/term/sys/windows/tty.c b/term/sys/windows/tty.c new file mode 100644 index 0000000..396113b --- /dev/null +++ b/term/sys/windows/tty.c @@ -0,0 +1,570 @@ +#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); +} diff --git a/term/tty.c b/term/tty.c new file mode 100644 index 0000000..98a4c90 --- /dev/null +++ b/term/tty.c @@ -0,0 +1,330 @@ +#include +#include +#include "tty.h" +#include "printf.h" + +#define MOD_HASH_BLACK 0x4b5dd0abbc6fc1e4 +#define MOD_HASH_RED 0x89e9be1960f4c21c +#define MOD_HASH_GREEN 0x0f40f029637fecbc +#define MOD_HASH_YELLOW 0x8346a574925e75a9 +#define MOD_HASH_BLUE 0xc5ccd29bc2dda64d +#define MOD_HASH_MAGENTA 0x6c90e772edbc8708 +#define MOD_HASH_CYAN 0x70ae2e90c1bce27a +#define MOD_HASH_WHITE 0xced973885856e206 + +#define MOD_HASH_DARK_GREY 0x55a19de854654d99 +#define MOD_HASH_BRIGHT_RED 0xbad8e3fe841b9385 +#define MOD_HASH_BRIGHT_GREEN 0x11cc5e579bdd2fb9 +#define MOD_HASH_BRIGHT_YELLOW 0xfd579007fe8579f6 +#define MOD_HASH_BRIGHT_BLUE 0x57c76bf18badb6d6 +#define MOD_HASH_BRIGHT_MAGENTA 0xf6ecc6d3fdfec129 +#define MOD_HASH_BRIGHT_CYAN 0x03df73fd4e12ec6d +#define MOD_HASH_BRIGHT_WHITE 0xb5ebc3323f57d7fb + +#define MOD_HASH_BG_BLACK 0xd87a8f2d9d394432 +#define MOD_HASH_BG_RED 0x145b1e4366c7d7aa +#define MOD_HASH_BG_GREEN 0xa00b8541d3b1e55a +#define MOD_HASH_BG_YELLOW 0x98b030fd86e3b3cf +#define MOD_HASH_BG_BLUE 0xa15529109506b5df +#define MOD_HASH_BG_MAGENTA 0x86dbda99bcc86222 +#define MOD_HASH_BG_CYAN 0xf16a3104cf61a098 +#define MOD_HASH_BG_WHITE 0x3408c46ab5836674 + +#define MOD_HASH_BG_DARK_GREY 0x820d2e77568eec47 +#define MOD_HASH_BRIGHT_BG_RED 0x144f5dc138087701 +#define MOD_HASH_BRIGHT_BG_GREEN 0xc4d88c6426ffe355 +#define MOD_HASH_BRIGHT_BG_YELLOW 0xf7bb000a4a792602 +#define MOD_HASH_BRIGHT_BG_BLUE 0x9b5c16d6807a1002 +#define MOD_HASH_BRIGHT_BG_MAGENTA 0xc59fb2196cdba3fd +#define MOD_HASH_BRIGHT_BG_CYAN 0x46feb6dc999a6f09 +#define MOD_HASH_BRIGHT_BG_WHITE 0xa3e7d1da08826f5f + +#define MOD_HASH_BOLD 0xcd32ea9bc6b26ff6 +#define MOD_HASH_ULINE 0x141fe741e9f8c22a +#define MOD_HASH_ITALIC 0x69d5e5f057d8992d +#define MOD_HASH_INVERT 0xab4ab85ddd6232e1 +#define MOD_HASH_RESET 0x0f136ff2c086b760 + +#define COMPARE_MOD_NAME(ss, sd, hs, hd) ((hs) == (hd) && !strcmp(ss, sd)) + +static void apply_code_to_vmode(struct tty_format_buf *fmt) +{ + const char *modifier = fmt->buf; + uint64_t mod_hash = b_hash_string(modifier); + + if (COMPARE_MOD_NAME(modifier, "black", mod_hash, MOD_HASH_BLACK)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BLACK; + } + + if (COMPARE_MOD_NAME(modifier, "red", mod_hash, MOD_HASH_RED)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_RED; + } + + if (COMPARE_MOD_NAME(modifier, "green", mod_hash, MOD_HASH_GREEN)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_GREEN; + } + + if (COMPARE_MOD_NAME(modifier, "yellow", mod_hash, MOD_HASH_YELLOW)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_YELLOW; + } + + if (COMPARE_MOD_NAME(modifier, "blue", mod_hash, MOD_HASH_BLUE)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BLUE; + } + + if (COMPARE_MOD_NAME(modifier, "magenta", mod_hash, MOD_HASH_MAGENTA)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_MAGENTA; + } + + if (COMPARE_MOD_NAME(modifier, "cyan", mod_hash, MOD_HASH_CYAN)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_CYAN; + } + + if (COMPARE_MOD_NAME(modifier, "white", mod_hash, MOD_HASH_WHITE)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_WHITE; + } + + if (COMPARE_MOD_NAME(modifier, "dark_grey", mod_hash, MOD_HASH_DARK_GREY)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_BLACK; + } + + if (COMPARE_MOD_NAME(modifier, "bright_red", mod_hash, MOD_HASH_BRIGHT_RED)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_RED; + } + + if (COMPARE_MOD_NAME(modifier, "bright_green", mod_hash, MOD_HASH_BRIGHT_GREEN)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_GREEN; + } + + if (COMPARE_MOD_NAME(modifier, "bright_yellow", mod_hash, MOD_HASH_BRIGHT_YELLOW)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_YELLOW; + } + + if (COMPARE_MOD_NAME(modifier, "bright_blue", mod_hash, MOD_HASH_BRIGHT_BLUE)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_BLUE; + } + + if (COMPARE_MOD_NAME(modifier, "bright_magenta", mod_hash, MOD_HASH_BRIGHT_MAGENTA)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_MAGENTA; + } + + if (COMPARE_MOD_NAME(modifier, "bright_cyan", mod_hash, MOD_HASH_BRIGHT_CYAN)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_CYAN; + } + + if (COMPARE_MOD_NAME(modifier, "bright_white", mod_hash, MOD_HASH_BRIGHT_WHITE)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_BRIGHT_WHITE; + } + + if (COMPARE_MOD_NAME(modifier, "bg_black", mod_hash, MOD_HASH_BG_BLACK)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BLACK; + } + + if (COMPARE_MOD_NAME(modifier, "bg_red", mod_hash, MOD_HASH_BG_RED)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_RED; + } + + if (COMPARE_MOD_NAME(modifier, "bg_green", mod_hash, MOD_HASH_BG_GREEN)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_GREEN; + } + + if (COMPARE_MOD_NAME(modifier, "bg_yellow", mod_hash, MOD_HASH_BG_YELLOW)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_YELLOW; + } + + if (COMPARE_MOD_NAME(modifier, "bg_blue", mod_hash, MOD_HASH_BG_BLUE)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BLUE; + } + + if (COMPARE_MOD_NAME(modifier, "bg_magenta", mod_hash, MOD_HASH_BG_MAGENTA)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_MAGENTA; + } + + if (COMPARE_MOD_NAME(modifier, "bg_cyan", mod_hash, MOD_HASH_BG_CYAN)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_CYAN; + } + + if (COMPARE_MOD_NAME(modifier, "bg_white", mod_hash, MOD_HASH_BG_WHITE)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_WHITE; + } + + if (COMPARE_MOD_NAME(modifier, "bg_dark_grey", mod_hash, MOD_HASH_BG_DARK_GREY)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_BLACK; + } + + if (COMPARE_MOD_NAME(modifier, "bright_bg_red", mod_hash, MOD_HASH_BRIGHT_BG_RED)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_RED; + } + + if (COMPARE_MOD_NAME(modifier, "bright_bg_green", mod_hash, MOD_HASH_BRIGHT_BG_GREEN)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_GREEN; + } + + if (COMPARE_MOD_NAME(modifier, "bright_bg_yellow", mod_hash, MOD_HASH_BRIGHT_BG_YELLOW)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_YELLOW; + } + + if (COMPARE_MOD_NAME(modifier, "bright_bg_blue", mod_hash, MOD_HASH_BRIGHT_BG_BLUE)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_BLUE; + } + + if (COMPARE_MOD_NAME(modifier, "bright_bg_magenta", mod_hash, MOD_HASH_BRIGHT_BG_MAGENTA)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_MAGENTA; + } + + if (COMPARE_MOD_NAME(modifier, "bright_bg_cyan", mod_hash, MOD_HASH_BRIGHT_BG_CYAN)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_CYAN; + } + + if (COMPARE_MOD_NAME(modifier, "bright_bg_white", mod_hash, MOD_HASH_BRIGHT_BG_WHITE)) { + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BRIGHT_WHITE; + } + + if (COMPARE_MOD_NAME(modifier, "bold", mod_hash, MOD_HASH_BOLD)) { + fmt->vmode.v_attrib |= B_TTY_ATTRIB_BOLD; + } + + if (COMPARE_MOD_NAME(modifier, "uline", mod_hash, MOD_HASH_ULINE)) { + fmt->vmode.v_attrib |= B_TTY_ATTRIB_UNDERLINE; + } + + if (COMPARE_MOD_NAME(modifier, "italic", mod_hash, MOD_HASH_ITALIC)) { + fmt->vmode.v_attrib |= B_TTY_ATTRIB_ITALIC; + } + + if (COMPARE_MOD_NAME(modifier, "invert", mod_hash, MOD_HASH_INVERT)) { + fmt->vmode.v_attrib |= B_TTY_ATTRIB_INVERT; + } + + if (COMPARE_MOD_NAME(modifier, "reset", mod_hash, MOD_HASH_RESET)) { + fmt->vmode.v_fg.c_mode = B_TTY_COLOUR_16; + fmt->vmode.v_bg.c_mode = B_TTY_COLOUR_16; + + fmt->vmode.v_fg.c_16.value = B_TTY_COLOUR16_WHITE; + fmt->vmode.v_bg.c_16.value = B_TTY_COLOUR16_BLACK; + fmt->vmode.v_attrib = 0; + } +} + +static void format_buffer_putc(struct tty_format_buf* fmt, int c) +{ + if (fmt->ptr < TTY_TMPBUF_SIZE - 1) { + fmt->buf[fmt->ptr++] = c; + fmt->buf[fmt->ptr] = '\0'; + } +} + +static void format_buffer_clear(struct tty_format_buf* fmt) +{ + fmt->ptr = 0; + fmt->buf[fmt->ptr] = '\0'; +} + +static void flush_vmode(struct b_tty* tty, struct tty_format_buf* fmt) +{ + b_tty_set_vmode(tty, &fmt->vmode); +} + +int b_tty_putc(struct b_tty *tty, enum b_tty_print_flags flags, char c) +{ + if (flags & B_TTY_DISABLE_FORMATTING) { + z__b_tty_putc(tty, c); + return c; + } + + struct tty_format_buf *fmt = z__b_tty_get_format_buf(tty); + + if (c == '[') { + if (!fmt->in_format) { + fmt->in_format = true; + return c; + } + + if (fmt->ptr == 0) { + fmt->in_format = false; + z__b_tty_putc(tty, '['); + return c; + } + + format_buffer_putc(fmt, c); + + return c; + } + + if (fmt->in_format && c == ']') { + apply_code_to_vmode(fmt); + format_buffer_clear(fmt); + flush_vmode(tty, fmt); + fmt->in_format = false; + return c; + } + + if (fmt->in_format && c == ',') { + apply_code_to_vmode(fmt); + format_buffer_clear(fmt); + return c; + } + + if (fmt->in_format) { + format_buffer_putc(fmt, c); + } else { + z__b_tty_putc(tty, c); + } + + return c; +} + +int b_tty_puts( + struct b_tty* tty, enum b_tty_print_flags flags, const char* s) +{ + int r = 0; + + while (s[r]) { + b_tty_putc(tty, flags, s[r]); + r++; + } + + return r; +} + +int b_tty_printf( + struct b_tty* tty, enum b_tty_print_flags flags, const char* s, ...) +{ + va_list arg; + va_start(arg, s); + int r = b_tty_vprintf(tty, flags, s, arg); + va_end(arg); + return r; +} diff --git a/term/tty.h b/term/tty.h new file mode 100644 index 0000000..6a38e11 --- /dev/null +++ b/term/tty.h @@ -0,0 +1,19 @@ +#ifndef _TTY_H_ +#define _TTY_H_ + +#define TTY_TMPBUF_SIZE 128 + +struct b_tty; + +struct tty_format_buf { + bool in_format; + char buf[TTY_TMPBUF_SIZE]; + unsigned int ptr; + + struct b_tty_vmode vmode; +}; + +extern struct tty_format_buf *z__b_tty_get_format_buf(struct b_tty *tty); +extern void z__b_tty_putc(struct b_tty *tty, char c); + +#endif \ No newline at end of file