Initial commit

This commit is contained in:
2021-10-10 14:39:17 +08:00
commit d25da95e1e
135 changed files with 19184 additions and 0 deletions

23
extlib/libvterm/LICENSE Normal file
View File

@ -0,0 +1,23 @@
The MIT License
Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

223
extlib/libvterm/encoding.c Normal file
View File

@ -0,0 +1,223 @@
#include "vterm_internal.h"
#define UNICODE_INVALID 0xFFFD
#if defined(DEBUG) && DEBUG > 1
#define DEBUG_PRINT_UTF8
#endif
struct UTF8DecoderData {
// number of bytes remaining in this codepoint
int bytes_remaining;
// number of bytes total in this codepoint once it's finished
// (for detecting overlongs)
int bytes_total;
int this_cp;
};
static void init_utf8(VTermEncoding *enc, void *data_) {
struct UTF8DecoderData *data = data_;
data->bytes_remaining = 0;
data->bytes_total = 0;
}
static void decode_utf8(VTermEncoding *enc, void *data_, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) {
struct UTF8DecoderData *data = data_;
#ifdef DEBUG_PRINT_UTF8
printf("BEGIN UTF-8\n");
#endif
for (; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos];
#ifdef DEBUG_PRINT_UTF8
printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
#endif
if (c < 0x20) // C0
return;
else if (c >= 0x20 && c < 0x7f) {
if (data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
cp[(*cpi)++] = c;
#ifdef DEBUG_PRINT_UTF8
printf(" UTF-8 char: U+%04x\n", c);
#endif
data->bytes_remaining = 0;
}
else if (c == 0x7f) // DEL
return;
else if (c >= 0x80 && c < 0xc0) {
if (!data->bytes_remaining) {
cp[(*cpi)++] = UNICODE_INVALID;
continue;
}
data->this_cp <<= 6;
data->this_cp |= c & 0x3f;
data->bytes_remaining--;
if (!data->bytes_remaining) {
#ifdef DEBUG_PRINT_UTF8
printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
#endif
// Check for overlong sequences
switch (data->bytes_total) {
case 2:
if (data->this_cp < 0x0080)
data->this_cp = UNICODE_INVALID;
break;
case 3:
if (data->this_cp < 0x0800)
data->this_cp = UNICODE_INVALID;
break;
case 4:
if (data->this_cp < 0x10000)
data->this_cp = UNICODE_INVALID;
break;
case 5:
if (data->this_cp < 0x200000)
data->this_cp = UNICODE_INVALID;
break;
case 6:
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) ||
data->this_cp == 0xFFFE ||
data->this_cp == 0xFFFF)
data->this_cp = UNICODE_INVALID;
#ifdef DEBUG_PRINT_UTF8
printf(" char: U+%04x\n", data->this_cp);
#endif
cp[(*cpi)++] = data->this_cp;
}
}
else if (c >= 0xc0 && c < 0xe0) {
if (data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x1f;
data->bytes_total = 2;
data->bytes_remaining = 1;
}
else if (c >= 0xe0 && c < 0xf0) {
if (data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x0f;
data->bytes_total = 3;
data->bytes_remaining = 2;
}
else if (c >= 0xf0 && c < 0xf8) {
if (data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x07;
data->bytes_total = 4;
data->bytes_remaining = 3;
}
else if (c >= 0xf8 && c < 0xfc) {
if (data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x03;
data->bytes_total = 5;
data->bytes_remaining = 4;
}
else if (c >= 0xfc && c < 0xfe) {
if (data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;
data->this_cp = c & 0x01;
data->bytes_total = 6;
data->bytes_remaining = 5;
}
else {
cp[(*cpi)++] = UNICODE_INVALID;
}
}
}
static VTermEncoding encoding_utf8 = {
.init = &init_utf8,
.decode = &decode_utf8,
};
static void decode_usascii(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) {
int is_gr = bytes[*pos] & 0x80;
for (; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos] ^ is_gr;
if (c < 0x20 || c == 0x7f || c >= 0x80)
return;
cp[(*cpi)++] = c;
}
}
static VTermEncoding encoding_usascii = {
.decode = &decode_usascii,
};
struct StaticTableEncoding {
const VTermEncoding enc;
const uint32_t chars[128];
};
static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) {
struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
int is_gr = bytes[*pos] & 0x80;
for (; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos] ^ is_gr;
if (c < 0x20 || c == 0x7f || c >= 0x80)
return;
if (table->chars[c])
cp[(*cpi)++] = table->chars[c];
else
cp[(*cpi)++] = c;
}
}
#include "encoding/DECdrawing.inc"
#include "encoding/uk.inc"
static struct {
VTermEncodingType type;
char designation;
VTermEncoding * enc;
} encodings[] = {
{ENC_UTF8, 'u', &encoding_utf8},
{ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing},
{ENC_SINGLE_94, 'A', (VTermEncoding *)&encoding_uk},
{ENC_SINGLE_94, 'B', &encoding_usascii},
{0},
};
/* This ought to be INTERNAL but isn't because it's used by unit testing */
VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) {
for (int i = 0; encodings[i].designation; i++)
if (encodings[i].type == type && encodings[i].designation == designation)
return encodings[i].enc;
return NULL;
}

View File

@ -0,0 +1,36 @@
static const struct StaticTableEncoding encoding_DECdrawing = {
{ .decode = &decode_table },
{
[0x60] = 0x25C6,
[0x61] = 0x2592,
[0x62] = 0x2409,
[0x63] = 0x240C,
[0x64] = 0x240D,
[0x65] = 0x240A,
[0x66] = 0x00B0,
[0x67] = 0x00B1,
[0x68] = 0x2424,
[0x69] = 0x240B,
[0x6a] = 0x2518,
[0x6b] = 0x2510,
[0x6c] = 0x250C,
[0x6d] = 0x2514,
[0x6e] = 0x253C,
[0x6f] = 0x23BA,
[0x70] = 0x23BB,
[0x71] = 0x2500,
[0x72] = 0x23BC,
[0x73] = 0x23BD,
[0x74] = 0x251C,
[0x75] = 0x2524,
[0x76] = 0x2534,
[0x77] = 0x252C,
[0x78] = 0x2502,
[0x79] = 0x2A7D,
[0x7a] = 0x2A7E,
[0x7b] = 0x03C0,
[0x7c] = 0x2260,
[0x7d] = 0x00A3,
[0x7e] = 0x00B7,
}
};

View File

@ -0,0 +1,6 @@
static const struct StaticTableEncoding encoding_uk = {
{ .decode = &decode_table },
{
[0x23] = 0x00a3,
}
};

View File

@ -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 },

228
extlib/libvterm/keyboard.c Normal file
View File

@ -0,0 +1,228 @@
#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_SS3, 'P'}, // F1
{KEYCODE_SS3, 'Q'}, // F2
{KEYCODE_SS3, 'R'}, // F3
{KEYCODE_SS3, '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~");
}

89
extlib/libvterm/mouse.c Normal file
View File

@ -0,0 +1,89 @@
#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);
}
}

329
extlib/libvterm/parser.c Normal file
View File

