summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenny Root <kroot@google.com>2019-01-22 15:52:41 +0900
committerKenny Root <kroot@google.com>2019-01-25 10:16:52 +0900
commit43b88be5546fcdf4e623f7c1a582ed38f33bda7a (patch)
treefb3c6a9ea8d56a41bd61e437b8a8997416db046f
parent5e87d3caaf09d435606210253a7ce99326c43a9f (diff)
downloadlibvterm-43b88be5546fcdf4e623f7c1a582ed38f33bda7a.tar.gz
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
-rw-r--r--Android.bp7
-rw-r--r--include/vterm.h249
-rw-r--r--include/vterm_keycodes.h (renamed from include/vterm_input.h)3
-rw-r--r--src/encoding.c26
-rw-r--r--src/fullwidth.inc104
-rw-r--r--src/keyboard.c226
-rw-r--r--src/mouse.c96
-rw-r--r--src/parser.c414
-rw-r--r--src/pen.c185
-rw-r--r--src/screen.c79
-rw-r--r--src/state.c375
-rw-r--r--src/unicode.c25
-rw-r--r--src/vterm.c68
-rw-r--r--src/vterm_internal.h113
14 files changed, 1442 insertions, 528 deletions
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 <stdint.h>
#include <stdlib.h>
+#include <stdbool.h>
-#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_keycodes.h
index 165d747..661759f 100644
--- a/include/vterm_input.h
+++ b/include/vterm_keycodes.h
@@ -6,6 +6,8 @@ typedef enum {
VTERM_MOD_SHIFT = 0x01,
VTERM_MOD_ALT = 0x02,
VTERM_MOD_CTRL = 0x04,
+
+ VTERM_ALL_MODS_MASK = 0x07
} VTermModifier;
typedef enum {
@@ -51,6 +53,7 @@ typedef enum {
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))
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 <stdio.h>
+
+#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 <stdio.h>
#include <string.h>
-#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 <stdio.h>
-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 <wchar.h>
-
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