From 43b88be5546fcdf4e623f7c1a582ed38f33bda7a Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Tue, 22 Jan 2019 15:52:41 +0900 Subject: Update to upstream revision 727 This is the newest version with changes to how the color handling is done. Test: start Terminal.apk with and without USE_TEST_SHELL Change-Id: I14decec8617ab108f2236fc66890bb1e7ab17394 --- Android.bp | 7 +- include/vterm.h | 249 +++++++++++++++++++++++++--- include/vterm_input.h | 58 ------- include/vterm_keycodes.h | 61 +++++++ src/encoding.c | 26 +-- src/fullwidth.inc | 104 ++++++++++++ src/keyboard.c | 226 ++++++++++++++++++++++++++ src/mouse.c | 96 +++++++++++ src/parser.c | 414 +++++++++++++++++++++++------------------------ src/pen.c | 185 ++++++++++++++------- src/screen.c | 79 +++++---- src/state.c | 375 +++++++++++++++++++++++++++--------------- src/unicode.c | 25 +-- src/vterm.c | 68 +++++--- src/vterm_internal.h | 113 ++++++++----- 15 files changed, 1500 insertions(+), 586 deletions(-) delete mode 100644 include/vterm_input.h create mode 100644 include/vterm_keycodes.h create mode 100644 src/fullwidth.inc create mode 100644 src/keyboard.c create mode 100644 src/mouse.c diff --git a/Android.bp b/Android.bp index 33d3b75..2a97539 100644 --- a/Android.bp +++ b/Android.bp @@ -4,14 +4,15 @@ cc_library_static { export_include_dirs: ["include"], srcs: [ - "src/input.c", - "src/vterm.c", "src/encoding.c", + "src/input.c", + "src/keyboard.c", "src/parser.c", - "src/unicode.c", "src/pen.c", "src/screen.c", "src/state.c", + "src/unicode.c", + "src/vterm.c", ], cflags: [ diff --git a/include/vterm.h b/include/vterm.h index d43add7..51dc970 100644 --- a/include/vterm.h +++ b/include/vterm.h @@ -7,8 +7,9 @@ extern "C" { #include #include +#include -#include "vterm_input.h" +#include "vterm_keycodes.h" typedef struct VTerm VTerm; typedef struct VTermState VTermState; @@ -48,16 +49,160 @@ static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta rect->start_col += col_delta; rect->end_col += col_delta; } -typedef struct { - uint8_t red, green, blue; +/** + * Bit-field describing the content of the tagged union `VTermColor`. + */ +typedef enum { + /** + * If the lower bit of `type` is not set, the colour is 24-bit RGB. + */ + VTERM_COLOR_RGB = 0x00, + + /** + * The colour is an index into a palette of 256 colours. + */ + VTERM_COLOR_INDEXED = 0x01, + + /** + * Mask that can be used to extract the RGB/Indexed bit. + */ + VTERM_COLOR_TYPE_MASK = 0x01, + + /** + * If set, indicates that this colour should be the default foreground + * color, i.e. there was no SGR request for another colour. When + * rendering this colour it is possible to ignore "idx" and just use a + * colour that is not in the palette. + */ + VTERM_COLOR_DEFAULT_FG = 0x02, + + /** + * If set, indicates that this colour should be the default background + * color, i.e. there was no SGR request for another colour. A common + * option when rendering this colour is to not render a background at + * all, for example by rendering the window transparently at this spot. + */ + VTERM_COLOR_DEFAULT_BG = 0x04, + + /** + * Mask that can be used to extract the default foreground/background bit. + */ + VTERM_COLOR_DEFAULT_MASK = 0x06 +} VTermColorType; + +/** + * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the + * given VTermColor instance is an indexed colour. + */ +#define VTERM_COLOR_IS_INDEXED(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) + +/** + * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that + * the given VTermColor instance is an rgb colour. + */ +#define VTERM_COLOR_IS_RGB(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default foreground + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_FG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default background + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_BG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) + +/** + * Tagged union storing either an RGB color or an index into a colour palette. + * In order to convert indexed colours to RGB, you may use the + * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() + * functions which lookup the RGB colour from the palette maintained by a + * VTermState or VTermScreen instance. + */ +typedef union { + /** + * Tag indicating which union member is actually valid. This variable + * coincides with the `type` member of the `rgb` and the `indexed` struct + * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether + * a particular type flag is set. + */ + uint8_t type; + + /** + * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * The actual 8-bit red, green, blue colour values. + */ + uint8_t red, green, blue; + } rgb; + + /** + * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into + * the colour palette. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * Index into the colour map. + */ + uint8_t idx; + } indexed; } VTermColor; +/** + * Constructs a new VTermColor instance representing the given RGB values. + */ +static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, + uint8_t blue) +{ + col->type = VTERM_COLOR_RGB; + col->rgb.red = red; + col->rgb.green = green; + col->rgb.blue = blue; +} + +/** + * Construct a new VTermColor instance representing an indexed color with the + * given index. + */ +static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) +{ + col->type = VTERM_COLOR_INDEXED; + col->indexed.idx = idx; +} + +/** + * Compares two colours. Returns true if the colors are equal, false otherwise. + */ +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); + typedef enum { /* VTERM_VALUETYPE_NONE = 0 */ VTERM_VALUETYPE_BOOL = 1, VTERM_VALUETYPE_INT, VTERM_VALUETYPE_STRING, VTERM_VALUETYPE_COLOR, + + VTERM_N_VALUETYPES } VTermValueType; typedef union { @@ -78,6 +223,8 @@ typedef enum { VTERM_ATTR_FONT, // number: 10-19 VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 + + VTERM_N_ATTRS } VTermAttr; typedef enum { @@ -89,15 +236,27 @@ typedef enum { VTERM_PROP_ICONNAME, // string VTERM_PROP_REVERSE, // bool VTERM_PROP_CURSORSHAPE, // number + VTERM_PROP_MOUSE, // number + + VTERM_N_PROPS } VTermProp; enum { VTERM_PROP_CURSORSHAPE_BLOCK = 1, VTERM_PROP_CURSORSHAPE_UNDERLINE, VTERM_PROP_CURSORSHAPE_BAR_LEFT, + + VTERM_N_PROP_CURSORSHAPES }; -typedef void (*VTermMouseFunc)(int x, int y, int button, int pressed, int modifiers, void *data); +enum { + VTERM_PROP_MOUSE_NONE = 0, + VTERM_PROP_MOUSE_CLICK, + VTERM_PROP_MOUSE_DRAG, + VTERM_PROP_MOUSE_MOVE, + + VTERM_N_PROP_MOUSES +}; typedef struct { const uint32_t *chars; @@ -126,18 +285,25 @@ void vterm_free(VTerm* vt); void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); void vterm_set_size(VTerm *vt, int rows, int cols); -void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len); - -void vterm_input_push_char(VTerm *vt, VTermModifier state, uint32_t c); -void vterm_input_push_key(VTerm *vt, VTermModifier state, VTermKey key); +int vterm_get_utf8(const VTerm *vt); +void vterm_set_utf8(VTerm *vt, int is_utf8); -size_t vterm_output_bufferlen(VTerm *vt); /* deprecated */ +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); size_t vterm_output_get_buffer_size(const VTerm *vt); size_t vterm_output_get_buffer_current(const VTerm *vt); size_t vterm_output_get_buffer_remaining(const VTerm *vt); -size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len); +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); + +void vterm_keyboard_start_paste(VTerm *vt); +void vterm_keyboard_end_paste(VTerm *vt); + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); // ------------ // Parser layer @@ -151,8 +317,8 @@ size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len); * * Don't confuse this with the final byte of the CSI escape; 'a' in this case. */ -#define CSI_ARG_FLAG_MORE (1<<31) -#define CSI_ARG_MASK (~(1<<31)) +#define CSI_ARG_FLAG_MORE (1U<<31) +#define CSI_ARG_MASK (~(1U<<31)) #define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) #define CSI_ARG(a) ((a) & CSI_ARG_MASK) @@ -174,9 +340,8 @@ typedef struct { int (*resize)(int rows, int cols, void *user); } VTermParserCallbacks; -void vterm_set_parser_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); - -void vterm_parser_set_utf8(VTerm *vt, int is_utf8); +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); +void *vterm_parser_get_cbdata(VTerm *vt); // ----------- // State layer @@ -191,7 +356,6 @@ typedef struct { int (*initpen)(void *user); int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); int (*settermprop)(VTermProp prop, VTermValue *val, void *user); - int (*setmousefunc)(VTermMouseFunc func, void *data, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, VTermPos *delta, void *user); int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); @@ -199,8 +363,14 @@ typedef struct { VTermState *vterm_obtain_state(VTerm *vt); +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); +void *vterm_state_get_cbdata(VTermState *state); + +// Only invokes control, csi, osc, dcs +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user); +void *vterm_state_get_unrecognised_fbdata(VTermState *state); + void vterm_state_reset(VTermState *state, int hard); -void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); @@ -209,17 +379,27 @@ void vterm_state_set_palette_color(VTermState *state, int index, const VTermColo void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); +void vterm_state_focus_in(VTermState *state); +void vterm_state_focus_out(VTermState *state); const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); +/** + * Makes sure that the given color `col` is indeed an RGB colour. After this + * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other + * flags stored in `col->type` will have been reset. + * + * @param state is the VTermState instance from which the colour palette should + * be extracted. + * @param col is a pointer at the VTermColor instance that should be converted + * to an RGB colour. + */ +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); + // ------------ // Screen layer // ------------ typedef struct { -#define VTERM_MAX_CHARS_PER_CELL 6 - uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; - char width; - struct { unsigned int bold : 1; unsigned int underline : 2; unsigned int italic : 1; @@ -229,7 +409,13 @@ typedef struct { unsigned int font : 4; /* 0 to 9 */ unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ - } attrs; +} VTermScreenCellAttrs; + +typedef struct { +#define VTERM_MAX_CHARS_PER_CELL 6 + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + char width; + VTermScreenCellAttrs attrs; VTermColor fg, bg; } VTermScreenCell; @@ -238,7 +424,6 @@ typedef struct { int (*moverect)(VTermRect dest, VTermRect src, void *user); int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); int (*settermprop)(VTermProp prop, VTermValue *val, void *user); - int (*setmousefunc)(VTermMouseFunc func, void *data, void *user); int (*bell)(void *user); int (*resize)(int rows, int cols, void *user); int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); @@ -247,14 +432,22 @@ typedef struct { VTermScreen *vterm_obtain_screen(VTerm *vt); +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); +void *vterm_screen_get_cbdata(VTermScreen *screen); + +// Only invokes control, csi, osc, dcs +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user); +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); + void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); -void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); typedef enum { VTERM_DAMAGE_CELL, /* every cell */ VTERM_DAMAGE_ROW, /* entire rows */ VTERM_DAMAGE_SCREEN, /* entire screen */ VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ + + VTERM_N_DAMAGES } VTermDamageSize; void vterm_screen_flush_damage(VTermScreen *screen); @@ -276,6 +469,8 @@ typedef enum { VTERM_ATTR_FONT_MASK = 1 << 6, VTERM_ATTR_FOREGROUND_MASK = 1 << 7, VTERM_ATTR_BACKGROUND_MASK = 1 << 8, + + VTERM_ALL_ATTRS_MASK = (1 << 9) - 1 } VTermAttrMask; int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); @@ -284,6 +479,12 @@ int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCe int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); +/** + * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` + * instance. + */ +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); + // --------- // Utilities // --------- diff --git a/include/vterm_input.h b/include/vterm_input.h deleted file mode 100644 index 165d747..0000000 --- a/include/vterm_input.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef __VTERM_INPUT_H__ -#define __VTERM_INPUT_H__ - -typedef enum { - VTERM_MOD_NONE = 0x00, - VTERM_MOD_SHIFT = 0x01, - VTERM_MOD_ALT = 0x02, - VTERM_MOD_CTRL = 0x04, -} VTermModifier; - -typedef enum { - VTERM_KEY_NONE, - - VTERM_KEY_ENTER, - VTERM_KEY_TAB, - VTERM_KEY_BACKSPACE, - VTERM_KEY_ESCAPE, - - VTERM_KEY_UP, - VTERM_KEY_DOWN, - VTERM_KEY_LEFT, - VTERM_KEY_RIGHT, - - VTERM_KEY_INS, - VTERM_KEY_DEL, - VTERM_KEY_HOME, - VTERM_KEY_END, - VTERM_KEY_PAGEUP, - VTERM_KEY_PAGEDOWN, - - VTERM_KEY_FUNCTION_0 = 256, - VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, - - VTERM_KEY_KP_0, - VTERM_KEY_KP_1, - VTERM_KEY_KP_2, - VTERM_KEY_KP_3, - VTERM_KEY_KP_4, - VTERM_KEY_KP_5, - VTERM_KEY_KP_6, - VTERM_KEY_KP_7, - VTERM_KEY_KP_8, - VTERM_KEY_KP_9, - VTERM_KEY_KP_MULT, - VTERM_KEY_KP_PLUS, - VTERM_KEY_KP_COMMA, - VTERM_KEY_KP_MINUS, - VTERM_KEY_KP_PERIOD, - VTERM_KEY_KP_DIVIDE, - VTERM_KEY_KP_ENTER, - VTERM_KEY_KP_EQUAL, - - VTERM_KEY_MAX, // Must be last -} VTermKey; - -#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) - -#endif diff --git a/include/vterm_keycodes.h b/include/vterm_keycodes.h new file mode 100644 index 0000000..661759f --- /dev/null +++ b/include/vterm_keycodes.h @@ -0,0 +1,61 @@ +#ifndef __VTERM_INPUT_H__ +#define __VTERM_INPUT_H__ + +typedef enum { + VTERM_MOD_NONE = 0x00, + VTERM_MOD_SHIFT = 0x01, + VTERM_MOD_ALT = 0x02, + VTERM_MOD_CTRL = 0x04, + + VTERM_ALL_MODS_MASK = 0x07 +} VTermModifier; + +typedef enum { + VTERM_KEY_NONE, + + VTERM_KEY_ENTER, + VTERM_KEY_TAB, + VTERM_KEY_BACKSPACE, + VTERM_KEY_ESCAPE, + + VTERM_KEY_UP, + VTERM_KEY_DOWN, + VTERM_KEY_LEFT, + VTERM_KEY_RIGHT, + + VTERM_KEY_INS, + VTERM_KEY_DEL, + VTERM_KEY_HOME, + VTERM_KEY_END, + VTERM_KEY_PAGEUP, + VTERM_KEY_PAGEDOWN, + + VTERM_KEY_FUNCTION_0 = 256, + VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, + + VTERM_KEY_KP_0, + VTERM_KEY_KP_1, + VTERM_KEY_KP_2, + VTERM_KEY_KP_3, + VTERM_KEY_KP_4, + VTERM_KEY_KP_5, + VTERM_KEY_KP_6, + VTERM_KEY_KP_7, + VTERM_KEY_KP_8, + VTERM_KEY_KP_9, + VTERM_KEY_KP_MULT, + VTERM_KEY_KP_PLUS, + VTERM_KEY_KP_COMMA, + VTERM_KEY_KP_MINUS, + VTERM_KEY_KP_PERIOD, + VTERM_KEY_KP_DIVIDE, + VTERM_KEY_KP_ENTER, + VTERM_KEY_KP_EQUAL, + + VTERM_KEY_MAX, // Must be last + VTERM_N_KEYS = VTERM_KEY_MAX +} VTermKey; + +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) + +#endif diff --git a/src/encoding.c b/src/encoding.c index 1495855..434ac3f 100644 --- a/src/encoding.c +++ b/src/encoding.c @@ -42,10 +42,10 @@ static void decode_utf8(VTermEncoding *enc, void *data_, printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); #endif - if(c < 0x20) + if(c < 0x20) // C0 return; - else if(c >= 0x20 && c < 0x80) { + else if(c >= 0x20 && c < 0x7f) { if(data->bytes_remaining) cp[(*cpi)++] = UNICODE_INVALID; @@ -56,6 +56,9 @@ static void decode_utf8(VTermEncoding *enc, void *data_, data->bytes_remaining = 0; } + else if(c == 0x7f) // DEL + return; + else if(c >= 0x80 && c < 0xc0) { if(!data->bytes_remaining) { cp[(*cpi)++] = UNICODE_INVALID; @@ -73,15 +76,20 @@ static void decode_utf8(VTermEncoding *enc, void *data_, // Check for overlong sequences switch(data->bytes_total) { case 2: - if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; break; + if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; + break; case 3: - if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; break; + if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; + break; case 4: - if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; break; + if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; + break; case 5: - if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; break; + if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; + break; case 6: - if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; break; + if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; + break; } // Now look for plain invalid ones if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || @@ -160,7 +168,7 @@ static void decode_usascii(VTermEncoding *enc, void *data, for(; *pos < bytelen && *cpi < cplen; (*pos)++) { unsigned char c = bytes[*pos] ^ is_gr; - if(c < 0x20 || c >= 0x80) + if(c < 0x20 || c == 0x7f || c >= 0x80) return; cp[(*cpi)++] = c; @@ -186,7 +194,7 @@ static void decode_table(VTermEncoding *enc, void *data, for(; *pos < bytelen && *cpi < cplen; (*pos)++) { unsigned char c = bytes[*pos] ^ is_gr; - if(c < 0x20 || c >= 0x80) + if(c < 0x20 || c == 0x7f || c >= 0x80) return; if(table->chars[c]) diff --git a/src/fullwidth.inc b/src/fullwidth.inc new file mode 100644 index 0000000..7ff142f --- /dev/null +++ b/src/fullwidth.inc @@ -0,0 +1,104 @@ + { 0x1100, 0x115f }, + { 0x231a, 0x231b }, + { 0x2329, 0x232a }, + { 0x23e9, 0x23ec }, + { 0x23f0, 0x23f0 }, + { 0x23f3, 0x23f3 }, + { 0x25fd, 0x25fe }, + { 0x2614, 0x2615 }, + { 0x2648, 0x2653 }, + { 0x267f, 0x267f }, + { 0x2693, 0x2693 }, + { 0x26a1, 0x26a1 }, + { 0x26aa, 0x26ab }, + { 0x26bd, 0x26be }, + { 0x26c4, 0x26c5 }, + { 0x26ce, 0x26ce }, + { 0x26d4, 0x26d4 }, + { 0x26ea, 0x26ea }, + { 0x26f2, 0x26f3 }, + { 0x26f5, 0x26f5 }, + { 0x26fa, 0x26fa }, + { 0x26fd, 0x26fd }, + { 0x2705, 0x2705 }, + { 0x270a, 0x270b }, + { 0x2728, 0x2728 }, + { 0x274c, 0x274c }, + { 0x274e, 0x274e }, + { 0x2753, 0x2755 }, + { 0x2757, 0x2757 }, + { 0x2795, 0x2797 }, + { 0x27b0, 0x27b0 }, + { 0x27bf, 0x27bf }, + { 0x2b1b, 0x2b1c }, + { 0x2b50, 0x2b50 }, + { 0x2b55, 0x2b55 }, + { 0x2e80, 0x2e99 }, + { 0x2e9b, 0x2ef3 }, + { 0x2f00, 0x2fd5 }, + { 0x2ff0, 0x2ffb }, + { 0x3000, 0x303e }, + { 0x3041, 0x3096 }, + { 0x3099, 0x30ff }, + { 0x3105, 0x312d }, + { 0x3131, 0x318e }, + { 0x3190, 0x31ba }, + { 0x31c0, 0x31e3 }, + { 0x31f0, 0x321e }, + { 0x3220, 0x3247 }, + { 0x3250, 0x32fe }, + { 0x3300, 0x4dbf }, + { 0x4e00, 0xa48c }, + { 0xa490, 0xa4c6 }, + { 0xa960, 0xa97c }, + { 0xac00, 0xd7a3 }, + { 0xf900, 0xfaff }, + { 0xfe10, 0xfe19 }, + { 0xfe30, 0xfe52 }, + { 0xfe54, 0xfe66 }, + { 0xfe68, 0xfe6b }, + { 0xff01, 0xff60 }, + { 0xffe0, 0xffe6 }, + { 0x16fe0, 0x16fe0 }, + { 0x17000, 0x187ec }, + { 0x18800, 0x18af2 }, + { 0x1b000, 0x1b001 }, + { 0x1f004, 0x1f004 }, + { 0x1f0cf, 0x1f0cf }, + { 0x1f18e, 0x1f18e }, + { 0x1f191, 0x1f19a }, + { 0x1f200, 0x1f202 }, + { 0x1f210, 0x1f23b }, + { 0x1f240, 0x1f248 }, + { 0x1f250, 0x1f251 }, + { 0x1f300, 0x1f320 }, + { 0x1f32d, 0x1f335 }, + { 0x1f337, 0x1f37c }, + { 0x1f37e, 0x1f393 }, + { 0x1f3a0, 0x1f3ca }, + { 0x1f3cf, 0x1f3d3 }, + { 0x1f3e0, 0x1f3f0 }, + { 0x1f3f4, 0x1f3f4 }, + { 0x1f3f8, 0x1f43e }, + { 0x1f440, 0x1f440 }, + { 0x1f442, 0x1f4fc }, + { 0x1f4ff, 0x1f53d }, + { 0x1f54b, 0x1f54e }, + { 0x1f550, 0x1f567 }, + { 0x1f57a, 0x1f57a }, + { 0x1f595, 0x1f596 }, + { 0x1f5a4, 0x1f5a4 }, + { 0x1f5fb, 0x1f64f }, + { 0x1f680, 0x1f6c5 }, + { 0x1f6cc, 0x1f6cc }, + { 0x1f6d0, 0x1f6d2 }, + { 0x1f6eb, 0x1f6ec }, + { 0x1f6f4, 0x1f6f6 }, + { 0x1f910, 0x1f91e }, + { 0x1f920, 0x1f927 }, + { 0x1f930, 0x1f930 }, + { 0x1f933, 0x1f93e }, + { 0x1f940, 0x1f94b }, + { 0x1f950, 0x1f95e }, + { 0x1f980, 0x1f991 }, + { 0x1f9c0, 0x1f9c0 }, diff --git a/src/keyboard.c b/src/keyboard.c new file mode 100644 index 0000000..b541fb1 --- /dev/null +++ b/src/keyboard.c @@ -0,0 +1,226 @@ +#include "vterm_internal.h" + +#include + +#include "utf8.h" + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) +{ + /* The shift modifier is never important for Unicode characters + * apart from Space + */ + if(c != ' ') + mod &= ~VTERM_MOD_SHIFT; + + if(mod == 0) { + // Normal text - ignore just shift + char str[6]; + int seqlen = fill_utf8(c, str); + vterm_push_output_bytes(vt, str, seqlen); + return; + } + + int needs_CSIu; + switch(c) { + /* Special Ctrl- letters that can't be represented elsewise */ + case 'i': case 'j': case 'm': case '[': + needs_CSIu = 1; + break; + /* Ctrl-\ ] ^ _ don't need CSUu */ + case '\\': case ']': case '^': case '_': + needs_CSIu = 0; + break; + /* Shift-space needs CSIu */ + case ' ': + needs_CSIu = !!(mod & VTERM_MOD_SHIFT); + break; + /* All other characters needs CSIu except for letters a-z */ + default: + needs_CSIu = (c < 'a' || c > 'z'); + } + + /* ALT we can just prefix with ESC; anything else requires CSI u */ + if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1); + return; + } + + if(mod & VTERM_MOD_CTRL) + c &= 0x1f; + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_SS3, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + KEYCODE_KEYPAD, + } type; + char literal; + int csinum; +} keycodes_s; + +static keycodes_s keycodes[] = { + { KEYCODE_NONE }, // NONE + + { KEYCODE_ENTER, '\r' }, // ENTER + { KEYCODE_TAB, '\t' }, // TAB + { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL + { KEYCODE_LITERAL, '\x1b' }, // ESCAPE + + { KEYCODE_CSI_CURSOR, 'A' }, // UP + { KEYCODE_CSI_CURSOR, 'B' }, // DOWN + { KEYCODE_CSI_CURSOR, 'D' }, // LEFT + { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT + + { KEYCODE_CSINUM, '~', 2 }, // INS + { KEYCODE_CSINUM, '~', 3 }, // DEL + { KEYCODE_CSI_CURSOR, 'H' }, // HOME + { KEYCODE_CSI_CURSOR, 'F' }, // END + { KEYCODE_CSINUM, '~', 5 }, // PAGEUP + { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN +}; + +static keycodes_s keycodes_fn[] = { + { KEYCODE_NONE }, // F0 - shouldn't happen + { KEYCODE_CSI_CURSOR, 'P' }, // F1 + { KEYCODE_CSI_CURSOR, 'Q' }, // F2 + { KEYCODE_CSI_CURSOR, 'R' }, // F3 + { KEYCODE_CSI_CURSOR, 'S' }, // F4 + { KEYCODE_CSINUM, '~', 15 }, // F5 + { KEYCODE_CSINUM, '~', 17 }, // F6 + { KEYCODE_CSINUM, '~', 18 }, // F7 + { KEYCODE_CSINUM, '~', 19 }, // F8 + { KEYCODE_CSINUM, '~', 20 }, // F9 + { KEYCODE_CSINUM, '~', 21 }, // F10 + { KEYCODE_CSINUM, '~', 23 }, // F11 + { KEYCODE_CSINUM, '~', 24 }, // F12 +}; + +static keycodes_s keycodes_kp[] = { + { KEYCODE_KEYPAD, '0', 'p' }, // KP_0 + { KEYCODE_KEYPAD, '1', 'q' }, // KP_1 + { KEYCODE_KEYPAD, '2', 'r' }, // KP_2 + { KEYCODE_KEYPAD, '3', 's' }, // KP_3 + { KEYCODE_KEYPAD, '4', 't' }, // KP_4 + { KEYCODE_KEYPAD, '5', 'u' }, // KP_5 + { KEYCODE_KEYPAD, '6', 'v' }, // KP_6 + { KEYCODE_KEYPAD, '7', 'w' }, // KP_7 + { KEYCODE_KEYPAD, '8', 'x' }, // KP_8 + { KEYCODE_KEYPAD, '9', 'y' }, // KP_9 + { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT + { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS + { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA + { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS + { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD + { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE + { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER + { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL +}; + +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) +{ + if(key == VTERM_KEY_NONE) + return; + + keycodes_s k; + if(key < VTERM_KEY_FUNCTION_0) { + if(key >= sizeof(keycodes)/sizeof(keycodes[0])) + return; + k = keycodes[key]; + } + else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { + if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) + return; + k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; + } + else if(key >= VTERM_KEY_KP_0) { + if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) + return; + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } + + switch(k.type) { + case KEYCODE_NONE: + break; + + case KEYCODE_TAB: + /* Shift-Tab is CSI Z but plain Tab is 0x09 */ + if(mod == VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); + else if(mod & VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1); + else + goto case_LITERAL; + break; + + case KEYCODE_ENTER: + /* Enter is CRLF in newline mode, but just LF in linefeed */ + if(vt->state->mode.newline) + vterm_push_output_sprintf(vt, "\r\n"); + else + goto case_LITERAL; + break; + + case KEYCODE_LITERAL: case_LITERAL: + if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1); + else + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); + break; + + case KEYCODE_SS3: case_SS3: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); + else + goto case_CSI; + break; + + case KEYCODE_CSI: case_CSI: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); + break; + + case KEYCODE_CSINUM: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); + break; + + case KEYCODE_CSI_CURSOR: + if(vt->state->mode.cursor) + goto case_SS3; + else + goto case_CSI; + + case KEYCODE_KEYPAD: + if(vt->state->mode.keypad) { + k.literal = k.csinum; + goto case_SS3; + } + else + goto case_LITERAL; + } +} + +void vterm_keyboard_start_paste(VTerm *vt) +{ + if(vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); +} + +void vterm_keyboard_end_paste(VTerm *vt) +{ + if(vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); +} diff --git a/src/mouse.c b/src/mouse.c new file mode 100644 index 0000000..9962e4f --- /dev/null +++ b/src/mouse.c @@ -0,0 +1,96 @@ +#include "vterm_internal.h" + +#include "utf8.h" + +static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) +{ + modifiers <<= 2; + + switch(state->mouse_protocol) { + case MOUSE_X10: + if(col + 0x21 > 0xff) + col = 0xff - 0x21; + if(row + 0x21 > 0xff) + row = 0xff - 0x21; + + if(!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", + (code | modifiers) + 0x20, col + 0x21, row + 0x21); + break; + + case MOUSE_UTF8: + { + char utf8[18]; size_t len = 0; + + if(!pressed) + code = 3; + + len += fill_utf8((code | modifiers) + 0x20, utf8 + len); + len += fill_utf8(col + 0x21, utf8 + len); + len += fill_utf8(row + 0x21, utf8 + len); + utf8[len] = 0; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); + } + break; + + case MOUSE_SGR: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", + code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); + break; + + case MOUSE_RXVT: + if(!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", + code | modifiers, col + 1, row + 1); + break; + } +} + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) +{ + VTermState *state = vt->state; + + if(col == state->mouse_col && row == state->mouse_row) + return; + + state->mouse_col = col; + state->mouse_row = row; + + if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || + (state->mouse_flags & MOUSE_WANT_MOVE)) { + int button = state->mouse_buttons & 0x01 ? 1 : + state->mouse_buttons & 0x02 ? 2 : + state->mouse_buttons & 0x04 ? 3 : 4; + output_mouse(state, button-1 + 0x20, 1, mod, col, row); + } +} + +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) +{ + VTermState *state = vt->state; + + int old_buttons = state->mouse_buttons; + + if(button > 0 && button <= 3) { + if(pressed) + state->mouse_buttons |= (1 << (button-1)); + else + state->mouse_buttons &= ~(1 << (button-1)); + } + + /* Most of the time we don't get button releases from 4/5 */ + if(state->mouse_buttons == old_buttons && button < 4) + return; + + if(button < 4) { + output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row); + } + else if(button < 6) { + output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); + } +} diff --git a/src/parser.c b/src/parser.c index 13bbc21..a01cd71 100644 --- a/src/parser.c +++ b/src/parser.c @@ -3,218 +3,154 @@ #include #include -#define CSI_ARGS_MAX 16 -#define CSI_LEADER_MAX 16 -#define CSI_INTERMED_MAX 16 +#undef DEBUG_PARSER + +static bool is_intermed(unsigned char c) +{ + return c >= 0x20 && c <= 0x2f; +} static void do_control(VTerm *vt, unsigned char control) { - if(vt->parser_callbacks && vt->parser_callbacks->control) - if((*vt->parser_callbacks->control)(control, vt->cbdata)) + if(vt->parser.callbacks && vt->parser.callbacks->control) + if((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) return; - fprintf(stderr, "libvterm: Unhandled control 0x%02x\n", control); + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); } -static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command) +static void do_csi(VTerm *vt, char command) { - int i = 0; - - int leaderlen = 0; - char leader[CSI_LEADER_MAX]; - - // Extract leader bytes 0x3c to 0x3f - for( ; i < arglen; i++) { - if(args[i] < 0x3c || args[i] > 0x3f) - break; - if(leaderlen < CSI_LEADER_MAX-1) - leader[leaderlen++] = args[i]; - } - - leader[leaderlen] = 0; - - int argcount = 1; // Always at least 1 arg - - for( ; i < arglen; i++) - if(args[i] == 0x3b || args[i] == 0x3a) // ; or : - argcount++; - - /* TODO: Consider if these buffers should live in the VTerm struct itself */ - long csi_args[CSI_ARGS_MAX]; - if(argcount > CSI_ARGS_MAX) - argcount = CSI_ARGS_MAX; - - int argi; - for(argi = 0; argi < argcount; argi++) - csi_args[argi] = CSI_ARG_MISSING; - - argi = 0; - for(i = leaderlen; i < arglen && argi < argcount; i++) { - switch(args[i]) { - case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: - case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: - if(csi_args[argi] == CSI_ARG_MISSING) - csi_args[argi] = 0; - csi_args[argi] *= 10; - csi_args[argi] += args[i] - '0'; - break; - case 0x3a: - csi_args[argi] |= CSI_ARG_FLAG_MORE; - /* FALLTHROUGH */ - case 0x3b: - argi++; - break; - default: - goto done_leader; - } +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.csi_leader); + for(int argi = 0; argi < vt->parser.csi_argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.csi_args[argi])); + if(!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi])) + printf("\n"); + printf(" intermed: %s\n", vt->parser.intermed); } -done_leader: ; - - int intermedlen = 0; - char intermed[CSI_INTERMED_MAX]; - - for( ; i < arglen; i++) { - if((args[i] & 0xf0) != 0x20) - break; +#endif + + if(vt->parser.callbacks && vt->parser.callbacks->csi) + if((*vt->parser.callbacks->csi)( + vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL, + vt->parser.csi_args, + vt->parser.csi_argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) + return; - if(intermedlen < CSI_INTERMED_MAX-1) - intermed[intermedlen++] = args[i]; - } + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} - intermed[intermedlen] = 0; +static void do_escape(VTerm *vt, char command) +{ + char seq[INTERMED_MAX+1]; - if(i < arglen) { - fprintf(stderr, "libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i); - } + size_t len = vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); + seq[len++] = command; + seq[len] = 0; - //printf("Parsed CSI args %.*s as:\n", arglen, args); - //printf(" leader: %s\n", leader); - //for(argi = 0; argi < argcount; argi++) { - // printf(" %lu", CSI_ARG(csi_args[argi])); - // if(!CSI_ARG_HAS_MORE(csi_args[argi])) - // printf("\n"); - //printf(" intermed: %s\n", intermed); - //} - - if(vt->parser_callbacks && vt->parser_callbacks->csi) - if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata)) + if(vt->parser.callbacks && vt->parser.callbacks->escape) + if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) return; - fprintf(stderr, "libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command); + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); } static void append_strbuffer(VTerm *vt, const char *str, size_t len) { - if(len > vt->strbuffer_len - vt->strbuffer_cur) { - len = vt->strbuffer_len - vt->strbuffer_cur; - fprintf(stderr, "Truncating strbuffer preserve to %zd bytes\n", len); + if(len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) { + len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur; + DEBUG_LOG("Truncating strbuffer preserve to %zd bytes\n", len); } if(len > 0) { - strncpy(vt->strbuffer + vt->strbuffer_cur, str, len); - vt->strbuffer_cur += len; + strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len); + vt->parser.strbuffer_cur += len; } } -static size_t do_string(VTerm *vt, const char *str_frag, size_t len) +static void start_string(VTerm *vt, VTermParserStringType type) { - if(vt->strbuffer_cur) { - if(str_frag) - append_strbuffer(vt, str_frag, len); - - str_frag = vt->strbuffer; - len = vt->strbuffer_cur; - } - else if(!str_frag) { - fprintf(stderr, "parser.c: TODO: No strbuffer _and_ no final fragment???\n"); - len = 0; - } - - vt->strbuffer_cur = 0; - - size_t eaten; - - switch(vt->parser_state) { - case NORMAL: - if(vt->parser_callbacks && vt->parser_callbacks->text) - if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata))) - return eaten; + vt->parser.stringtype = type; - fprintf(stderr, "libvterm: Unhandled text (%zu chars)\n", len); - return 0; - - case ESC: - if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) { - // C1 emulations using 7bit clean - // ESC 0x40 == 0x80 - do_control(vt, str_frag[0] + 0x40); - return 0; - } + vt->parser.strbuffer_cur = 0; +} - if(vt->parser_callbacks && vt->parser_callbacks->escape) - if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata)) - return 0; +static void more_string(VTerm *vt, const char *str, size_t len) +{ + append_strbuffer(vt, str, len); +} - fprintf(stderr, "libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]); - return 0; +static void done_string(VTerm *vt, const char *str, size_t len) +{ + if(vt->parser.strbuffer_cur) { + if(str) + append_strbuffer(vt, str, len); - case CSI: - do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]); - return 0; + str = vt->parser.strbuffer; + len = vt->parser.strbuffer_cur; + } + else if(!str) { + DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n"); + len = 0; + } - case OSC: - if(vt->parser_callbacks && vt->parser_callbacks->osc) - if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata)) - return 0; + switch(vt->parser.stringtype) { + case VTERM_PARSER_OSC: + if(vt->parser.callbacks && vt->parser.callbacks->osc) + if((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata)) + return; - fprintf(stderr, "libvterm: Unhandled OSC %.*s\n", (int)len, str_frag); - return 0; + DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str); + return; - case DCS: - if(vt->parser_callbacks && vt->parser_callbacks->dcs) - if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata)) - return 0; + case VTERM_PARSER_DCS: + if(vt->parser.callbacks && vt->parser.callbacks->dcs) + if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata)) + return; - fprintf(stderr, "libvterm: Unhandled DCS %.*s\n", (int)len, str_frag); - return 0; + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str); + return; - case ESC_IN_OSC: - case ESC_IN_DCS: - fprintf(stderr, "libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n"); - return 0; + case VTERM_N_PARSER_TYPES: + return; } - - return 0; } -void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) { size_t pos = 0; const char *string_start; - switch(vt->parser_state) { + switch(vt->parser.state) { case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case ESC: string_start = NULL; break; - case ESC: - case ESC_IN_OSC: - case ESC_IN_DCS: - case CSI: - case OSC: - case DCS: + case STRING: + case ESC_IN_STRING: string_start = bytes; break; } -#define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0) -#define ENTER_NORMAL_STATE() do { vt->parser_state = NORMAL; string_start = NULL; } while(0) +#define ENTER_STRING_STATE(st) do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0) +#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) for( ; pos < len; pos++) { unsigned char c = bytes[pos]; if(c == 0x00 || c == 0x7f) { // NUL, DEL - if(vt->parser_state != NORMAL) { - append_strbuffer(vt, string_start, bytes + pos - string_start); + if(vt->parser.state >= STRING) { + more_string(vt, string_start, bytes + pos - string_start); string_start = bytes + pos + 1; } continue; @@ -224,83 +160,131 @@ void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) continue; } else if(c == 0x1b) { // ESC - if(vt->parser_state == OSC) - vt->parser_state = ESC_IN_OSC; - else if(vt->parser_state == DCS) - vt->parser_state = ESC_IN_DCS; + vt->parser.intermedlen = 0; + if(vt->parser.state == STRING) + vt->parser.state = ESC_IN_STRING; else - ENTER_STRING_STATE(ESC); + ENTER_STATE(ESC); continue; } else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state - (vt->parser_state == OSC || vt->parser_state == DCS)) { + vt->parser.state == STRING) { // fallthrough } else if(c < 0x20) { // other C0 - if(vt->parser_state != NORMAL) - append_strbuffer(vt, string_start, bytes + pos - string_start); + if(vt->parser.state >= STRING) + more_string(vt, string_start, bytes + pos - string_start); do_control(vt, c); - if(vt->parser_state != NORMAL) + if(vt->parser.state >= STRING) string_start = bytes + pos + 1; continue; } // else fallthrough - switch(vt->parser_state) { - case ESC_IN_OSC: - case ESC_IN_DCS: + switch(vt->parser.state) { + case ESC_IN_STRING: if(c == 0x5c) { // ST - switch(vt->parser_state) { - case ESC_IN_OSC: vt->parser_state = OSC; break; - case ESC_IN_DCS: vt->parser_state = DCS; break; - default: break; - } - do_string(vt, string_start, bytes + pos - string_start - 1); + vt->parser.state = STRING; + done_string(vt, string_start, bytes + pos - string_start - 1); ENTER_NORMAL_STATE(); break; } - vt->parser_state = ESC; - string_start = bytes + pos; + vt->parser.state = ESC; // else fallthrough case ESC: switch(c) { case 0x50: // DCS - ENTER_STRING_STATE(DCS); + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); break; case 0x5b: // CSI - ENTER_STRING_STATE(CSI); + vt->parser.csi_leaderlen = 0; + ENTER_STATE(CSI_LEADER); break; case 0x5d: // OSC - ENTER_STRING_STATE(OSC); + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); break; default: - if(c >= 0x30 && c < 0x7f) { - /* +1 to pos because we want to include this command byte as well */ - do_string(vt, string_start, bytes + pos - string_start + 1); + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + } + else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) { + do_control(vt, c + 0x40); ENTER_NORMAL_STATE(); } - else if(c >= 0x20 && c < 0x30) { - /* intermediate byte */ + else if(c >= 0x30 && c < 0x7f) { + do_escape(vt, c); + ENTER_NORMAL_STATE(); } else { - fprintf(stderr, "TODO: Unhandled byte %02x in Escape\n", c); + DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); } } break; - case CSI: - if(c >= 0x40 && c <= 0x7f) { - /* +1 to pos because we want to include this command byte as well */ - do_string(vt, string_start, bytes + pos - string_start + 1); - ENTER_NORMAL_STATE(); + case CSI_LEADER: + /* Extract leader bytes 0x3c to 0x3f */ + if(c >= 0x3c && c <= 0x3f) { + if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1) + vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c; + break; + } + + /* else fallthrough */ + vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0; + + vt->parser.csi_argi = 0; + vt->parser.csi_args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + /* fallthrough */ + case CSI_ARGS: + /* Numerical value of argument */ + if(c >= '0' && c <= '9') { + if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING) + vt->parser.csi_args[vt->parser.csi_argi] = 0; + vt->parser.csi_args[vt->parser.csi_argi] *= 10; + vt->parser.csi_args[vt->parser.csi_argi] += c - '0'; + break; + } + if(c == ':') { + vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE; + c = ';'; } + if(c == ';') { + vt->parser.csi_argi++; + vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING; + break; + } + + /* else fallthrough */ + vt->parser.csi_argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + case CSI_INTERMED: + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + break; + } + else if(c == 0x1b) { + /* ESC in CSI cancels */ + } + else if(c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, c); + } + /* else was invalid CSI */ + + ENTER_NORMAL_STATE(); break; - case OSC: - case DCS: + case STRING: if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { - do_string(vt, string_start, bytes + pos - string_start); + done_string(vt, string_start, bytes + pos - string_start); ENTER_NORMAL_STATE(); } break; @@ -309,13 +293,15 @@ void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { switch(c) { case 0x90: // DCS - ENTER_STRING_STATE(DCS); + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); break; case 0x9b: // CSI - ENTER_STRING_STATE(CSI); + ENTER_STATE(CSI_LEADER); break; case 0x9d: // OSC - ENTER_STRING_STATE(OSC); + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); break; default: do_control(vt, c); @@ -323,22 +309,32 @@ void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) } } else { - size_t text_eaten = do_string(vt, bytes + pos, len - pos); - - if(text_eaten == 0) { - string_start = bytes + pos; - goto pause; + size_t eaten = 0; + if(vt->parser.callbacks && vt->parser.callbacks->text) + eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + + if(!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + /* force it to make progress */ + eaten = 1; } - pos += (text_eaten - 1); // we'll ++ it again in a moment + pos += (eaten - 1); // we'll ++ it again in a moment } break; } } -pause: - if(string_start && string_start < len + bytes) { - size_t remaining = len - (string_start - bytes); - append_strbuffer(vt, string_start, remaining); - } + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +{ + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} + +void *vterm_parser_get_cbdata(VTerm *vt) +{ + return vt->parser.cbdata; } diff --git a/src/pen.c b/src/pen.c index fb8c8e3..7488203 100644 --- a/src/pen.c +++ b/src/pen.c @@ -2,7 +2,15 @@ #include -static const VTermColor ansi_colors[] = { +/** + * Structure used to store RGB triples without the additional metadata stored in + * VTermColor. + */ +typedef struct { + uint8_t red, green, blue; +} VTermRGB; + +static const VTermRGB ansi_colors[] = { /* R G B */ { 0, 0, 0 }, // black { 224, 0, 0 }, // red @@ -33,60 +41,75 @@ static int ramp24[] = { 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, }; -static void lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) +static void lookup_default_colour_ansi(long idx, VTermColor *col) +{ + if (idx >= 0 && idx < 16) { + vterm_color_rgb( + col, + ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); + } +} + +static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) { if(index >= 0 && index < 16) { *col = state->colors[index]; + return true; } + + return false; } -static void lookup_colour_palette(const VTermState *state, long index, VTermColor *col) +static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) { if(index >= 0 && index < 16) { // Normal 8 colours or high intensity - parse as palette 0 - lookup_colour_ansi(state, index, col); + return lookup_colour_ansi(state, index, col); } else if(index >= 16 && index < 232) { // 216-colour cube index -= 16; - col->blue = ramp6[index % 6]; - col->green = ramp6[index/6 % 6]; - col->red = ramp6[index/6/6 % 6]; + vterm_color_rgb(col, ramp6[index/6/6 % 6], + ramp6[index/6 % 6], + ramp6[index % 6]); + + return true; } else if(index >= 232 && index < 256) { // 24 greyscales index -= 232; - col->red = ramp24[index]; - col->green = ramp24[index]; - col->blue = ramp24[index]; + vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); + + return true; } + + return false; } -static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col, int *index) +static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) { switch(palette) { case 2: // RGB mode - 3 args contain colour values directly if(argcount < 3) return argcount; - col->red = CSI_ARG(args[0]); - col->green = CSI_ARG(args[1]); - col->blue = CSI_ARG(args[2]); + vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); return 3; case 5: // XTerm 256-colour mode - if(index) - *index = CSI_ARG_OR(args[0], -1); + if (!argcount || CSI_ARG_IS_MISSING(args[0])) { + return argcount ? 1 : 0; + } - lookup_colour_palette(state, argcount ? CSI_ARG_OR(args[0], -1) : -1, col); + vterm_color_indexed(col, args[0]); return argcount ? 1 : 0; default: - fprintf(stderr, "Unrecognised colour palette %d\n", palette); + DEBUG_LOG("Unrecognised colour palette %d\n", palette); return 0; } } @@ -97,7 +120,7 @@ static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, V { #ifdef DEBUG if(type != vterm_get_attr_type(attr)) { - fprintf(stderr, "Cannot set attr %d as it has type %d, not type %d\n", + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", attr, vterm_get_attr_type(attr), type); return; } @@ -128,7 +151,7 @@ static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) { VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; - lookup_colour_ansi(state, col, colp); + vterm_color_indexed(colp, col); setpenattr_col(state, attr, *colp); } @@ -136,11 +159,12 @@ static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) INTERNAL void vterm_state_newpen(VTermState *state) { // 90% grey so that pure white is brighter - state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240; - state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0; + vterm_color_rgb(&state->default_fg, 240, 240, 240); + vterm_color_rgb(&state->default_bg, 0, 0, 0); + vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); for(int col = 0; col < 16; col++) - state->colors[col] = ansi_colors[col]; + lookup_default_colour_ansi(col, &state->colors[col]); } INTERNAL void vterm_state_resetpen(VTermState *state) @@ -153,8 +177,6 @@ INTERNAL void vterm_state_resetpen(VTermState *state) state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); state->pen.font = 0; setpenattr_int( state, VTERM_ATTR_FONT, 0); - state->fg_index = -1; - state->bg_index = -1; state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); } @@ -179,6 +201,26 @@ INTERNAL void vterm_state_savepen(VTermState *state, int save) } } +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) +{ + /* First make sure that the two colours are of the same type (RGB/Indexed) */ + if (a->type != b->type) { + return false; + } + + /* Depending on the type inspect the corresponding members */ + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } + else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) + && (a->rgb.green == b->rgb.green) + && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} + void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) { *default_fg = state->default_fg; @@ -192,8 +234,15 @@ void vterm_state_get_palette_color(const VTermState *state, int index, VTermColo void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) { + /* Copy the given colors */ state->default_fg = *default_fg; state->default_bg = *default_bg; + + /* Make sure the correct type flags are set */ + state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_FG; + state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_BG; } void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) @@ -202,6 +251,14 @@ void vterm_state_set_palette_color(VTermState *state, int index, const VTermColo state->colors[index] = *col; } +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) +{ + if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ + lookup_colour_palette(state, col->indexed.idx, col); + } + col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ +} + void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) { state->bold_is_highbright = bold_is_highbright; @@ -226,12 +283,14 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco vterm_state_resetpen(state); break; - case 1: // Bold on + case 1: { // Bold on + const VTermColor *fg = &state->pen.fg; state->pen.bold = 1; setpenattr_bool(state, VTERM_ATTR_BOLD, 1); - if(state->fg_index > -1 && state->fg_index < 8 && state->bold_is_highbright) - set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_index + (state->pen.bold ? 8 : 0)); + if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); break; + } case 3: // Italic on state->pen.italic = 1; @@ -302,22 +361,19 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: // Foreground colour palette value = CSI_ARG(args[argi]) - 30; - state->fg_index = value; if(state->pen.bold && state->bold_is_highbright) value += 8; set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); break; case 38: // Foreground colour alternative palette - state->fg_index = -1; if(argcount - argi < 1) return; - argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg, &state->fg_index); + argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg); setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); break; case 39: // Foreground colour default - state->fg_index = -1; state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); break; @@ -325,20 +381,17 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: // Background colour palette value = CSI_ARG(args[argi]) - 40; - state->bg_index = value; set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); break; case 48: // Background colour alternative palette - state->bg_index = -1; if(argcount - argi < 1) return; - argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg, &state->bg_index); + argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg); setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); break; case 49: // Default background - state->bg_index = -1; state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); break; @@ -346,14 +399,12 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette value = CSI_ARG(args[argi]) - 90 + 8; - state->fg_index = value; set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); break; case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: // Background colour high-intensity palette value = CSI_ARG(args[argi]) - 100 + 8; - state->bg_index = value; set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); break; @@ -363,12 +414,45 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco } if(!done) - fprintf(stderr, "libvterm: Unhandled CSI SGR %lu\n", arg); + DEBUG_LOG("libvterm: Unhandled CSI SGR %lu\n", arg); while(CSI_ARG_HAS_MORE(args[argi++])); } } +static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) +{ + /* Do nothing if the given color is the default color */ + if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || + (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { + return argi; + } + + /* Decide whether to send an indexed color or an RGB color */ + if (VTERM_COLOR_IS_INDEXED(col)) { + const uint8_t idx = col->indexed.idx; + if (idx < 8) { + args[argi++] = (idx + (fg ? 30 : 40)); + } + else if (idx < 16) { + args[argi++] = (idx - 8 + (fg ? 90 : 100)); + } + else { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 5; + args[argi++] = idx; + } + } + else if (VTERM_COLOR_IS_RGB(col)) { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 2; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; + args[argi++] = col->rgb.blue; + } + return argi; +} + INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) { int argi = 0; @@ -397,25 +481,9 @@ INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) if(state->pen.underline == 2) args[argi++] = 21; - if(state->fg_index >= 0 && state->fg_index < 8) - args[argi++] = 30 + state->fg_index; - else if(state->fg_index >= 8 && state->fg_index < 16) - args[argi++] = 90 + state->fg_index - 8; - else if(state->fg_index >= 16 && state->fg_index < 256) { - args[argi++] = CSI_ARG_FLAG_MORE|38; - args[argi++] = CSI_ARG_FLAG_MORE|5; - args[argi++] = state->fg_index; - } + argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); - if(state->bg_index >= 0 && state->bg_index < 8) - args[argi++] = 40 + state->bg_index; - else if(state->bg_index >= 8 && state->bg_index < 16) - args[argi++] = 100 + state->bg_index - 8; - else if(state->bg_index >= 16 && state->bg_index < 256) { - args[argi++] = CSI_ARG_FLAG_MORE|48; - args[argi++] = CSI_ARG_FLAG_MORE|5; - args[argi++] = state->bg_index; - } + argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); return argi; } @@ -458,6 +526,9 @@ int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue case VTERM_ATTR_BACKGROUND: val->color = state->pen.bg; return 1; + + case VTERM_N_ATTRS: + return 0; } return 0; diff --git a/src/screen.c b/src/screen.c index c4de59e..1d4d86c 100644 --- a/src/screen.c +++ b/src/screen.c @@ -149,7 +149,7 @@ static void damagerect(VTermScreen *screen, VTermRect rect) return; default: - fprintf(stderr, "TODO: Maybe merge damage for level %d\n", screen->damage_merge); + DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); return; } @@ -266,7 +266,7 @@ static int erase_internal(VTermRect rect, int selective, void *user) { VTermScreen *screen = user; - for(int row = rect.start_row; row < rect.end_row; row++) { + for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); for(int col = rect.start_col; col < rect.end_col; col++) { @@ -304,10 +304,10 @@ static int scrollrect(VTermRect rect, int downward, int rightward, void *user) { VTermScreen *screen = user; - vterm_scroll_rect(rect, downward, rightward, - moverect_internal, erase_internal, screen); - if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + vterm_screen_flush_damage(screen); vterm_scroll_rect(rect, downward, rightward, @@ -340,10 +340,14 @@ static int scrollrect(VTermRect rect, int downward, int rightward, void *user) screen->pending_scroll_rightward = rightward; } + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + if(screen->damaged.start_row == -1) return 1; if(rect_contains(&rect, &screen->damaged)) { + /* Scroll region entirely contains the damage; just move it */ vterm_rect_move(&screen->damaged, -downward, -rightward); rect_clip(&screen->damaged, &rect); } @@ -373,7 +377,7 @@ static int scrollrect(VTermRect rect, int downward, int rightward, void *user) } } else { - fprintf(stderr, "TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", + DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", ARGSrect(screen->damaged), ARGSrect(rect)); } @@ -422,6 +426,9 @@ static int setpenattr(VTermAttr attr, VTermValue *val, void *user) case VTERM_ATTR_BACKGROUND: screen->pen.bg = val->color; return 1; + + case VTERM_N_ATTRS: + return 0; } return 0; @@ -457,16 +464,6 @@ static int settermprop(VTermProp prop, VTermValue *val, void *user) return 1; } -static int setmousefunc(VTermMouseFunc func, void *data, void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->setmousefunc) - return (*screen->callbacks->setmousefunc)(func, data, screen->cbdata); - - return 0; -} - static int bell(void *user) { VTermScreen *screen = user; @@ -490,8 +487,10 @@ static int resize(int new_rows, int new_cols, VTermPos *delta, void *user) // Fewer rows - determine if we're going to scroll at all, and if so, push // those lines to scrollback VTermPos pos = { 0, 0 }; + VTermPos cursor = screen->state->pos; + // Find the first blank row after the cursor. for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--) - if(!vterm_screen_is_eol(screen, pos)) + if(!vterm_screen_is_eol(screen, pos) || cursor.row == pos.row) break; int first_blank_row = pos.row + 1; @@ -609,16 +608,15 @@ static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInf } static VTermStateCallbacks state_cbs = { - .putglyph = &putglyph, - .movecursor = &movecursor, - .scrollrect = &scrollrect, - .erase = &erase, - .setpenattr = &setpenattr, - .settermprop = &settermprop, - .setmousefunc = &setmousefunc, - .bell = &bell, - .resize = &resize, - .setlineinfo = &setlineinfo, + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .bell = &bell, + .resize = &resize, + .setlineinfo = &setlineinfo, }; static VTermScreen *screen_new(VTerm *vt) @@ -642,6 +640,9 @@ static VTermScreen *screen_new(VTerm *vt) screen->rows = rows; screen->cols = cols; + screen->callbacks = NULL; + screen->cbdata = NULL; + screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols); screen->buffer = screen->buffers[0]; @@ -839,6 +840,21 @@ void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks screen->cbdata = user; } +void *vterm_screen_get_cbdata(VTermScreen *screen) +{ + return screen->cbdata; +} + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user) +{ + vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); +} + +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) +{ + return vterm_state_get_unrecognised_fbdata(screen->state); +} + void vterm_screen_flush_damage(VTermScreen *screen) { if(screen->pending_scrollrect.start_row != -1) { @@ -878,9 +894,9 @@ static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) return 1; if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) return 1; - if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_equal(a->pen.fg, b->pen.fg)) + if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) return 1; - if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_equal(a->pen.bg, b->pen.bg)) + if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) return 1; return 0; @@ -913,3 +929,8 @@ int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, return 1; } + +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) +{ + vterm_state_convert_color_to_rgb(screen->state, col); +} diff --git a/src/state.c b/src/state.c index e42be53..68cc4f6 100644 --- a/src/state.c +++ b/src/state.c @@ -5,16 +5,10 @@ #define strneq(a,b,n) (strncmp(a,b,n)==0) -#include "utf8.h" - #if defined(DEBUG) && DEBUG > 1 # define DEBUG_GLYPH_COMBINE #endif -#define MOUSE_WANT_CLICK 0x01 -#define MOUSE_WANT_DRAG 0x02 -#define MOUSE_WANT_MOVE 0x04 - /* Some convenient wrappers to make callback functions easier */ static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) @@ -31,7 +25,7 @@ static void putglyph(VTermState *state, const uint32_t chars[], int width, VTerm if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) return; - fprintf(stderr, "libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); + DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); } static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) @@ -63,6 +57,15 @@ static VTermState *vterm_state_new(VTerm *vt) state->rows = vt->rows; state->cols = vt->cols; + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_protocol = MOUSE_X10; + + state->callbacks = NULL; + state->cbdata = NULL; + vterm_state_newpen(state); state->bold_is_highbright = 0; @@ -83,6 +86,18 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar if(!downward && !rightward) return; + int rows = rect.end_row - rect.start_row; + if(downward > rows) + downward = rows; + else if(downward < -rows) + downward = -rows; + + int cols = rect.end_col - rect.start_col; + if(rightward > cols) + rightward = cols; + else if(rightward < -cols) + rightward = -cols; + // Update lineinfo if full line if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { int height = rect.end_row - rect.start_row - abs(downward); @@ -153,15 +168,37 @@ static int is_col_tabstop(VTermState *state, int col) return state->tabstops[col >> 3] & mask; } +static int is_cursor_in_scrollregion(const VTermState *state) +{ + if(state->pos.row < state->scrollregion_top || + state->pos.row >= SCROLLREGION_BOTTOM(state)) + return 0; + if(state->pos.col < SCROLLREGION_LEFT(state) || + state->pos.col >= SCROLLREGION_RIGHT(state)) + return 0; + + return 1; +} + static void tab(VTermState *state, int count, int direction) { - while(count--) - while(state->pos.col >= 0 && state->pos.col < THISROWWIDTH(state)-1) { - state->pos.col += direction; + while(count > 0) { + if(direction > 0) { + if(state->pos.col >= THISROWWIDTH(state)-1) + return; - if(is_col_tabstop(state, state->pos.col)) - break; + state->pos.col++; } + else if(direction < 0) { + if(state->pos.col < 1) + return; + + state->pos.col--; + } + + if(is_col_tabstop(state, state->pos.col)) + count--; + } } #define NO_FORCE 0 @@ -219,6 +256,12 @@ static int on_text(const char bytes[], size_t len, void *user) codepoints, &npoints, state->gsingle_set ? 1 : len, bytes, &eaten, len); + /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet + * for even a single codepoint + */ + if(!npoints) + return eaten; + if(state->gsingle_set && npoints) state->gsingle_set = 0; @@ -262,7 +305,7 @@ static int on_text(const char bytes[], size_t len, void *user) putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); } else { - fprintf(stderr, "libvterm: TODO: Skip over split char+combining\n"); + DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); } } @@ -280,7 +323,14 @@ static int on_text(const char bytes[], size_t len, void *user) for( ; i < glyph_ends; i++) { chars[i - glyph_starts] = codepoints[i]; - width += vterm_unicode_width(codepoints[i]); + int this_width = vterm_unicode_width(codepoints[i]); +#ifdef DEBUG + if(this_width < 0) { + fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]); + abort(); + } +#endif + width += this_width; } chars[glyph_ends - glyph_starts] = 0; @@ -343,6 +393,15 @@ static int on_text(const char bytes[], size_t len, void *user) updatecursor(state, &oldpos, 0); +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", + state->pos.row, state->pos.col); + abort(); + } +#endif + return eaten; } @@ -424,102 +483,25 @@ static int on_control(unsigned char control, void *user) break; default: + if(state->fallbacks && state->fallbacks->control) + if((*state->fallbacks->control)(control, state->fbdata)) + return 1; + return 0; } updatecursor(state, &oldpos, 1); - return 1; -} - -static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) -{ - modifiers <<= 2; - - switch(state->mouse_protocol) { - case MOUSE_X10: - if(col + 0x21 > 0xff) - col = 0xff - 0x21; - if(row + 0x21 > 0xff) - row = 0xff - 0x21; - - if(!pressed) - code = 3; - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", - (code | modifiers) + 0x20, col + 0x21, row + 0x21); - break; - - case MOUSE_UTF8: - { - char utf8[18]; size_t len = 0; - - if(!pressed) - code = 3; - - len += fill_utf8((code | modifiers) + 0x20, utf8 + len); - len += fill_utf8(col + 0x21, utf8 + len); - len += fill_utf8(row + 0x21, utf8 + len); - utf8[len] = 0; - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); - } - break; - - case MOUSE_SGR: - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", - code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); - break; - - case MOUSE_RXVT: - if(!pressed) - code = 3; - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", - code | modifiers, col + 1, row + 1); - break; - } -} - -static void mousefunc(int col, int row, int button, int pressed, int modifiers, void *data) -{ - VTermState *state = data; - - int old_col = state->mouse_col; - int old_row = state->mouse_row; - int old_buttons = state->mouse_buttons; - - state->mouse_col = col; - state->mouse_row = row; - - if(button > 0 && button <= 3) { - if(pressed) - state->mouse_buttons |= (1 << (button-1)); - else - state->mouse_buttons &= ~(1 << (button-1)); +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", + control, state->pos.row, state->pos.col); + abort(); } +#endif - modifiers &= 0x7; - - - /* Most of the time we don't get button releases from 4/5 */ - if(state->mouse_buttons != old_buttons || button >= 4) { - if(button < 4) { - output_mouse(state, button-1, pressed, modifiers, col, row); - } - else if(button < 6) { - output_mouse(state, button-4 + 0x40, pressed, modifiers, col, row); - } - } - else if(col != old_col || row != old_row) { - if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || - (state->mouse_flags & MOUSE_WANT_MOVE)) { - int button = state->mouse_buttons & 0x01 ? 1 : - state->mouse_buttons & 0x02 ? 2 : - state->mouse_buttons & 0x04 ? 3 : 4; - output_mouse(state, button-1 + 0x20, 1, modifiers, col, row); - } - } + return 1; } static int settermprop_bool(VTermState *state, VTermProp prop, int v) @@ -722,7 +704,7 @@ static void set_mode(VTermState *state, int num, int val) break; default: - fprintf(stderr, "libvterm: Unknown mode %d\n", num); + DEBUG_LOG("libvterm: Unknown mode %d\n", num); return; } } @@ -774,26 +756,15 @@ static void set_dec_mode(VTermState *state, int num, int val) case 1000: case 1002: case 1003: - if(val) { - state->mouse_col = 0; - state->mouse_row = 0; - state->mouse_buttons = 0; - - state->mouse_flags = MOUSE_WANT_CLICK; - state->mouse_protocol = MOUSE_X10; - - if(num == 1002) - state->mouse_flags |= MOUSE_WANT_DRAG; - if(num == 1003) - state->mouse_flags |= MOUSE_WANT_MOVE; - } - else { - state->mouse_flags = 0; - } - - if(state->callbacks && state->callbacks->setmousefunc) - (*state->callbacks->setmousefunc)(val ? mousefunc : NULL, state, state->cbdata); + settermprop_int(state, VTERM_PROP_MOUSE, + !val ? VTERM_PROP_MOUSE_NONE : + (num == 1000) ? VTERM_PROP_MOUSE_CLICK : + (num == 1002) ? VTERM_PROP_MOUSE_DRAG : + VTERM_PROP_MOUSE_MOVE); + break; + case 1004: + state->mode.report_focus = val; break; case 1005: @@ -821,8 +792,12 @@ static void set_dec_mode(VTermState *state, int num, int val) savecursor(state, val); break; + case 2004: + state->mode.bracketpaste = val; + break; + default: - fprintf(stderr, "libvterm: Unknown DEC mode %d\n", num); + DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); return; } } @@ -872,6 +847,10 @@ static void request_dec_mode(VTermState *state, int num) reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); break; + case 1004: + reply = state->mode.report_focus; + break; + case 1005: reply = state->mouse_protocol == MOUSE_UTF8; break; @@ -888,6 +867,10 @@ static void request_dec_mode(VTermState *state, int num) reply = state->mode.alt_screen; break; + case 2004: + reply = state->mode.bracketpaste; + break; + default: vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); return; @@ -901,6 +884,7 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha VTermState *state = user; int leader_byte = 0; int intermed_byte = 0; + int cancel_phantom = 1; if(leader && leader[0]) { if(leader[1]) // longer than 1 char @@ -950,6 +934,9 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha case 0x40: // ICH - ECMA-48 8.3.64 count = CSI_ARG_COUNT(args[0]); + if(!is_cursor_in_scrollregion(state)) + break; + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.start_col = state->pos.col; @@ -1093,6 +1080,9 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha case 0x4c: // IL - ECMA-48 8.3.67 count = CSI_ARG_COUNT(args[0]); + if(!is_cursor_in_scrollregion(state)) + break; + rect.start_row = state->pos.row; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = SCROLLREGION_LEFT(state); @@ -1105,6 +1095,9 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha case 0x4d: // DL - ECMA-48 8.3.32 count = CSI_ARG_COUNT(args[0]); + if(!is_cursor_in_scrollregion(state)) + break; + rect.start_row = state->pos.row; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = SCROLLREGION_LEFT(state); @@ -1117,6 +1110,9 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha case 0x50: // DCH - ECMA-48 8.3.26 count = CSI_ARG_COUNT(args[0]); + if(!is_cursor_in_scrollregion(state)) + break; + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; rect.start_col = state->pos.col; @@ -1182,6 +1178,24 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha state->at_phantom = 0; break; + case 0x62: { // REP - ECMA-48 8.3.103 + const int row_width = THISROWWIDTH(state); + count = CSI_ARG_COUNT(args[0]); + col = state->pos.col + count; + UBOUND(col, row_width); + while (state->pos.col < col) { + putglyph(state, state->combine_chars, state->combine_width, state->pos); + state->pos.col += state->combine_width; + } + if (state->pos.col + state->combine_width >= row_width) { + if (state->mode.autowrap) { + state->at_phantom = 1; + cancel_phantom = 0; + } + } + break; + } + case 0x63: // DA - ECMA-48 8.3.24 val = CSI_ARG_OR(args[0], 0); if(val == 0) @@ -1357,7 +1371,7 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha case 0x72: // DECSTBM - DEC custom state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); - LBOUND(state->scrollregion_top, -1); + LBOUND(state->scrollregion_top, 0); UBOUND(state->scrollregion_top, state->rows); LBOUND(state->scrollregion_bottom, -1); if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) @@ -1365,13 +1379,19 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha else UBOUND(state->scrollregion_bottom, state->rows); + if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + // Invalid + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + } + break; case 0x73: // DECSLRM - DEC custom // Always allow setting these margins, just they won't take effect without DECVSSM state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); - LBOUND(state->scrollregion_left, -1); + LBOUND(state->scrollregion_left, 0); UBOUND(state->scrollregion_left, state->cols); LBOUND(state->scrollregion_right, -1); if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) @@ -1379,11 +1399,21 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha else UBOUND(state->scrollregion_right, state->cols); + if(state->scrollregion_right > -1 && + state->scrollregion_right <= state->scrollregion_left) { + // Invalid + state->scrollregion_left = 0; + state->scrollregion_right = -1; + } + break; case INTERMED('\'', 0x7D): // DECIC count = CSI_ARG_COUNT(args[0]); + if(!is_cursor_in_scrollregion(state)) + break; + rect.start_row = state->scrollregion_top; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = state->pos.col; @@ -1396,6 +1426,9 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha case INTERMED('\'', 0x7E): // DECDC count = CSI_ARG_COUNT(args[0]); + if(!is_cursor_in_scrollregion(state)) + break; + rect.start_row = state->scrollregion_top; rect.end_row = SCROLLREGION_BOTTOM(state); rect.start_col = state->pos.col; @@ -1406,12 +1439,16 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha break; default: + if(state->fallbacks && state->fallbacks->csi) + if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) + return 1; + return 0; } if(state->mode.origin) { LBOUND(state->pos.row, state->scrollregion_top); - UBOUND(state->pos.row, state->scrollregion_bottom-1); + UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1); LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); } @@ -1422,7 +1459,28 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha UBOUND(state->pos.col, THISROWWIDTH(state)-1); } - updatecursor(state, &oldpos, 1); + updatecursor(state, &oldpos, cancel_phantom); + +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", + command, state->pos.row, state->pos.col); + abort(); + } + + if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); + abort(); + } + + if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { + fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); + abort(); + } +#endif return 1; } @@ -1447,6 +1505,9 @@ static int on_osc(const char *command, size_t cmdlen, void *user) settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); return 1; } + else if(state->fallbacks && state->fallbacks->osc) + if((*state->fallbacks->osc)(command, cmdlen, state->fbdata)) + return 1; return 0; } @@ -1508,6 +1569,9 @@ static int on_dcs(const char *command, size_t cmdlen, void *user) request_status_string(state, command+2, cmdlen-2); return 1; } + else if(state->fallbacks && state->fallbacks->dcs) + if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata)) + return 1; return 0; } @@ -1563,6 +1627,11 @@ static int on_resize(int rows, int cols, void *user) state->rows = rows; state->cols = cols; + if(state->scrollregion_bottom > -1) + UBOUND(state->scrollregion_bottom, state->rows); + if(state->scrollregion_right > -1) + UBOUND(state->scrollregion_right, state->cols); + VTermPos delta = { 0, 0 }; if(state->callbacks && state->callbacks->resize) @@ -1615,7 +1684,7 @@ VTermState *vterm_obtain_state(VTerm *vt) if(*state->encoding_utf8.enc->init) (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); - vterm_set_parser_callbacks(vt, &parser_callbacks, state); + vterm_parser_set_callbacks(vt, &parser_callbacks, state); return state; } @@ -1635,6 +1704,8 @@ void vterm_state_reset(VTermState *state, int hard) state->mode.alt_screen = 0; state->mode.origin = 0; state->mode.leftrightmargin = 0; + state->mode.bracketpaste = 0; + state->mode.report_focus = 0; state->vt->mode.ctrl8bit = 0; @@ -1703,6 +1774,28 @@ void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *cal } } +void *vterm_state_get_cbdata(VTermState *state) +{ + return state->cbdata; +} + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user) +{ + if(fallbacks) { + state->fallbacks = fallbacks; + state->fbdata = user; + } + else { + state->fallbacks = NULL; + state->fbdata = NULL; + } +} + +void *vterm_state_get_unrecognised_fbdata(VTermState *state) +{ + return state->fbdata; +} + int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) { /* Only store the new value of the property if usercode said it was happy. @@ -1740,11 +1833,35 @@ int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) erase(state, rect, 0); } return 1; + case VTERM_PROP_MOUSE: + state->mouse_flags = 0; + if(val->number) + state->mouse_flags |= MOUSE_WANT_CLICK; + if(val->number == VTERM_PROP_MOUSE_DRAG) + state->mouse_flags |= MOUSE_WANT_DRAG; + if(val->number == VTERM_PROP_MOUSE_MOVE) + state->mouse_flags |= MOUSE_WANT_MOVE; + return 1; + + case VTERM_N_PROPS: + return 0; } return 0; } +void vterm_state_focus_in(VTermState *state) +{ + if(state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); +} + +void vterm_state_focus_out(VTermState *state) +{ + if(state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); +} + const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) { return state->lineinfo + row; diff --git a/src/unicode.c b/src/unicode.c index 69b7682..0d1b5ff 100644 --- a/src/unicode.c +++ b/src/unicode.c @@ -67,8 +67,6 @@ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c */ -#include - struct interval { int first; int last; @@ -129,7 +127,7 @@ static const struct interval combining[] = { /* auxiliary function for binary search in interval table */ -static int bisearch(wchar_t ucs, const struct interval *table, int max) { +static int bisearch(uint32_t ucs, const struct interval *table, int max) { int min = 0; int mid; @@ -177,12 +175,12 @@ static int bisearch(wchar_t ucs, const struct interval *table, int max) { * ISO 8859-1 and WGL4 characters, Unicode control characters, * etc.) have a column width of 1. * - * This implementation assumes that wchar_t characters are encoded + * This implementation assumes that uint32_t characters are encoded * in ISO 10646. */ -static int mk_wcwidth(wchar_t ucs) +static int mk_wcwidth(uint32_t ucs) { /* test for 8-bit control characters */ if (ucs == 0) @@ -214,7 +212,7 @@ static int mk_wcwidth(wchar_t ucs) } -static int mk_wcswidth(const wchar_t *pwcs, size_t n) +static int mk_wcswidth(const uint32_t *pwcs, size_t n) { int w, width = 0; @@ -237,7 +235,7 @@ static int mk_wcswidth(const wchar_t *pwcs, size_t n) * the traditional terminal character-width behaviour. It is not * otherwise recommended for general use. */ -static int mk_wcwidth_cjk(wchar_t ucs) +static int mk_wcwidth_cjk(uint32_t ucs) { /* sorted list of non-overlapping intervals of East Asian Ambiguous * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ @@ -305,7 +303,7 @@ static int mk_wcwidth_cjk(wchar_t ucs) } -static int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) +static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n) { int w, width = 0; @@ -321,12 +319,19 @@ static int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) // ################################ // ### The rest added by Paul Evans -INTERNAL int vterm_unicode_width(int codepoint) +static const struct interval fullwidth[] = { +#include "fullwidth.inc" +}; + +INTERNAL int vterm_unicode_width(uint32_t codepoint) { + if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1)) + return 2; + return mk_wcwidth(codepoint); } -INTERNAL int vterm_unicode_is_combining(int codepoint) +INTERNAL int vterm_unicode_is_combining(uint32_t codepoint) { return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1); } diff --git a/src/vterm.c b/src/vterm.c index 04651d3..843bb47 100644 --- a/src/vterm.c +++ b/src/vterm.c @@ -43,11 +43,14 @@ VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *fun vt->rows = rows; vt->cols = cols; - vt->parser_state = NORMAL; + vt->parser.state = NORMAL; - vt->strbuffer_len = 64; - vt->strbuffer_cur = 0; - vt->strbuffer = vterm_allocator_malloc(vt, vt->strbuffer_len); + vt->parser.callbacks = NULL; + vt->parser.cbdata = NULL; + + vt->parser.strbuffer_len = 64; + vt->parser.strbuffer_cur = 0; + vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len); vt->outbuffer_len = 64; vt->outbuffer_cur = 0; @@ -64,7 +67,7 @@ void vterm_free(VTerm *vt) if(vt->state) vterm_state_free(vt->state); - vterm_allocator_free(vt, vt->strbuffer); + vterm_allocator_free(vt, vt->parser.strbuffer); vterm_allocator_free(vt, vt->outbuffer); vterm_allocator_free(vt, vt); @@ -93,17 +96,16 @@ void vterm_set_size(VTerm *vt, int rows, int cols) vt->rows = rows; vt->cols = cols; - if(vt->parser_callbacks && vt->parser_callbacks->resize) - (*vt->parser_callbacks->resize)(rows, cols, vt->cbdata); + if(vt->parser.callbacks && vt->parser.callbacks->resize) + (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); } -void vterm_set_parser_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +int vterm_get_utf8(const VTerm *vt) { - vt->parser_callbacks = callbacks; - vt->cbdata = user; + return vt->mode.utf8; } -void vterm_parser_set_utf8(VTerm *vt, int is_utf8) +void vterm_set_utf8(VTerm *vt, int is_utf8) { vt->mode.utf8 = is_utf8; } @@ -111,7 +113,7 @@ void vterm_parser_set_utf8(VTerm *vt, int is_utf8) INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) { if(len > vt->outbuffer_len - vt->outbuffer_cur) { - fprintf(stderr, "vterm_push_output(): buffer overflow; truncating output\n"); + DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n"); len = vt->outbuffer_len - vt->outbuffer_cur; } @@ -119,12 +121,28 @@ INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) vt->outbuffer_cur += len; } +static int outbuffer_is_full(VTerm *vt) +{ + return vt->outbuffer_cur >= vt->outbuffer_len - 1; +} + INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) { + if(outbuffer_is_full(vt)) { + DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n"); + return; + } + int written = vsnprintf(vt->outbuffer + vt->outbuffer_cur, vt->outbuffer_len - vt->outbuffer_cur, format, args); - vt->outbuffer_cur += written; + + if(written == vt->outbuffer_len - vt->outbuffer_cur) { + /* output was truncated */ + vt->outbuffer_cur = vt->outbuffer_len - 1; + } + else + vt->outbuffer_cur += written; } INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) @@ -137,8 +155,10 @@ INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) { + size_t orig_cur = vt->outbuffer_cur; + if(ctrl >= 0x80 && !vt->mode.ctrl8bit) - vterm_push_output_sprintf(vt, "\e%c", ctrl - 0x40); + vterm_push_output_sprintf(vt, ESC_S "%c", ctrl - 0x40); else vterm_push_output_sprintf(vt, "%c", ctrl); @@ -146,12 +166,17 @@ INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, cons va_start(args, fmt); vterm_push_output_vsprintf(vt, fmt, args); va_end(args); + + if(outbuffer_is_full(vt)) + vt->outbuffer_cur = orig_cur; } INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...) { + size_t orig_cur = vt->outbuffer_cur; + if(!vt->mode.ctrl8bit) - vterm_push_output_sprintf(vt, "\e%c", C1_DCS - 0x40); + vterm_push_output_sprintf(vt, ESC_S "%c", C1_DCS - 0x40); else vterm_push_output_sprintf(vt, "%c", C1_DCS); @@ -161,11 +186,9 @@ INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...) va_end(args); vterm_push_output_sprintf_ctrl(vt, C1_ST, ""); -} -size_t vterm_output_bufferlen(VTerm *vt) -{ - return vterm_output_get_buffer_current(vt); + if(outbuffer_is_full(vt)) + vt->outbuffer_cur = orig_cur; } size_t vterm_output_get_buffer_size(const VTerm *vt) @@ -183,7 +206,7 @@ size_t vterm_output_get_buffer_remaining(const VTerm *vt) return vt->outbuffer_len - vt->outbuffer_cur; } -size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len) +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) { if(len > vt->outbuffer_cur) len = vt->outbuffer_cur; @@ -210,6 +233,8 @@ VTermValueType vterm_get_attr_type(VTermAttr attr) case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; + + case VTERM_N_ATTRS: return 0; } return 0; /* UNREACHABLE */ } @@ -224,6 +249,9 @@ VTermValueType vterm_get_prop_type(VTermProp prop) case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; + + case VTERM_N_PROPS: return 0; } return 0; /* UNREACHABLE */ } diff --git a/src/vterm_internal.h b/src/vterm_internal.h index 4bc8e6b..363faee 100644 --- a/src/vterm_internal.h +++ b/src/vterm_internal.h @@ -11,6 +11,19 @@ # define INTERNAL #endif +#ifdef DEBUG +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_LOG(...) +#endif + +#define ESC_S "\x1b" + +#define INTERMED_MAX 16 + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 + typedef struct VTermEncoding VTermEncoding; typedef struct { @@ -33,11 +46,6 @@ struct VTermPen unsigned int font:4; /* To store 0-9 */ }; -static inline int vterm_color_equal(VTermColor a, VTermColor b) -{ - return a.red == b.red && a.green == b.green && a.blue == b.blue; -} - struct VTermState { VTerm *vt; @@ -45,6 +53,9 @@ struct VTermState const VTermStateCallbacks *callbacks; void *cbdata; + const VTermParserCallbacks *fallbacks; + void *fbdata; + int rows; int cols; @@ -72,6 +83,10 @@ struct VTermState int mouse_col, mouse_row; int mouse_buttons; int mouse_flags; +#define MOUSE_WANT_CLICK 0x01 +#define MOUSE_WANT_DRAG 0x02 +#define MOUSE_WANT_MOVE 0x04 + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; /* Last glyph output, for Unicode recombining purposes */ @@ -81,18 +96,20 @@ struct VTermState VTermPos combine_pos; // Position before movement struct { - int keypad:1; - int cursor:1; - int autowrap:1; - int insert:1; - int newline:1; - int cursor_visible:1; - int cursor_blink:1; + unsigned int keypad:1; + unsigned int cursor:1; + unsigned int autowrap:1; + unsigned int insert:1; + unsigned int newline:1; + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; unsigned int cursor_shape:2; - int alt_screen:1; - int origin:1; - int screen:1; - int leftrightmargin:1; + unsigned int alt_screen:1; + unsigned int origin:1; + unsigned int screen:1; + unsigned int leftrightmargin:1; + unsigned int bracketpaste:1; + unsigned int report_focus:1; } mode; VTermEncodingInstance encoding[4], encoding_utf8; @@ -104,8 +121,6 @@ struct VTermState VTermColor default_bg; VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only - int fg_index; - int bg_index; int bold_is_highbright; unsigned int protected_cell : 1; @@ -116,13 +131,20 @@ struct VTermState struct VTermPen pen; struct { - int cursor_visible:1; - int cursor_blink:1; + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; unsigned int cursor_shape:2; } mode; } saved; }; +typedef enum { + VTERM_PARSER_OSC, + VTERM_PARSER_DCS, + + VTERM_N_PARSER_TYPES +} VTermParserStringType; + struct VTerm { VTermAllocatorFunctions *allocator; @@ -132,26 +154,41 @@ struct VTerm int cols; struct { - int utf8:1; - int ctrl8bit:1; + unsigned int utf8:1; + unsigned int ctrl8bit:1; } mode; - enum VTermParserState { - NORMAL, - CSI, - OSC, - DCS, - ESC, - ESC_IN_OSC, - ESC_IN_DCS, - } parser_state; - const VTermParserCallbacks *parser_callbacks; - void *cbdata; + struct { + enum VTermParserState { + NORMAL, + CSI_LEADER, + CSI_ARGS, + CSI_INTERMED, + ESC, + /* below here are the "string states" */ + STRING, + ESC_IN_STRING, + } state; + + int intermedlen; + char intermed[INTERMED_MAX]; + + int csi_leaderlen; + char csi_leader[CSI_LEADER_MAX]; + + int csi_argi; + long csi_args[CSI_ARGS_MAX]; + + const VTermParserCallbacks *callbacks; + void *cbdata; + + VTermParserStringType stringtype; + char *strbuffer; + size_t strbuffer_len; + size_t strbuffer_cur; + } parser; /* len == malloc()ed size; cur == number of valid bytes */ - char *strbuffer; - size_t strbuffer_len; - size_t strbuffer_cur; char *outbuffer; size_t outbuffer_len; @@ -203,7 +240,7 @@ void vterm_screen_free(VTermScreen *screen); VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); -int vterm_unicode_width(int codepoint); -int vterm_unicode_is_combining(int codepoint); +int vterm_unicode_width(uint32_t codepoint); +int vterm_unicode_is_combining(uint32_t codepoint); #endif -- cgit v1.2.3