@ -0,0 +1,329 @@
#include "vterm_internal.h"
#include <stdio.h>
#include <string.h>
#include "../../runtime/printf.h"
#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->parser.cbdata))
return;
DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control);
}
static void do_csi(VTerm *vt, char command) {
#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);
}
#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;
DEBUG_LOG("libvterm: Unhandled CSI %c\n", command);
}
static void do_escape(VTerm *vt, char command) {
char seq[INTERMED_MAX + 1];
size_t len = vt->parser.intermedlen;
strncpy(seq, vt->parser.intermed, len);
seq[len++] = command;
seq[len] = 0;
if (vt->parser.callbacks && vt->parser.callbacks->escape)
if ((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
return;
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->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->parser.strbuffer + vt->parser.strbuffer_cur, str, len);
vt->parser.strbuffer_cur += len;
}
}
static void start_string(VTerm *vt, VTermParserStringType type) {
vt->parser.stringtype = type;
vt->parser.strbuffer_cur = 0;
}
static void more_string(VTerm *vt, const char *str, size_t len) {
append_strbuffer(vt, str, len);
}
static void done_string(VTerm *vt, const char *str, size_t len) {
if (vt->parser.strbuffer_cur) {
if (str)
append_strbuffer(vt, str, len);
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;
}
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;
DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str);
return;
case VTERM_PARSER_DCS:
if (vt->parser.callbacks && vt->parser.callbacks->dcs)
if ((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata))
return;
DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str);
return;
case VTERM_N_PARSER_TYPES:
return;
}
}
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) {
case NORMAL:
case CSI_LEADER:
case CSI_ARGS:
case CSI_INTERMED:
case ESC:
string_start = NULL;
break;
case STRING:
case ESC_IN_STRING:
string_start = bytes;
break;
}
#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 >= STRING) {
more_string(vt, string_start, bytes + pos - string_start);
string_start = bytes + pos + 1;
}
continue;
}
if (c == 0x18 || c == 0x1a) { // CAN, SUB
ENTER_NORMAL_STATE();
continue;
} else if (c == 0x1b) { // ESC
vt->parser.intermedlen = 0;
if (vt->parser.state == STRING)
vt->parser.state = ESC_IN_STRING;
else
ENTER_STATE(ESC);
continue;
} else if (c == 0x07 && // BEL, can stand for ST in OSC or DCS state
vt->parser.state == STRING) {
// fallthrough
} else if (c < 0x20) { // other C0
if (vt->parser.state >= STRING)
more_string(vt, string_start, bytes + pos - string_start);
do_control(vt, c);
if (vt->parser.state >= STRING)
string_start = bytes + pos + 1;
continue;
}
// else fallthrough
switch (vt->parser.state) {
case ESC_IN_STRING:
if (c == 0x5c) { // ST
vt->parser.state = STRING;
done_string(vt, string_start, bytes + pos - string_start - 1);
ENTER_NORMAL_STATE();
break;
}
vt->parser.state = ESC;
// else fallthrough
case ESC:
switch (c) {
case 0x50: // DCS
start_string(vt, VTERM_PARSER_DCS);
ENTER_STRING_STATE();
break;
case 0x5b: // CSI
vt->parser.csi_leaderlen = 0;
ENTER_STATE(CSI_LEADER);
break;
case 0x5d: // OSC
start_string(vt, VTERM_PARSER_OSC);
ENTER_STRING_STATE();
break;
default:
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 >= 0x30 && c < 0x7f) {
do_escape(vt, c);
ENTER_NORMAL_STATE();
} else {
DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c);
}
}
break;
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 STRING:
if (c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
done_string(vt, string_start, bytes + pos - string_start);
ENTER_NORMAL_STATE();
}
break;
case NORMAL:
if (c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
switch (c) {
case 0x90: // DCS
start_string(vt, VTERM_PARSER_DCS);
ENTER_STRING_STATE();
break;
case 0x9b: // CSI
ENTER_STATE(CSI_LEADER);
break;
case 0x9d: // OSC
start_string(vt, VTERM_PARSER_OSC);
ENTER_STRING_STATE();
break;
default:
do_control(vt, c);
break;
}
} else {
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 += (eaten - 1); // we'll ++ it again in a moment
}
break;
}
}
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;
}

586
extlib/libvterm/pen.c Normal file
View File

@ -0,0 +1,586 @@
#include "vterm_internal.h"
#include <stdio.h>
/**
* 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
{0, 224, 0}, // green
{224, 224, 0}, // yellow
{0, 0, 224}, // blue
{224, 0, 224}, // magenta
{0, 224, 224}, // cyan
{224, 224, 224}, // white == light grey
// high intensity
{128, 128, 128}, // black
{255, 64, 64}, // red
{64, 255, 64}, // green
{255, 255, 64}, // yellow
{64, 64, 255}, // blue
{255, 64, 255}, // magenta
{64, 255, 255}, // cyan
{255, 255, 255}, // white for real
};
static int ramp6[] = {
0x00,
0x33,
0x66,
0x99,
0xCC,
0xFF,
};
static int ramp24[] = {
0x00,
0x0B,
0x16,
0x21,
0x2C,
0x37,
0x42,
0x4D,
0x58,
0x63,
0x6E,
0x79,
0x85,
0x90,
0x9B,
0xA6,
0xB1,
0xBC,
0xC7,
0xD2,
0xDD,
0xE8,
0xF3,
0xFF,
};
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 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
return lookup_colour_ansi(state, index, col);
} else if (index >= 16 && index < 232) {
// 216-colour cube
index -= 16;
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;
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) {
switch (palette) {
case 2: // RGB mode - 3 args contain colour values directly
if (argcount < 3)
return argcount;
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 (!argcount || CSI_ARG_IS_MISSING(args[0])) {
return argcount ? 1 : 0;
}
vterm_color_indexed(col, args[0]);
return argcount ? 1 : 0;
default:
DEBUG_LOG("Unrecognised colour palette %d\n", palette);
return 0;
}
}
// Some conveniences
static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) {
#ifdef DEBUG
if (type != vterm_get_attr_type(attr)) {
DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", attr, vterm_get_attr_type(attr), type);
return;
}
#endif
if (state->callbacks && state->callbacks->setpenattr)
(*state->callbacks->setpenattr)(attr, val, state->cbdata);
}
static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) {
VTermValue val = {.boolean = boolean};
setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);
}
static void setpenattr_int(VTermState *state, VTermAttr attr, int number) {
VTermValue val = {.number = number};
setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);
}
static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) {
VTermValue val = {.color = color};
setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);
}
static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) {
VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;
vterm_color_indexed(colp, col);
setpenattr_col(state, attr, *colp);
}
INTERNAL void vterm_state_newpen(VTermState *state) {
// 90% grey so that pure white is brighter
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++)
lookup_default_colour_ansi(col, &state->colors[col]);
}
INTERNAL void vterm_state_resetpen(VTermState *state) {
state->pen.bold = 0;
setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
state->pen.underline = 0;
setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
state->pen.italic = 0;
setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
state->pen.blink = 0;
setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
state->pen.reverse = 0;
setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
state->pen.strike = 0;
setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
state->pen.font = 0;
setpenattr_int(state, VTERM_ATTR_FONT, 0);
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);
}
INTERNAL void vterm_state_savepen(VTermState *state, int save) {
if (save) {
state->saved.pen = state->pen;
} else {
state->pen = state->saved.pen;
setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold);
setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic);
setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink);
setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse);
setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
}
}
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;
*default_bg = state->default_bg;
}
void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) {
lookup_colour_palette(state, index, col);
}
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) {
if (index >= 0 && index < 16)
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;
}
INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) {
// SGR - ECMA-48 8.3.117
int argi = 0;
int value;
while (argi < argcount) {
// This logic is easier to do 'done' backwards; set it true, and make it
// false again in the 'default' case
int done = 1;
long arg;
switch (arg = CSI_ARG(args[argi])) {
case CSI_ARG_MISSING:
case 0: // Reset
vterm_state_resetpen(state);
break;
case 1: { // Bold on
const VTermColor *fg = &state->pen.fg;
state->pen.bold = 1;
setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
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;
setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
break;
case 4: // Underline
state->pen.underline = VTERM_UNDERLINE_SINGLE;
if (CSI_ARG_HAS_MORE(args[argi])) {
argi++;
switch (CSI_ARG(args[argi])) {
case 0:
state->pen.underline = 0;
break;
case 1:
state->pen.underline = VTERM_UNDERLINE_SINGLE;
break;
case 2:
state->pen.underline = VTERM_UNDERLINE_DOUBLE;
break;
case 3:
state->pen.underline = VTERM_UNDERLINE_CURLY;
break;
}
}
setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
break;
case 5: // Blink
state->pen.blink = 1;
setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
break;
case 7: // Reverse on
state->pen.reverse = 1;
setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
break;
case 9: // Strikethrough on
state->pen.strike = 1;
setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
break;
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
case 19: // Select font
state->pen.font = CSI_ARG(args[argi]) - 10;
setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
break;
case 21: // Underline double
state->pen.underline = VTERM_UNDERLINE_DOUBLE;
setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
break;
case 22: // Bold off
state->pen.bold = 0;
setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
break;
case 23: // Italic and Gothic (currently unsupported) off
state->pen.italic = 0;
setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
break;
case 24: // Underline off
state->pen.underline = 0;
setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
break;
case 25: // Blink off
state->pen.blink = 0;
setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
break;
case 27: // Reverse off
state->pen.reverse = 0;
setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
break;
case 29: // Strikethrough off
state->pen.strike = 0;
setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
break;
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;
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
if (argcount - argi < 1)
return;
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->pen.fg = state->default_fg;
setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
break;
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;
set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
break;
case 48: // Background colour alternative palette
if (argcount - argi < 1)
return;
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->pen.bg = state->default_bg;
setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
break;
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;
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;
set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
break;
default:
done = 0;
break;
}
if (!done)
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;
if (state->pen.bold)
args[argi++] = 1;
if (state->pen.italic)
args[argi++] = 3;
if (state->pen.underline == VTERM_UNDERLINE_SINGLE)
args[argi++] = 4;
if (state->pen.underline == VTERM_UNDERLINE_CURLY)
args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3;
if (state->pen.blink)
args[argi++] = 5;
if (state->pen.reverse)
args[argi++] = 7;
if (state->pen.strike)
args[argi++] = 9;
if (state->pen.font)
args[argi++] = 10 + state->pen.font;
if (state->pen.underline == VTERM_UNDERLINE_DOUBLE)
args[argi++] = 21;
argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true);
argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);
return argi;
}
int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) {
switch (attr) {
case VTERM_ATTR_BOLD:
val->boolean = state->pen.bold;
return 1;
case VTERM_ATTR_UNDERLINE:
val->number = state->pen.underline;
return 1;
case VTERM_ATTR_ITALIC:
val->boolean = state->pen.italic;
return 1;
case VTERM_ATTR_BLINK:
val->boolean = state->pen.blink;
return 1;
case VTERM_ATTR_REVERSE:
val->boolean = state->pen.reverse;
return 1;
case VTERM_ATTR_STRIKE:
val->boolean = state->pen.strike;
return 1;
case VTERM_ATTR_FONT:
val->number = state->pen.font;
return 1;
case VTERM_ATTR_FOREGROUND:
val->color = state->pen.fg;
return 1;
case VTERM_ATTR_BACKGROUND:
val->color = state->pen.bg;
return 1;
case VTERM_N_ATTRS:
return 0;
}
return 0;
}

56
extlib/libvterm/rect.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Some utility functions on VTermRect structures
*/
#define STRFrect "(%d,%d-%d,%d)"
#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col
/* Expand dst to contain src as well */
static void rect_expand(VTermRect *dst, VTermRect *src)
{
if(dst->start_row > src->start_row) dst->start_row = src->start_row;
if(dst->start_col > src->start_col) dst->start_col = src->start_col;
if(dst->end_row < src->end_row) dst->end_row = src->end_row;
if(dst->end_col < src->end_col) dst->end_col = src->end_col;
}
/* Clip the dst to ensure it does not step outside of bounds */
static void rect_clip(VTermRect *dst, VTermRect *bounds)
{
if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row;
if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col;
if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row;
if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col;
/* Ensure it doesn't end up negatively-sized */
if(dst->end_row < dst->start_row) dst->end_row = dst->start_row;
if(dst->end_col < dst->start_col) dst->end_col = dst->start_col;
}
/* True if the two rectangles are equal */
static int rect_equal(VTermRect *a, VTermRect *b)
{
return (a->start_row == b->start_row) &&
(a->start_col == b->start_col) &&
(a->end_row == b->end_row) &&
(a->end_col == b->end_col);
}
/* True if small is contained entirely within big */
static int rect_contains(VTermRect *big, VTermRect *small)
{
if(small->start_row < big->start_row) return 0;
if(small->start_col < big->start_col) return 0;
if(small->end_row > big->end_row) return 0;
if(small->end_col > big->end_col) return 0;
return 1;
}
/* True if the rectangles overlap at all */
static int rect_intersects(VTermRect *a, VTermRect *b)
{
if(a->start_row > b->end_row || b->start_row > a->end_row)
return 0;
if(a->start_col > b->end_col || b->start_col > a->end_col)
return 0;
return 1;
}

877
extlib/libvterm/screen.c Normal file
View File

@ -0,0 +1,877 @@
#include "vterm_internal.h"
#include <stdio.h>
#include <string.h>
#include "rect.h"
#include "utf8.h"
#define UNICODE_SPACE 0x20
#define UNICODE_LINEFEED 0x0a
/* State of the pen at some moment in time, also used in a cell */
typedef struct
{
/* After the bitfield */
VTermColor fg, bg;
unsigned int bold : 1;
unsigned int underline : 2;
unsigned int italic : 1;
unsigned int blink : 1;
unsigned int reverse : 1;
unsigned int strike : 1;
unsigned int font : 4; /* 0 to 9 */
/* Extra state storage that isn't strictly pen-related */
unsigned int protected_cell : 1;
unsigned int dwl : 1; /* on a DECDWL or DECDHL line */
unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */
} ScreenPen;
/* Internal representation of a screen cell */
typedef struct
{
uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
ScreenPen pen;
} ScreenCell;
static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell);
struct VTermScreen {
VTerm * vt;
VTermState *state;
const VTermScreenCallbacks *callbacks;
void * cbdata;
VTermDamageSize damage_merge;
/* start_row == -1 => no damage */
VTermRect damaged;
VTermRect pending_scrollrect;
int pending_scroll_downward, pending_scroll_rightward;
int rows;
int cols;
int global_reverse;
/* Primary and Altscreen. buffers[1] is lazily allocated as needed */
ScreenCell *buffers[2];
/* buffer will == buffers[0] or buffers[1], depending on altscreen */
ScreenCell *buffer;
/* buffer for a single screen row used in scrollback storage callbacks */
VTermScreenCell *sb_buffer;
ScreenPen pen;
};
static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) {
if (row < 0 || row >= screen->rows)
return NULL;
if (col < 0 || col >= screen->cols)
return NULL;
return screen->buffer + (screen->cols * row) + col;
}
static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols) {
ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
for (int row = 0; row < new_rows; row++) {
for (int col = 0; col < new_cols; col++) {
ScreenCell *new_cell = new_buffer + row * new_cols + col;
if (buffer && row < screen->rows && col < screen->cols)
*new_cell = buffer[row * screen->cols + col];
else {
new_cell->chars[0] = 0;
new_cell->pen = screen->pen;
}
}
}
if (buffer)
vterm_allocator_free(screen->vt, buffer);
return new_buffer;
}
static void damagerect(VTermScreen *screen, VTermRect rect) {
VTermRect emit;
switch (screen->damage_merge) {
case VTERM_DAMAGE_CELL:
/* Always emit damage event */
emit = rect;
break;
case VTERM_DAMAGE_ROW:
/* Emit damage longer than one row. Try to merge with existing damage in
* the same row */
if (rect.end_row > rect.start_row + 1) {
// Bigger than 1 line - flush existing, emit this
vterm_screen_flush_damage(screen);
emit = rect;
} else if (screen->damaged.start_row == -1) {
// None stored yet
screen->damaged = rect;
return;
} else if (rect.start_row == screen->damaged.start_row) {
// Merge with the stored line
if (screen->damaged.start_col > rect.start_col)
screen->damaged.start_col = rect.start_col;
if (screen->damaged.end_col < rect.end_col)
screen->damaged.end_col = rect.end_col;
return;
} else {
// Emit the currently stored line, store a new one
emit = screen->damaged;
screen->damaged = rect;
}
break;
case VTERM_DAMAGE_SCREEN:
case VTERM_DAMAGE_SCROLL:
/* Never emit damage event */
if (screen->damaged.start_row == -1)
screen->damaged = rect;
else {
rect_expand(&screen->damaged, &rect);
}
return;
default:
DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge);
return;
}
if (screen->callbacks && screen->callbacks->damage)
(*screen->callbacks->damage)(emit, screen->cbdata);
}
static void damagescreen(VTermScreen *screen) {
VTermRect rect = {
.start_row = 0,
.end_row = screen->rows,
.start_col = 0,
.end_col = screen->cols,
};
damagerect(screen, rect);
}
static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) {
VTermScreen *screen = user;
ScreenCell * cell = getcell(screen, pos.row, pos.col);
if (!cell)
return 0;
int i;
for (i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
cell->chars[i] = info->chars[i];
cell->pen = screen->pen;
}
if (i < VTERM_MAX_CHARS_PER_CELL)
cell->chars[i] = 0;
for (int col = 1; col < info->width; col++)
getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
VTermRect rect = {
.start_row = pos.row,
.end_row = pos.row + 1,
.start_col = pos.col,
.end_col = pos.col + info->width,
};
cell->pen.protected_cell = info->protected_cell;
cell->pen.dwl = info->dwl;
cell->pen.dhl = info->dhl;
damagerect(screen, rect);
return 1;
}
static int moverect_internal(VTermRect dest, VTermRect src, void *user) {
VTermScreen *screen = user;
if (screen->callbacks && screen->callbacks->sb_pushline &&
dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner
dest.end_col == screen->cols && // full width
screen->buffer == screen->buffers[0]) { // not altscreen
VTermPos pos;
for (pos.row = 0; pos.row < src.start_row; pos.row++) {
for (pos.col = 0; pos.col < screen->cols; pos.col++)
vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
(screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
}
}
int cols = src.end_col - src.start_col;
int downward = src.start_row - dest.start_row;
int init_row, test_row, inc_row;
if (downward < 0) {
init_row = dest.end_row - 1;
test_row = dest.start_row - 1;
inc_row = -1;
} else {
init_row = dest.start_row;
test_row = dest.end_row;
inc_row = +1;
}
for (int row = init_row; row != test_row; row += inc_row)
memmove(getcell(screen, row, dest.start_col), getcell(screen, row + downward, src.start_col), cols * sizeof(ScreenCell));
return 1;
}
static int moverect_user(VTermRect dest, VTermRect src, void *user) {
VTermScreen *screen = user;
if (screen->callbacks && screen->callbacks->moverect) {
if (screen->damage_merge != VTERM_DAMAGE_SCROLL)
// Avoid an infinite loop
vterm_screen_flush_damage(screen);
if ((*screen->callbacks->moverect)(dest, src, screen->cbdata))
return 1;
}
damagerect(screen, dest);
return 1;
}
static int erase_internal(VTermRect rect, int selective, void *user) {
VTermScreen *screen = user;
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++) {
ScreenCell *cell = getcell(screen, row, col);
if (selective && cell->pen.protected_cell)
continue;
cell->chars[0] = 0;
cell->pen = screen->pen;
cell->pen.dwl = info->doublewidth;
cell->pen.dhl = info->doubleheight;
}
}
return 1;
}
static int erase_user(VTermRect rect, int selective, void *user) {
VTermScreen *screen = user;
damagerect(screen, rect);
return 1;
}
static int erase(VTermRect rect, int selective, void *user) {
erase_internal(rect, selective, user);
return erase_user(rect, 0, user);
}
static int scrollrect(VTermRect rect, int downward, int rightward, void *user) {
VTermScreen *screen = user;
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, moverect_user, erase_user, screen);
return 1;
}
if (screen->damaged.start_row != -1 &&
!rect_intersects(&rect, &screen->damaged)) {
vterm_screen_flush_damage(screen);
}
if (screen->pending_scrollrect.start_row == -1) {
screen->pending_scrollrect = rect;
screen->pending_scroll_downward = downward;
screen->pending_scroll_rightward = rightward;
} else if (rect_equal(&screen->pending_scrollrect, &rect) && ((screen->pending_scroll_downward == 0 && downward == 0) || (screen->pending_scroll_rightward == 0 && rightward == 0))) {
screen->pending_scroll_downward += downward;
screen->pending_scroll_rightward += rightward;
} else {
vterm_screen_flush_damage(screen);
screen->pending_scrollrect = rect;
screen->pending_scroll_downward = downward;
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);
}
/* There are a number of possible cases here, but lets restrict this to only
* the common case where we might actually gain some performance by
* optimising it. Namely, a vertical scroll that neatly cuts the damage
* region in half.
*/
else if (rect.start_col <= screen->damaged.start_col && rect.end_col >= screen->damaged.end_col && rightward == 0) {
if (screen->damaged.start_row >= rect.start_row &&
screen->damaged.start_row < rect.end_row) {
screen->damaged.start_row -= downward;
if (screen->damaged.start_row < rect.start_row)
screen->damaged.start_row = rect.start_row;
if (screen->damaged.start_row > rect.end_row)
screen->damaged.start_row = rect.end_row;
}
if (screen->damaged.end_row >= rect.start_row &&
screen->damaged.end_row < rect.end_row) {
screen->damaged.end_row -= downward;
if (screen->damaged.end_row < rect.start_row)
screen->damaged.end_row = rect.start_row;
if (screen->damaged.end_row > rect.end_row)
screen->damaged.end_row = rect.end_row;
}
} else {
DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", ARGSrect(screen->damaged), ARGSrect(rect));
}
return 1;
}
static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) {
VTermScreen *screen = user;
if (screen->callbacks && screen->callbacks->movecursor)
return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
return 0;
}
static int setpenattr(VTermAttr attr, VTermValue *val, void *user) {
VTermScreen *screen = user;
switch (attr) {
case VTERM_ATTR_BOLD:
screen->pen.bold = val->boolean;
return 1;
case VTERM_ATTR_UNDERLINE:
screen->pen.underline = val->number;
return 1;
case VTERM_ATTR_ITALIC:
screen->pen.italic = val->boolean;
return 1;
case VTERM_ATTR_BLINK:
screen->pen.blink = val->boolean;
return 1;
case VTERM_ATTR_REVERSE:
screen->pen.reverse = val->boolean;
return 1;
case VTERM_ATTR_STRIKE:
screen->pen.strike = val->boolean;
return 1;
case VTERM_ATTR_FONT:
screen->pen.font = val->number;
return 1;
case VTERM_ATTR_FOREGROUND:
screen->pen.fg = val->color;
return 1;
case VTERM_ATTR_BACKGROUND:
screen->pen.bg = val->color;
return 1;
case VTERM_N_ATTRS:
return 0;
}
return 0;
}
static int settermprop(VTermProp prop, VTermValue *val, void *user) {
VTermScreen *screen = user;
switch (prop) {
case VTERM_PROP_ALTSCREEN:
if (val->boolean && !screen->buffers[1])
return 0;
screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0];
/* only send a damage event on disable; because during enable there's an
* erase that sends a damage anyway
*/
if (!val->boolean)
damagescreen(screen);
break;
case VTERM_PROP_REVERSE:
screen->global_reverse = val->boolean;
damagescreen(screen);
break;
default:; /* ignore */
}
if (screen->callbacks && screen->callbacks->settermprop)
return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
return 1;
}
static int bell(void *user) {
VTermScreen *screen = user;
if (screen->callbacks && screen->callbacks->bell)
return (*screen->callbacks->bell)(screen->cbdata);
return 0;
}
static int resize(int new_rows, int new_cols, VTermPos *delta, void *user) {
VTermScreen *screen = user;
int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]);
int old_rows = screen->rows;
int old_cols = screen->cols;
if (!is_altscreen && new_rows < old_rows) {
// 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) || cursor.row == pos.row)
break;
int first_blank_row = pos.row + 1;
if (first_blank_row > new_rows) {
VTermRect rect = {
.start_row = 0,
.end_row = old_rows,
.start_col = 0,
.end_col = old_cols,
};
scrollrect(rect, first_blank_row - new_rows, 0, user);
vterm_screen_flush_damage(screen);
delta->row -= first_blank_row - new_rows;
}
}
screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols);
if (screen->buffers[1])
screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols);
screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0];
screen->rows = new_rows;
screen->cols = new_cols;
if (screen->sb_buffer)
vterm_allocator_free(screen->vt, screen->sb_buffer);
screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
if (new_cols > old_cols) {
VTermRect rect = {
.start_row = 0,
.end_row = old_rows,
.start_col = old_cols,
.end_col = new_cols,
};
damagerect(screen, rect);
}
if (new_rows > old_rows) {
if (!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) {
int rows = new_rows - old_rows;
while (rows) {
if (!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata)))
break;
VTermRect rect = {
.start_row = 0,
.end_row = screen->rows,
.start_col = 0,
.end_col = screen->cols,
};
scrollrect(rect, -1, 0, user);
VTermPos pos = {0, 0};
for (pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width)
vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col);
rect.end_row = 1;
damagerect(screen, rect);
vterm_screen_flush_damage(screen);
rows--;
delta->row++;
}
}
VTermRect rect = {
.start_row = old_rows,
.end_row = new_rows,
.start_col = 0,
.end_col = new_cols,
};
damagerect(screen, rect);
}
if (screen->callbacks && screen->callbacks->resize)
return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
return 1;
}
static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) {
VTermScreen *screen = user;
if (newinfo->doublewidth != oldinfo->doublewidth ||
newinfo->doubleheight != oldinfo->doubleheight) {
for (int col = 0; col < screen->cols; col++) {
ScreenCell *cell = getcell(screen, row, col);
cell->pen.dwl = newinfo->doublewidth;
cell->pen.dhl = newinfo->doubleheight;
}
VTermRect rect = {
.start_row = row,
.end_row = row + 1,
.start_col = 0,
.end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols,
};
damagerect(screen, rect);
if (newinfo->doublewidth) {
rect.start_col = screen->cols / 2;
rect.end_col = screen->cols;
erase_internal(rect, 0, user);
}
}
return 1;
}
static VTermStateCallbacks state_cbs = {
.putglyph = &putglyph,
.movecursor = &movecursor,
.scrollrect = &scrollrect,
.erase = &erase,
.setpenattr = &setpenattr,
.settermprop = &settermprop,
.bell = &bell,
.resize = &resize,
.setlineinfo = &setlineinfo,
};
static VTermScreen *screen_new(VTerm *vt) {
VTermState *state = vterm_obtain_state(vt);
if (!state)
return NULL;
VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
int rows, cols;
vterm_get_size(vt, &rows, &cols);
screen->vt = vt;
screen->state = state;
screen->damage_merge = VTERM_DAMAGE_CELL;
screen->damaged.start_row = -1;
screen->pending_scrollrect.start_row = -1;
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];
screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
vterm_state_set_callbacks(screen->state, &state_cbs, screen);
return screen;
}
INTERNAL void vterm_screen_free(VTermScreen *screen) {
vterm_allocator_free(screen->vt, screen->buffers[0]);
if (screen->buffers[1])
vterm_allocator_free(screen->vt, screen->buffers[1]);
vterm_allocator_free(screen->vt, screen->sb_buffer);
vterm_allocator_free(screen->vt, screen);
}
void vterm_screen_reset(VTermScreen *screen, int hard) {
screen->damaged.start_row = -1;
screen->pending_scrollrect.start_row = -1;
vterm_state_reset(screen->state, hard);
vterm_screen_flush_damage(screen);
}
static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) {
size_t outpos = 0;
int padding = 0;
#define PUT(c) \
if (utf8) { \
size_t thislen = utf8_seqlen(c); \
if (buffer && outpos + thislen <= len) \
outpos += fill_utf8((c), (char *)buffer + outpos); \
else \
outpos += thislen; \
} else { \
if (buffer && outpos + 1 <= len) \
((uint32_t *)buffer)[outpos++] = (c); \
else \
outpos++; \
}
for (int row = rect.start_row; row < rect.end_row; row++) {
for (int col = rect.start_col; col < rect.end_col; col++) {
ScreenCell *cell = getcell(screen, row, col);
if (cell->chars[0] == 0)
// Erased cell, might need a space
padding++;
else if (cell->chars[0] == (uint32_t)-1)
// Gap behind a double-width char, do nothing
;
else {
while (padding) {
PUT(UNICODE_SPACE);
padding--;
}
for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
PUT(cell->chars[i]);
}
}
}
if (row < rect.end_row - 1) {
PUT(UNICODE_LINEFEED);
padding = 0;
}
}
return outpos;
}
size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) {
return _get_chars(screen, 0, chars, len, rect);
}
size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect) {
return _get_chars(screen, 1, str, len, rect);
}
/* Copy internal to external representation of a screen cell */
int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) {
ScreenCell *intcell = getcell(screen, pos.row, pos.col);
if (!intcell)
return 0;
for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
cell->chars[i] = intcell->chars[i];
if (!intcell->chars[i])
break;
}
cell->attrs.bold = intcell->pen.bold;
cell->attrs.underline = intcell->pen.underline;
cell->attrs.italic = intcell->pen.italic;
cell->attrs.blink = intcell->pen.blink;
cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse;
cell->attrs.strike = intcell->pen.strike;
cell->attrs.font = intcell->pen.font;
cell->attrs.dwl = intcell->pen.dwl;
cell->attrs.dhl = intcell->pen.dhl;
cell->fg = intcell->pen.fg;
cell->bg = intcell->pen.bg;
if (pos.col < (screen->cols - 1) &&
getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
cell->width = 2;
else
cell->width = 1;
return 1;
}
/* Copy external to internal representation of a screen cell */
/* static because it's only used internally for sb_popline during resize */
static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell) {
ScreenCell *intcell = getcell(screen, pos.row, pos.col);
if (!intcell)
return 0;
for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
intcell->chars[i] = cell->chars[i];
if (!cell->chars[i])
break;
}
intcell->pen.bold = cell->attrs.bold;
intcell->pen.underline = cell->attrs.underline;
intcell->pen.italic = cell->attrs.italic;
intcell->pen.blink = cell->attrs.blink;
intcell->pen.reverse = cell->attrs.reverse ^ screen->global_reverse;
intcell->pen.strike = cell->attrs.strike;
intcell->pen.font = cell->attrs.font;
intcell->pen.fg = cell->fg;
intcell->pen.bg = cell->bg;
if (cell->width == 2)
getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1;
return 1;
}
int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) {
/* This cell is EOL if this and every cell to the right is black */
for (; pos.col < screen->cols; pos.col++) {
ScreenCell *cell = getcell(screen, pos.row, pos.col);
if (cell->chars[0] != 0)
return 0;
}
return 1;
}
VTermScreen *vterm_obtain_screen(VTerm *vt) {
if (vt->screen)
return vt->screen;
VTermScreen *screen = screen_new(vt);
vt->screen = screen;
return screen;
}
void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) {
if (!screen->buffers[1] && altscreen) {
int rows, cols;
vterm_get_size(screen->vt, &rows, &cols);
screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols);
}
}
void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) {
screen->callbacks = callbacks;
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) {
vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, moverect_user, erase_user, screen);
screen->pending_scrollrect.start_row = -1;
}
if (screen->damaged.start_row != -1) {
if (screen->callbacks && screen->callbacks->damage)
(*screen->callbacks->damage)(screen->damaged, screen->cbdata);
screen->damaged.start_row = -1;
}
}
void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) {
vterm_screen_flush_damage(screen);
screen->damage_merge = size;
}
static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) {
if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold))
return 1;
if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline))
return 1;
if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic))
return 1;
if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink))
return 1;
if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse))
return 1;
if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike))
return 1;
if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font))
return 1;
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_is_equal(&a->pen.bg, &b->pen.bg))
return 1;
return 0;
}
int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) {
ScreenCell *target = getcell(screen, pos.row, pos.col);
// TODO: bounds check
extent->start_row = pos.row;
extent->end_row = pos.row + 1;
if (extent->start_col < 0)
extent->start_col = 0;
if (extent->end_col < 0)
extent->end_col = screen->cols;
int col;
for (col = pos.col - 1; col >= extent->start_col; col--)
if (attrs_differ(attrs, target, getcell(screen, pos.row, col)))
break;
extent->start_col = col + 1;
for (col = pos.col + 1; col < extent->end_col; col++)
if (attrs_differ(attrs, target, getcell(screen, pos.row, col)))
break;
extent->end_col = col - 1;
return 1;
}
void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) {
vterm_state_convert_color_to_rgb(screen->state, col);
}

1801
extlib/libvterm/state.c Normal file

File diff suppressed because it is too large Load Diff

229
extlib/libvterm/unicode.c Normal file
View File

@ -0,0 +1,229 @@
#include "vterm_internal.h"
// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
// With modifications:
// made functions static
// moved 'combining' table to file scope, so other functions can see it
// ###################################################################
/*
* This is an implementation of wcwidth() and wcswidth() (defined in
* IEEE Std 1002.1-2001) for Unicode.
*
* http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
* http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
*
* In fixed-width output devices, Latin characters all occupy a single
* "cell" position of equal width, whereas ideographic CJK characters
* occupy two such cells. Interoperability between terminal-line
* applications and (teletype-style) character terminals using the
* UTF-8 encoding requires agreement on which character should advance
* the cursor by how many cell positions. No established formal
* standards exist at present on which Unicode character shall occupy
* how many cell positions on character terminals. These routines are
* a first attempt of defining such behavior based on simple rules
* applied to data provided by the Unicode Consortium.
*
* For some graphical characters, the Unicode standard explicitly
* defines a character-cell width via the definition of the East Asian
* FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
* In all these cases, there is no ambiguity about which width a
* terminal shall use. For characters in the East Asian Ambiguous (A)
* class, the width choice depends purely on a preference of backward
* compatibility with either historic CJK or Western practice.
* Choosing single-width for these characters is easy to justify as
* the appropriate long-term solution, as the CJK practice of
* displaying these characters as double-width comes from historic
* implementation simplicity (8-bit encoded characters were displayed
* single-width and 16-bit ones double-width, even for Greek,
* Cyrillic, etc.) and not any typographic considerations.
*
* Much less clear is the choice of width for the Not East Asian
* (Neutral) class. Existing practice does not dictate a width for any
* of these characters. It would nevertheless make sense
* typographically to allocate two character cells to characters such
* as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
* represented adequately with a single-width glyph. The following
* routines at present merely assign a single-cell width to all
* neutral characters, in the interest of simplicity. This is not
* entirely satisfactory and should be reconsidered before
* establishing a formal standard in this area. At the moment, the
* decision which Not East Asian (Neutral) characters should be
* represented by double-width glyphs cannot yet be answered by
* applying a simple rule from the Unicode database content. Setting
* up a proper standard for the behavior of UTF-8 character terminals
* will require a careful analysis not only of each Unicode character,
* but also of each presentation form, something the author of these
* routines has avoided to do so far.
*
* http://www.unicode.org/unicode/reports/tr11/
*
* Markus Kuhn -- 2007-05-26 (Unicode 5.0)
*
* Permission to use, copy, modify, and distribute this software
* for any purpose and without fee is hereby granted. The author
* disclaims all warranties with regard to this software.
*
* Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
*/
struct interval {
int first;
int last;
};
/* sorted list of non-overlapping intervals of non-spacing characters */
/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
static const struct interval combining[] = {
{0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, {0xE0100, 0xE01EF}};
/* auxiliary function for binary search in interval table */
static int bisearch(uint32_t ucs, const struct interval *table, int max) {
int min = 0;
int mid;
if (ucs < table[0].first || ucs > table[max].last)
return 0;
while (max >= min) {
mid = (min + max) / 2;
if (ucs > table[mid].last)
min = mid + 1;
else if (ucs < table[mid].first)
max = mid - 1;
else
return 1;
}
return 0;
}
/* The following two functions define the column width of an ISO 10646
* character as follows:
*
* - The null character (U+0000) has a column width of 0.
*
* - Other C0/C1 control characters and DEL will lead to a return
* value of -1.
*
* - Non-spacing and enclosing combining characters (general
* category code Mn or Me in the Unicode database) have a
* column width of 0.
*
* - SOFT HYPHEN (U+00AD) has a column width of 1.
*
* - Other format characters (general category code Cf in the Unicode
* database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
*
* - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
* have a column width of 0.
*
* - Spacing characters in the East Asian Wide (W) or East Asian
* Full-width (F) category as defined in Unicode Technical
* Report #11 have a column width of 2.
*
* - All remaining characters (including all printable
* ISO 8859-1 and WGL4 characters, Unicode control characters,
* etc.) have a column width of 1.
*
* This implementation assumes that uint32_t characters are encoded
* in ISO 10646.
*/
static int mk_wcwidth(uint32_t ucs) {
/* test for 8-bit control characters */
if (ucs == 0)
return 0;
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
return -1;
/* binary search in table of non-spacing characters */
if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1))
return 0;
/* if we arrive here, ucs is not a combining or C0/C1 control character */
return 1 +
(ucs >= 0x1100 &&
(ucs <= 0x115f || /* Hangul Jamo init. consonants */
ucs == 0x2329 || ucs == 0x232a ||
(ucs >= 0x2e80 && ucs <= 0xa4cf &&
ucs != 0x303f) || /* CJK ... Yi */
(ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
(ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
(ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
(ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
(ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
(ucs >= 0x20000 && ucs <= 0x2fffd) ||
(ucs >= 0x30000 && ucs <= 0x3fffd)));
}
static int mk_wcswidth(const uint32_t *pwcs, size_t n) {
int w, width = 0;
for (; *pwcs && n-- > 0; pwcs++)
if ((w = mk_wcwidth(*pwcs)) < 0)
return -1;
else
width += w;
return width;
}
/*
* The following functions are the same as mk_wcwidth() and
* mk_wcswidth(), except that spacing characters in the East Asian
* Ambiguous (A) category as defined in Unicode Technical Report #11
* have a column width of 2. This variant might be useful for users of
* CJK legacy encodings who want to migrate to UCS without changing
* the traditional terminal character-width behaviour. It is not
* otherwise recommended for general use.
*/
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" */
static const struct interval ambiguous[] = {
{0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8}, {0x00AA, 0x00AA}, {0x00AE, 0x00AE}, {0x00B0, 0x00B4}, {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6}, {0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1}, {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED}, {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA}, {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101}, {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B}, {0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133}, {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144}, {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153}, {0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE}, {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4}, {0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA}, {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261}, {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB}, {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB}, {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1}, {0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F}, {0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016}, {0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022}, {0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033}, {0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E}, {0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084}, {0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105}, {0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116}, {0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B}, {0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B}, {0x2170, 0x2179}, {0x2190, 0x2199}, {0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4}, {0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203}, {0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F}, {0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A}, {0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225}, {0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237}, {0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C}, {0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267}, {0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283}, {0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299}, {0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312}, {0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573}, {0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1}, {0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7}, {0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8}, {0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5}, {0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609}, {0x260E, 0x260F}, {0x2614, 0x2615}, {0x261C, 0x261C}, {0x261E, 0x261E}, {0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661}, {0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D}, {0x266F, 0x266F}, {0x273D, 0x273D}, {0x2776, 0x277F}, {0xE000, 0xF8FF}, {0xFFFD, 0xFFFD}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}};
/* binary search in table of non-spacing characters */
if (bisearch(ucs, ambiguous, sizeof(ambiguous) / sizeof(struct interval) - 1))
return 2;
return mk_wcwidth(ucs);
}
static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n) {
int w, width = 0;
for (; *pwcs && n-- > 0; pwcs++)
if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
return -1;
else
width += w;
return width;
}
// ################################
// ### The rest added by Paul Evans
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(uint32_t codepoint) {
return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);
}

39
extlib/libvterm/utf8.h Normal file
View File

@ -0,0 +1,39 @@
/* The following functions copied and adapted from libtermkey
*
* http://www.leonerd.org.uk/code/libtermkey/
*/
static inline unsigned int utf8_seqlen(long codepoint)
{
if(codepoint < 0x0000080) return 1;
if(codepoint < 0x0000800) return 2;
if(codepoint < 0x0010000) return 3;
if(codepoint < 0x0200000) return 4;
if(codepoint < 0x4000000) return 5;
return 6;
}
/* Does NOT NUL-terminate the buffer */
static int fill_utf8(long codepoint, char *str)
{
int nbytes = utf8_seqlen(codepoint);
// This is easier done backwards
int b = nbytes;
while(b > 1) {
b--;
str[b] = 0x80 | (codepoint & 0x3f);
codepoint >>= 6;
}
switch(nbytes) {
case 1: str[0] = (codepoint & 0x7f); break;
case 2: str[0] = 0xc0 | (codepoint & 0x1f); break;
case 3: str[0] = 0xe0 | (codepoint & 0x0f); break;
case 4: str[0] = 0xf0 | (codepoint & 0x07); break;
case 5: str[0] = 0xf8 | (codepoint & 0x03); break;
case 6: str[0] = 0xfc | (codepoint & 0x01); break;
}
return nbytes;
}
/* end copy */

360
extlib/libvterm/vterm.c Normal file
View File

@ -0,0 +1,360 @@
#include "vterm_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "../../runtime/panic_assert.h"
#include "../../runtime/printf.h"
/*****************
* API functions *
*****************/
/*
static void *default_malloc(size_t size, void *allocdata) {
void *ptr = malloc(size);
if (ptr)
memset(ptr, 0, size);
return ptr;
}
static void default_free(void *ptr, void *allocdata) {
free(ptr);
}
*/
static VTermAllocatorFunctions default_allocator = {
//.malloc = &default_malloc,
//.free = &default_free,
};
VTerm *vterm_new(int rows, int cols) {
return vterm_new_with_allocator(rows, cols, &default_allocator, NULL);
}
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) {
/* Need to bootstrap using the allocator function directly */
VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata);
vt->allocator = funcs;
vt->allocdata = allocdata;
vt->rows = rows;
vt->cols = cols;
vt->parser.state = NORMAL;
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->outfunc = NULL;
vt->outdata = NULL;
vt->outbuffer_len = 64;
vt->outbuffer_cur = 0;
vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
vt->tmpbuffer_len = 64;
vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len);
return vt;
}
void vterm_free(VTerm *vt) {
if (vt->screen)
vterm_screen_free(vt->screen);
if (vt->state)
vterm_state_free(vt->state);
vterm_allocator_free(vt, vt->parser.strbuffer);
vterm_allocator_free(vt, vt->outbuffer);
vterm_allocator_free(vt, vt->tmpbuffer);
vterm_allocator_free(vt, vt);
}
INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) {
return (*vt->allocator->malloc)(size, vt->allocdata);
}
INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) {
(*vt->allocator->free)(ptr, vt->allocdata);
}
void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) {
if (rowsp)
*rowsp = vt->rows;
if (colsp)
*colsp = vt->cols;
}
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->parser.cbdata);
}
int vterm_get_utf8(const VTerm *vt) {
return vt->mode.utf8;
}
void vterm_set_utf8(VTerm *vt, int is_utf8) {
vt->mode.utf8 = is_utf8;
}
void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) {
vt->outfunc = func;
vt->outdata = user;
}
INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) {
if (vt->outfunc) {
(vt->outfunc)(bytes, len, vt->outdata);
return;
}
if (len > vt->outbuffer_len - vt->outbuffer_cur)
return;
memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len);
vt->outbuffer_cur += len;
}
INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) {
size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, format, args);
vterm_push_output_bytes(vt, vt->tmpbuffer, len);
}
INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) {
va_list args;
va_start(args, format);
vterm_push_output_vsprintf(vt, format, args);
va_end(args);
}
INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) {
size_t cur;
if (ctrl >= 0x80 && !vt->mode.ctrl8bit)
cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40);
else
cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl);
if (cur >= vt->tmpbuffer_len)
return;
va_list args;
va_start(args, fmt);
cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args);
va_end(args);
if (cur >= vt->tmpbuffer_len)
return;
vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
}
INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...) {
size_t cur = 0;
cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
vt->mode.ctrl8bit ? "\x90" : ESC_S "P"); // DCS
if (cur >= vt->tmpbuffer_len)
return;
va_list args;
va_start(args, fmt);
cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args);
va_end(args);
if (cur >= vt->tmpbuffer_len)
return;
cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST
if (cur >= vt->tmpbuffer_len)
return;
vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
}
size_t vterm_output_get_buffer_size(const VTerm *vt) {
return vt->outbuffer_len;
}
size_t vterm_output_get_buffer_current(const VTerm *vt) {
return vt->outbuffer_cur;
}
size_t vterm_output_get_buffer_remaining(const VTerm *vt) {
return vt->outbuffer_len - vt->outbuffer_cur;
}
size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) {
if (len > vt->outbuffer_cur)
len = vt->outbuffer_cur;
memcpy(buffer, vt->outbuffer, len);
if (len < vt->outbuffer_cur)
memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len);
vt->outbuffer_cur -= len;
return len;
}
VTermValueType vterm_get_attr_type(VTermAttr attr) {
switch (attr) {
case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT;
case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL;
case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL;
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 */
}
VTermValueType vterm_get_prop_type(VTermProp prop) {
switch (prop) {
case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL;
case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL;
case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL;
case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING;
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 */
}
void vterm_scroll_rect(VTermRect rect, int downward, int rightward, int (*moverect)(VTermRect src, VTermRect dest, void *user), int (*eraserect)(VTermRect rect, int selective, void *user), void *user) {
VTermRect src;
VTermRect dest;
if (abs(downward) >= rect.end_row - rect.start_row ||
abs(rightward) >= rect.end_col - rect.start_col) {
/* Scroll more than area; just erase the lot */
(*eraserect)(rect, 0, user);
return;
}
if (rightward >= 0) {
/* rect: [XXX................]
* src: [----------------]
* dest: [----------------]
*/
dest.start_col = rect.start_col;
dest.end_col = rect.end_col - rightward;
src.start_col = rect.start_col + rightward;
src.end_col = rect.end_col;
} else {
/* rect: [................XXX]
* src: [----------------]
* dest: [----------------]
*/
int leftward = -rightward;
dest.start_col = rect.start_col + leftward;
dest.end_col = rect.end_col;
src.start_col = rect.start_col;
src.end_col = rect.end_col - leftward;
}
if (downward >= 0) {
dest.start_row = rect.start_row;
dest.end_row = rect.end_row - downward;
src.start_row = rect.start_row + downward;
src.end_row = rect.end_row;
} else {
int upward = -downward;
dest.start_row = rect.start_row + upward;
dest.end_row = rect.end_row;
src.start_row = rect.start_row;
src.end_row = rect.end_row - upward;
}
if (moverect)
(*moverect)(dest, src, user);
if (downward > 0)
rect.start_row = rect.end_row - downward;
else if (downward < 0)
rect.end_row = rect.start_row - downward;
if (rightward > 0)
rect.start_col = rect.end_col - rightward;
else if (rightward < 0)
rect.end_col = rect.start_col - rightward;
(*eraserect)(rect, 0, user);
}
void vterm_copy_cells(VTermRect dest, VTermRect src, void (*copycell)(VTermPos dest, VTermPos src, void *user), void *user) {
int downward = src.start_row - dest.start_row;
int rightward = src.start_col - dest.start_col;
int init_row, test_row, init_col, test_col;
int inc_row, inc_col;
if (downward < 0) {
init_row = dest.end_row - 1;
test_row = dest.start_row - 1;
inc_row = -1;
} else /* downward >= 0 */ {
init_row = dest.start_row;
test_row = dest.end_row;
inc_row = +1;
}
if (rightward < 0) {
init_col = dest.end_col - 1;
test_col = dest.start_col - 1;
inc_col = -1;
} else /* rightward >= 0 */ {
init_col = dest.start_col;
test_col = dest.end_col;
inc_col = +1;
}
VTermPos pos;
for (pos.row = init_row; pos.row != test_row; pos.row += inc_row)
for (pos.col = init_col; pos.col != test_col; pos.col += inc_col) {
VTermPos srcpos = {pos.row + downward, pos.col + rightward};
(*copycell)(pos, srcpos, user);
}
}
void vterm_check_version(int major, int minor) {
if (major != VTERM_VERSION_MAJOR) {
//fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", major, VTERM_VERSION_MAJOR);
Panicf("libvterm major version mismatch; %d (wants) != %d (library)\n", major, VTERM_VERSION_MAJOR);
}
if (minor > VTERM_VERSION_MINOR) {
//fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", minor, VTERM_VERSION_MINOR);
Panicf("libvterm minor version mismatch; %d (wants) > %d (library)\n", minor, VTERM_VERSION_MINOR);
}
// Happy
}

533
extlib/libvterm/vterm.h Normal file
View File

@ -0,0 +1,533 @@
#ifndef __VTERM_H__
#define __VTERM_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "vterm_keycodes.h"
#define VTERM_VERSION_MAJOR 0
#define VTERM_VERSION_MINOR 1
#define VTERM_CHECK_VERSION \
vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)
typedef struct VTerm VTerm;
typedef struct VTermState VTermState;
typedef struct VTermScreen VTermScreen;
typedef struct {
int row;
int col;
} VTermPos;
/* some small utility functions; we can just keep these static here */
/* order points by on-screen flow order */
static inline int vterm_pos_cmp(VTermPos a, VTermPos b)
{
return (a.row == b.row) ? a.col - b.col : a.row - b.row;
}
typedef struct {
int start_row;
int end_row;
int start_col;
int end_col;
} VTermRect;
/* true if the rect contains the point */
static inline int vterm_rect_contains(VTermRect r, VTermPos p)
{
return p.row >= r.start_row && p.row < r.end_row &&
p.col >= r.start_col && p.col < r.end_col;
}
/* move a rect */
static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)
{
rect->start_row += row_delta; rect->end_row += row_delta;
rect->start_col += col_delta; rect->end_col += col_delta;
}
/**
* 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 {
int boolean;
int number;
char *string;
VTermColor color;
} VTermValue;
typedef enum {
/* VTERM_ATTR_NONE = 0 */
VTERM_ATTR_BOLD = 1, // bool: 1, 22
VTERM_ATTR_UNDERLINE, // number: 4, 21, 24
VTERM_ATTR_ITALIC, // bool: 3, 23
VTERM_ATTR_BLINK, // bool: 5, 25
VTERM_ATTR_REVERSE, // bool: 7, 27
VTERM_ATTR_STRIKE, // bool: 9, 29
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 {
/* VTERM_PROP_NONE = 0 */
VTERM_PROP_CURSORVISIBLE = 1, // bool
VTERM_PROP_CURSORBLINK, // bool
VTERM_PROP_ALTSCREEN, // bool
VTERM_PROP_TITLE, // string
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
};
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;
int width;
unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */
unsigned int dwl:1; /* DECDWL or DECDHL double-width line */
unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */
} VTermGlyphInfo;
typedef struct {
unsigned int doublewidth:1; /* DECDWL or DECDHL line */
unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */
} VTermLineInfo;
typedef struct {
/* libvterm relies on this memory to be zeroed out before it is returned
* by the allocator. */
void *(*malloc)(size_t size, void *allocdata);
void (*free)(void *ptr, void *allocdata);
} VTermAllocatorFunctions;
void vterm_check_version(int major, int minor);
VTerm *vterm_new(int rows, int cols);
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);
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);
int vterm_get_utf8(const VTerm *vt);
void vterm_set_utf8(VTerm *vt, int is_utf8);
size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);
/* Setting output callback will override the buffer logic */
typedef void VTermOutputCallback(const char *s, size_t len, void *user);
void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user);
/* These buffer functions only work if output callback is NOT set
* These are deprecated and will be removed in a later version */
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);
/* This too */
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
// ------------
/* Flag to indicate non-final subparameters in a single CSI parameter.
* Consider
* CSI 1;2:3:4;5a
* 1 4 and 5 are final.
* 2 and 3 are non-final and will have this bit set
*
* Don't confuse this with the final byte of the CSI escape; 'a' in this case.
*/
#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)
/* Can't use -1 to indicate a missing argument; use this instead */
#define CSI_ARG_MISSING ((1UL<<31)-1)
#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)
#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))
#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))
typedef struct {
int (*text)(const char *bytes, size_t len, void *user);
int (*control)(unsigned char control, void *user);
int (*escape)(const char *bytes, size_t len, void *user);
int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
int (*osc)(const char *command, size_t cmdlen, void *user);
int (*dcs)(const char *command, size_t cmdlen, void *user);
int (*resize)(int rows, int cols, void *user);
} VTermParserCallbacks;
void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
void *vterm_parser_get_cbdata(VTerm *vt);
// -----------
// State layer
// -----------
typedef struct {
int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);
int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);
int (*moverect)(VTermRect dest, VTermRect src, void *user);
int (*erase)(VTermRect rect, int selective, void *user);
int (*initpen)(void *user);
int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
int (*settermprop)(VTermProp prop, VTermValue *val, 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);
} VTermStateCallbacks;
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_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);
void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
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 {
unsigned int bold : 1;
unsigned int underline : 2;
unsigned int italic : 1;
unsigned int blink : 1;
unsigned int reverse : 1;
unsigned int strike : 1;
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) */
} VTermScreenCellAttrs;
enum {
VTERM_UNDERLINE_OFF,
VTERM_UNDERLINE_SINGLE,
VTERM_UNDERLINE_DOUBLE,
VTERM_UNDERLINE_CURLY,
};
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;
typedef struct {
int (*damage)(VTermRect rect, void *user);
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 (*bell)(void *user);
int (*resize)(int rows, int cols, void *user);
int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
} VTermScreenCallbacks;
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);
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);
void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);
void vterm_screen_reset(VTermScreen *screen, int hard);
/* Neither of these functions NUL-terminate the buffer */
size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);
typedef enum {
VTERM_ATTR_BOLD_MASK = 1 << 0,
VTERM_ATTR_UNDERLINE_MASK = 1 << 1,
VTERM_ATTR_ITALIC_MASK = 1 << 2,
VTERM_ATTR_BLINK_MASK = 1 << 3,
VTERM_ATTR_REVERSE_MASK = 1 << 4,
VTERM_ATTR_STRIKE_MASK = 1 << 5,
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);
int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);
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
// ---------
VTermValueType vterm_get_attr_type(VTermAttr attr);
VTermValueType vterm_get_prop_type(VTermProp prop);
void vterm_scroll_rect(VTermRect rect,
int downward,
int rightward,
int (*moverect)(VTermRect src, VTermRect dest, void *user),
int (*eraserect)(VTermRect rect, int selective, void *user),
void *user);
void vterm_copy_cells(VTermRect dest,
VTermRect src,
void (*copycell)(VTermPos dest, VTermPos src, void *user),
void *user);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,252 @@
#ifndef __VTERM_INTERNAL_H__
#define __VTERM_INTERNAL_H__
#include "vterm.h"
#include <stdarg.h>
#if defined(__GNUC__)
# define INTERNAL __attribute__((visibility("internal")))
#else
# 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 {
VTermEncoding *enc;
// This size should be increased if required by other stateful encodings
char data[4*sizeof(uint32_t)];
} VTermEncodingInstance;
struct VTermPen
{
VTermColor fg;
VTermColor bg;
unsigned int bold:1;
unsigned int underline:2;
unsigned int italic:1;
unsigned int blink:1;
unsigned int reverse:1;
unsigned int strike:1;
unsigned int font:4; /* To store 0-9 */
};
struct VTermState
{
VTerm *vt;
const VTermStateCallbacks *callbacks;
void *cbdata;
const VTermParserCallbacks *fallbacks;
void *fbdata;
int rows;
int cols;
/* Current cursor position */
VTermPos pos;
int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */
int scrollregion_top;
int scrollregion_bottom; /* -1 means unbounded */
#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows)
int scrollregion_left;
#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0)
int scrollregion_right; /* -1 means unbounded */
#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols)
/* Bitvector of tab stops */
unsigned char *tabstops;
VTermLineInfo *lineinfo;
#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)
#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)
/* Mouse state */
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 */
uint32_t *combine_chars;
size_t combine_chars_size; // Number of ELEMENTS in the above
int combine_width; // The width of the glyph above
VTermPos combine_pos; // Position before movement
struct {
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;
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;
int gl_set, gr_set, gsingle_set;
struct VTermPen pen;
VTermColor default_fg;
VTermColor default_bg;
VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only
int bold_is_highbright;
unsigned int protected_cell : 1;
/* Saved state under DEC mode 1048/1049 */
struct {
VTermPos pos;
struct VTermPen pen;
struct {
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;
void *allocdata;
int rows;
int cols;
struct {
unsigned int utf8:1;
unsigned int ctrl8bit:1;
} mode;
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 */
VTermOutputCallback *outfunc;
void *outdata;
char *outbuffer;
size_t outbuffer_len;
size_t outbuffer_cur;
char *tmpbuffer;
size_t tmpbuffer_len;
VTermState *state;
VTermScreen *screen;
};
struct VTermEncoding {
void (*init) (VTermEncoding *enc, void *data);
void (*decode)(VTermEncoding *enc, void *data,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t len);
};
typedef enum {
ENC_UTF8,
ENC_SINGLE_94
} VTermEncodingType;
void *vterm_allocator_malloc(VTerm *vt, size_t size);
void vterm_allocator_free(VTerm *vt, void *ptr);
void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len);
void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);
void vterm_push_output_sprintf(VTerm *vt, const char *format, ...);
void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);
void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...);
void vterm_state_free(VTermState *state);
void vterm_state_newpen(VTermState *state);
void vterm_state_resetpen(VTermState *state);
void vterm_state_setpen(VTermState *state, const long args[], int argcount);
int vterm_state_getpen(VTermState *state, long args[], int argcount);
void vterm_state_savepen(VTermState *state, int save);
enum {
C1_SS3 = 0x8f,
C1_DCS = 0x90,
C1_CSI = 0x9b,
C1_ST = 0x9c,
};
void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...);
void vterm_screen_free(VTermScreen *screen);
VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation);
int vterm_unicode_width(uint32_t codepoint);
int vterm_unicode_is_combining(uint32_t codepoint);
#endif

View File

@ -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