diff --git a/clients/terminal.c b/clients/terminal.c index ecbf0a4f..76a51a51 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -35,10 +35,6 @@ #include -#include "wayland-util.h" -#include "wayland-client.h" -#include "wayland-glib.h" - #include "window.h" static int option_fullscreen; @@ -47,48 +43,528 @@ static int option_fullscreen; #define MOD_ALT 0x02 #define MOD_CTRL 0x04 +#define ATTRMASK_UNDERLINE 0x80 +#define ATTRMASK_BACKGROUND 0x70 +#define ATTRSHIFT_BACKGROUND 4 +#define ATTRMASK_FOREGROUND 0x0F +#define ATTRSHIFT_FOREGROUND 0 +#define ATTRMASK_INTENSITY 0x08 + +/* UTF-8 characters are at most 4 chars wide */ +#define MAX_CHAR_WIDTH 4 + +/* Buffer sizes */ +#define MAX_RESPONSE 11 +#define MAX_ESCAPE 64 + +union utf8_char { + unsigned char byte[4]; + uint32_t ch; +}; + +enum utf8_state { + UTF8STATE_START, + UTF8STATE_ACCEPT, + UTF8STATE_REJECT, + UTF8STATE_EXPECT3, + UTF8STATE_EXPECT2, + UTF8STATE_EXPECT1 +}; + +struct utf8_state_machine { + enum utf8_state state; + int len; + union utf8_char s; +}; + +static void +init_state_machine(struct utf8_state_machine *machine) +{ + machine->state = UTF8STATE_START; + machine->len = 0; + memset(machine->s.byte, 0, 4); +} + +static enum utf8_state +utf8_next_char(struct utf8_state_machine *machine, char c) +{ + switch (machine->state) { + case UTF8STATE_START: + case UTF8STATE_ACCEPT: + case UTF8STATE_REJECT: + memset(machine->s.byte, 0, 4); + machine->len = 0; + if(c == 0xC0 || c == 0xC1) { + /* overlong encoding, reject */ + machine->state = UTF8STATE_REJECT; + } else if((c & 0x80) == 0) { + /* single byte, accept */ + machine->s.byte[machine->len++] = c; + machine->state = UTF8STATE_ACCEPT; + } else if((c & 0xC0) == 0x80) { + /* parser out of sync, ignore byte */ + machine->state = UTF8STATE_START; + } else if((c & 0xE0) == 0xC0) { + /* start of two byte sequence */ + machine->s.byte[machine->len++] = c; + machine->state = UTF8STATE_EXPECT1; + } else if((c & 0xF0) == 0xE0) { + /* start of three byte sequence */ + machine->s.byte[machine->len++] = c; + machine->state = UTF8STATE_EXPECT2; + } else if((c & 0xF8) == 0xF0) { + /* start of four byte sequence */ + machine->s.byte[machine->len++] = c; + machine->state = UTF8STATE_EXPECT3; + } else { + /* overlong encoding, reject */ + machine->state = UTF8STATE_REJECT; + } + break; + case UTF8STATE_EXPECT3: + machine->s.byte[machine->len++] = c; + if((c & 0xC0) == 0x80) { + /* all good, continue */ + machine->state = UTF8STATE_EXPECT2; + } else { + /* missing extra byte, reject */ + machine->state = UTF8STATE_REJECT; + } + break; + case UTF8STATE_EXPECT2: + machine->s.byte[machine->len++] = c; + if((c & 0xC0) == 0x80) { + /* all good, continue */ + machine->state = UTF8STATE_EXPECT1; + } else { + /* missing extra byte, reject */ + machine->state = UTF8STATE_REJECT; + } + break; + case UTF8STATE_EXPECT1: + machine->s.byte[machine->len++] = c; + if((c & 0xC0) == 0x80) { + /* all good, accept */ + machine->state = UTF8STATE_ACCEPT; + } else { + /* missing extra byte, reject */ + machine->state = UTF8STATE_REJECT; + } + break; + default: + machine->state = UTF8STATE_REJECT; + break; + } + + return machine->state; +} + +struct character_set { + unsigned char match[4]; + unsigned char replace[4]; +}; +/* Set last character_set match to NULL char */ + +struct character_set CS_US[] = { + { { 0, }, { 0, } } +}; +static struct character_set CS_UK[] = { + { { '#', 0, }, { 0xC2, 0xA3, 0, } }, + { { 0, }, { 0, } } +}; +static struct character_set CS_SPECIAL[] = { + { { '`', 0, }, { 0xE2, 0x99, 0xA6, 0} }, /* diamond */ + { { 'a', 0, }, { 0xE2, 0x96, 0x92, 0} }, /* 50% cell */ + { { 'b', 0, }, { 0xE2, 0x90, 0x89, 0} }, /* HT */ + { { 'c', 0, }, { 0xE2, 0x90, 0x8C, 0} }, /* FF */ + { { 'd', 0, }, { 0xE2, 0x90, 0x8D, 0} }, /* CR */ + { { 'e', 0, }, { 0xE2, 0x90, 0x8A, 0} }, /* LF */ + { { 'f', 0, }, { 0xC2, 0xB0, 0, } }, /* Degree */ + { { 'g', 0, }, { 0xC2, 0xB1, 0, } }, /* Plus/Minus */ + { { 'h', 0, }, { 0xE2, 0x90, 0xA4, 0} }, /* NL */ + { { 'i', 0, }, { 0xE2, 0x90, 0x8B, 0} }, /* VT */ + { { 'j', 0, }, { 0xE2, 0x94, 0x98, 0} }, /* CN_RB */ + { { 'k', 0, }, { 0xE2, 0x94, 0x90, 0} }, /* CN_RT */ + { { 'l', 0, }, { 0xE2, 0x94, 0x8C, 0} }, /* CN_LT */ + { { 'm', 0, }, { 0xE2, 0x94, 0x94, 0} }, /* CN_RB */ + { { 'n', 0, }, { 0xE2, 0x94, 0xBC, 0} }, /* CROSS */ + { { 'o', 0, }, { 0xE2, 0x94, 0x80, 0} }, /* H */ + { { 'p', 0, }, { 0xE2, 0x94, 0x80, 0} }, /* H */ + { { 'q', 0, }, { 0xE2, 0x94, 0x80, 0} }, /* H */ + { { 'r', 0, }, { 0xE2, 0x94, 0x80, 0} }, /* H */ + { { 's', 0, }, { 0xE2, 0x94, 0x80, 0} }, /* H */ + { { 't', 0, }, { 0xE2, 0x94, 0x9C, 0} }, /* TR */ + { { 'u', 0, }, { 0xE2, 0x94, 0xA4, 0} }, /* TL */ + { { 'v', 0, }, { 0xE2, 0x94, 0xB4, 0} }, /* TU */ + { { 'w', 0, }, { 0xE2, 0x94, 0xAC, 0} }, /* TD */ + { { 'x', 0, }, { 0xE2, 0x94, 0x82, 0} }, /* V */ + { { 'y', 0, }, { 0xE2, 0x89, 0xA4, 0} }, /* LE */ + { { 'z', 0, }, { 0xE2, 0x89, 0xA5, 0} }, /* GE */ + { { '{', 0, }, { 0xCF, 0x80, 0, } }, /* PI */ + { { '|', 0, }, { 0xE2, 0x89, 0xA0, 0} }, /* NEQ */ + { { '}', 0, }, { 0xC2, 0xA3, 0, } }, /* POUND */ + { { '~', 0, }, { 0xE2, 0x8B, 0x85, 0} }, /* DOT */ + { { 0, }, { 0, } } +}; + +static void +apply_char_set(struct character_set * cs, unsigned char *c) +{ + unsigned char *match; + int i = 0; + + while (*(cs[i].match)) { + match = cs[i++].match; + if (*((uint32_t*) c) == *((uint32_t*) match)) { + memcpy(c, cs[i-1].replace, 4); + break; + } + } +} + +struct keyboard_map { + int sym; + char response[MAX_RESPONSE]; +}; +/* Set last key_sub sym to NULL */ + +static struct keyboard_map KM_NORMAL[] = { + { XK_Left, "\e[D" }, + { XK_Right, "\e[C" }, + { XK_Up, "\e[A" }, + { XK_Down, "\e[B" }, + { XK_Home, "\e[H" }, + { XK_End, "\e[E" }, + { 0, "" } +}; +static struct keyboard_map KM_APPLICATION[] = { + { XK_Left, "\eOD" }, + { XK_Right, "\eOC" }, + { XK_Up, "\eOA" }, + { XK_Down, "\eOB" }, + { XK_Home, "\eOH" }, + { XK_End, "\eOE" }, + { 0, ""} +}; + +/* returns the number of bytes written into response, + * which must have room for MAX_RESPONSE bytes */ +static int +apply_key_map(struct keyboard_map *mode, int sym, char *response) +{ + struct keyboard_map map; + int len = 0; + int i = 0; + + while (mode[i].sym) { + map = mode[i++]; + if (sym == map.sym) { + len = strlen(map.response); + memcpy(response, map.response, len + 1); + break; + } + } + + return len; +} + +struct terminal_color { double r, g, b, a; }; +struct color_scheme { + struct terminal_color palette[16]; + struct terminal_color border; + char default_attr; +}; + struct terminal { struct window *window; struct display *display; - char *data; + union utf8_char *data; + char *tab_ruler; + char *data_attr; /* one byte per cell, format: + * 76543210 + * ugbrigbr + * BBBFFFF + * where u = underline bit, + * B = background color bit + * F = foreground color bit + * i = intensity bit + * but it isn't enough, will need + * to expand this to two bytes + * at some stage. */ + /* These should be refactored into some flags */ + char curr_attr; + char inverse_video; /* SGR inverse is on */ + char origin_mode; /* 0 for abs, 1 for rel */ + char show_cursor; + + char autowrap; + char normal_newline; /* 0 for , 1 for */ + char inverse_mode; /* mode ?5 (inverse video) is on */ + char irm; /* mode 4 (insert/replace) is on */ + + char deleteSendsDel; + char altSendsEsc; + + char auto_repeat; + char saved_attr; + char saved_inverse_video; + char saved_origin_mode; + + union utf8_char last_char; + + int margin_top, margin_bottom; + + struct character_set *cs, *g0, *g1; + struct character_set *saved_cs, *saved_g0, *saved_g1; + struct keyboard_map *key_mode; + + int pitch; /* The width in bytes of a line */ int width, height, start, row, column; + int saved_row, saved_column; int fd, master; GIOChannel *channel; uint32_t modifiers; - char escape[64]; + char escape[MAX_ESCAPE]; int escape_length; int state; + int qmark_flag; + + struct utf8_state_machine state_machine; + int margin; int fullscreen; int focused; struct color_scheme *color_scheme; cairo_font_extents_t extents; + cairo_font_face_t *font_normal, *font_bold; }; -static char * +/* Create default tab stops, every 8 characters */ +static void +terminal_init_tabs(struct terminal *terminal) +{ + int i = 0; + + while (i < terminal->width) { + if (i % 8 == 0) + terminal->tab_ruler[i] = 1; + else + terminal->tab_ruler[i] = 0; + i++; + } +} + +static void +terminal_init(struct terminal *terminal) +{ + terminal->curr_attr = terminal->color_scheme->default_attr; + terminal->inverse_video = 0; + terminal->origin_mode = 0; + terminal->show_cursor = 1; + terminal->autowrap = 1; + terminal->normal_newline = 0; + terminal->inverse_mode = 0; + terminal->irm = 0; + terminal->auto_repeat = 1; + + terminal->deleteSendsDel = 0; + terminal->altSendsEsc = 1; + + terminal->saved_attr = terminal->curr_attr; + terminal->saved_inverse_video = terminal->inverse_video; + terminal->saved_origin_mode = terminal->origin_mode; + + terminal->margin_top = 0; + terminal->margin_bottom = terminal->height-1; + + terminal->row = 0; + terminal->column = 0; + + terminal->g0 = CS_US; + terminal->g1 = CS_US; + terminal->cs = terminal->g0; + + terminal->key_mode = KM_NORMAL; + + terminal->saved_g0 = terminal->g0; + terminal->saved_g1 = terminal->g1; + terminal->saved_cs = terminal->cs; + + if (terminal->tab_ruler != NULL) + terminal_init_tabs(terminal); +} + +static union utf8_char * terminal_get_row(struct terminal *terminal, int row) { int index; index = (row + terminal->start) % terminal->height; - return &terminal->data[index * (terminal->width + 1)]; + return &terminal->data[index * terminal->width]; +} + +static char* +terminal_get_attr_row(struct terminal *terminal, int row) +{ + int index; + + index = (row + terminal->start) % terminal->height; + + return &terminal->data_attr[index * terminal->width]; +} + +static char +terminal_get_attr(struct terminal *terminal, int row, int col) +{ + return terminal_get_attr_row(terminal, row)[col]; +} + +static void +terminal_scroll_buffer(struct terminal *terminal, int d) +{ + int i; + + d = d % (terminal->height + 1); + terminal->start = (terminal->start + d) % terminal->height; + if (terminal->start < 0) terminal->start = terminal->height + terminal->start; + if(d < 0) { + d = 0 - d; + for(i = 0; i < d; i++) { + memset(terminal_get_row(terminal, i), 0, terminal->pitch); + memset(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } else { + for(i = terminal->height - d; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), 0, terminal->pitch); + memset(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } +} + +static void +terminal_scroll_window(struct terminal *terminal, int d) +{ + int i; + int window_height; + int from_row, to_row; + char *dup_attr; + + /* scrolling range is inclusive */ + window_height = terminal->margin_bottom - terminal->margin_top + 1; + d = d % (window_height + 1); + if(d < 0) { + d = 0 - d; + to_row = terminal->margin_bottom; + from_row = terminal->margin_bottom - d; + + for (i = 0; i < (window_height - d); i++) { + memcpy(terminal_get_row(terminal, to_row - i), + terminal_get_row(terminal, from_row - i), + terminal->pitch); + memcpy(terminal_get_attr_row(terminal, to_row - i), + terminal_get_attr_row(terminal, from_row - i), + terminal->width); + } + dup_attr = terminal_get_attr_row(terminal, terminal->margin_top); + for (i = terminal->margin_top; i < (terminal->margin_top + d); i++) { + memset(terminal_get_row(terminal, i), 0, terminal->pitch); + if (i > terminal->margin_top) { + memcpy(terminal_get_attr_row(terminal, i), + dup_attr, terminal->width); + } + } + } else { + to_row = terminal->margin_top; + from_row = terminal->margin_top + d; + + for (i = 0; i < (window_height - d); i++) { + memcpy(terminal_get_row(terminal, to_row + i), + terminal_get_row(terminal, from_row + i), + terminal->pitch); + memcpy(terminal_get_attr_row(terminal, to_row + i), + terminal_get_attr_row(terminal, from_row + i), + terminal->width); + } + dup_attr = terminal_get_attr_row(terminal, terminal->margin_bottom); + for (i = terminal->margin_bottom - d + 1; i <= terminal->margin_bottom; i++) { + memset(terminal_get_row(terminal, i), 0, terminal->pitch); + if (i < terminal->margin_bottom) { + memcpy(terminal_get_attr_row(terminal, i), + dup_attr, terminal->width); + } + } + } +} + +static void +terminal_scroll(struct terminal *terminal, int d) +{ + if(terminal->margin_top == 0 && terminal->margin_bottom == terminal->height - 1) + terminal_scroll_buffer(terminal, d); + else + terminal_scroll_window(terminal, d); +} + +static void +terminal_shift_line(struct terminal *terminal, int d) +{ + union utf8_char *row; + char *attr_row, attr; + + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + + if ((terminal->width + d) <= terminal->column) + d = terminal->column + 1 - terminal->width; + if ((terminal->column + d) >= terminal->width) + d = terminal->width - terminal->column - 1; + + if (d < 0) { + d = 0 - d; + memmove(&row[terminal->column], + &row[terminal->column + d], + (terminal->width - terminal->column - d) * MAX_CHAR_WIDTH); + attr = attr_row[terminal->width - 1]; + memmove(&attr_row[terminal->column], &attr_row[terminal->column + d], + terminal->width - terminal->column - d); + memset(&row[terminal->width - d], 0, d * MAX_CHAR_WIDTH); + memset(&attr_row[terminal->width - d], terminal->curr_attr, d); + } else { + memmove(&row[terminal->column + d], &row[terminal->column], + (terminal->width - terminal->column - d) * MAX_CHAR_WIDTH); + memmove(&attr_row[terminal->column + d], &attr_row[terminal->column], + terminal->width - terminal->column - d); + memset(&row[terminal->column], 0, d * MAX_CHAR_WIDTH); + memset(&attr_row[terminal->column], terminal->curr_attr, d); + } } static void terminal_resize(struct terminal *terminal, int width, int height) { size_t size; - char *data; + union utf8_char *data; + char *data_attr, *tab_ruler; + int pitch; int i, l, total_rows, start; + struct rectangle rectangle; + struct winsize ws; if (terminal->width == width && terminal->height == height) return; - size = (width + 1) * height; + pitch = width * sizeof(union utf8_char); + size = pitch * height; data = malloc(size); + data_attr = malloc(width * height); + tab_ruler = malloc(width); memset(data, 0, size); - if (terminal->data) { + memset(tab_ruler, 0, width); + memset(data_attr, terminal->curr_attr, width * height); + if (terminal->data && terminal->data_attr && terminal->tab_ruler) { if (width > terminal->width) l = terminal->width; else @@ -102,27 +578,74 @@ terminal_resize(struct terminal *terminal, int width, int height) start = 0; } - for (i = 0; i < total_rows; i++) - memcpy(data + (width + 1) * i, + memcpy(tab_ruler, terminal->tab_ruler, l); + for (i = 0; i < total_rows; i++) { + memcpy(&data[width * i], terminal_get_row(terminal, i), l); + memcpy(&data_attr[width * i], + terminal_get_attr_row(terminal, i), l); + } free(terminal->data); + free(terminal->data_attr); + free(terminal->tab_ruler); } + terminal->pitch = pitch; terminal->width = width; terminal->height = height; + if(terminal->margin_bottom >= terminal->height) + terminal->margin_bottom = terminal->height - 1; terminal->data = data; + terminal->data_attr = data_attr; + terminal->tab_ruler = tab_ruler; + terminal_init_tabs(terminal); if (terminal->row >= terminal->height) terminal->row = terminal->height - 1; if (terminal->column >= terminal->width) terminal->column = terminal->width - 1; terminal->start = 0; + + if (!terminal->fullscreen) { + rectangle.width = terminal->width * + terminal->extents.max_x_advance + 2 * terminal->margin; + rectangle.height = terminal->height * + terminal->extents.height + 2 * terminal->margin; + window_set_child_size(terminal->window, &rectangle); + } + + /* Update the window size */ + ws.ws_row = terminal->height; + ws.ws_col = terminal->width; + window_get_child_rectangle(terminal->window, &rectangle); + ws.ws_xpixel = rectangle.width; + ws.ws_ypixel = rectangle.height; + ioctl(terminal->master, TIOCSWINSZ, &ws); } -struct color_scheme { struct { double r, g, b, a; } fg, bg; } - matrix_colors = { { 0, 0.7, 0, 1 }, { 0, 0, 0, 0.9 } }, - jbarnes_colors = { { 1, 1, 1, 1 }, { 0, 0, 0, 1 } }; +struct color_scheme DEFAULT_COLORS = { + { + { 0, 0, 0, 1 }, /* black */ + { 0.66, 0, 0, 1 }, /* red */ + { 0 , 0.66, 0, 1 }, /* green */ + { 0.66, 0.33, 0, 1 }, /* orange (nicer than muddy yellow) */ + { 0 , 0 , 0.66, 1 }, /* blue */ + { 0.66, 0 , 0.66, 1 }, /* magenta */ + { 0, 0.66, 0.66, 1 }, /* cyan */ + { 0.66, 0.66, 0.66, 1 }, /* light grey */ + { 0.22, 0.33, 0.33, 1 }, /* dark grey */ + { 1, 0.33, 0.33, 1 }, /* high red */ + { 0.33, 1, 0.33, 1 }, /* high green */ + { 1, 1, 0.33, 1 }, /* high yellow */ + { 0.33, 0.33, 1, 1 }, /* high blue */ + { 1, 0.33, 1, 1 }, /* high magenta */ + { 0.33, 1, 1, 1 }, /* high cyan */ + { 1, 1, 1, 1 } /* white */ + }, + { 0, 0, 0, 1 }, /* black border */ + 0x07 /* bg:black, fg:light gray */ +}; static void terminal_draw_contents(struct terminal *terminal) @@ -130,7 +653,12 @@ terminal_draw_contents(struct terminal *terminal) struct rectangle rectangle; cairo_t *cr; cairo_font_extents_t extents; - int i, top_margin, side_margin; + int top_margin, side_margin; + int row, col; + char attr; + char toShow[5] = {0, }; + int foreground, background, bold, underline, tmp; + int text_x, text_y; cairo_surface_t *surface; double d; @@ -138,49 +666,93 @@ terminal_draw_contents(struct terminal *terminal) surface = display_create_surface(terminal->display, &rectangle); cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, - terminal->color_scheme->bg.r, - terminal->color_scheme->bg.g, - terminal->color_scheme->bg.b, - terminal->color_scheme->bg.a); + terminal->color_scheme->border.r, + terminal->color_scheme->border.g, + terminal->color_scheme->border.b, + terminal->color_scheme->border.a); cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - cairo_set_source_rgba(cr, - terminal->color_scheme->fg.r, - terminal->color_scheme->fg.g, - terminal->color_scheme->fg.b, - terminal->color_scheme->fg.a); - - cairo_select_font_face (cr, "mono", - CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_face(cr, terminal->font_normal); cairo_set_font_size(cr, 14); - cairo_font_extents(cr, &extents); + side_margin = (rectangle.width - terminal->width * extents.max_x_advance) / 2; top_margin = (rectangle.height - terminal->height * extents.height) / 2; - for (i = 0; i < terminal->height; i++) { - cairo_move_to(cr, side_margin, - top_margin + extents.ascent + extents.height * i); - cairo_show_text(cr, terminal_get_row(terminal, i)); + cairo_set_line_width(cr, 1.0); + cairo_set_antialias(cr, CAIRO_ANTIALIAS_SUBPIXEL); + + for (row = 0; row < terminal->height; row++) + for (col = 0; col < terminal->width; col++) + { + /* get the attributes for this character cell */ + attr = terminal_get_attr(terminal, row, col); + foreground = (attr & ATTRMASK_FOREGROUND) >> ATTRSHIFT_FOREGROUND; + background = (attr & ATTRMASK_BACKGROUND) >> ATTRSHIFT_BACKGROUND; + if (terminal->inverse_mode || + (terminal->show_cursor && terminal->focused && + terminal->row == row && terminal->column == col)) { + tmp = foreground; + foreground = background | (foreground & ATTRMASK_INTENSITY); + background = tmp & ~ATTRMASK_INTENSITY; + } + bold = (attr & ATTRMASK_INTENSITY); + underline = (attr & ATTRMASK_UNDERLINE); + + /* paint the background */ + cairo_set_source_rgba(cr, + terminal->color_scheme->palette[background].r, + terminal->color_scheme->palette[background].g, + terminal->color_scheme->palette[background].b, + terminal->color_scheme->palette[background].a); + cairo_move_to(cr, side_margin + (col * extents.max_x_advance), + top_margin + (row * extents.height)); + cairo_rel_line_to(cr, extents.max_x_advance, 0); + cairo_rel_line_to(cr, 0, extents.height); + cairo_rel_line_to(cr, -extents.max_x_advance, 0); + cairo_close_path(cr); + cairo_fill(cr); + + /* paint the foreground */ + if (bold) + cairo_set_font_face(cr, terminal->font_bold); + else + cairo_set_font_face(cr, terminal->font_normal); + cairo_set_source_rgba(cr, + terminal->color_scheme->palette[foreground].r, + terminal->color_scheme->palette[foreground].g, + terminal->color_scheme->palette[foreground].b, + terminal->color_scheme->palette[foreground].a); + + text_x = side_margin + col * extents.max_x_advance; + text_y = top_margin + extents.ascent + row * extents.height; + if (underline) { + cairo_move_to(cr, text_x, text_y + 2); + cairo_line_to(cr, text_x + extents.max_x_advance, text_y + 2); + cairo_stroke(cr); + } + cairo_move_to(cr, text_x, text_y); + + memcpy(toShow, &terminal_get_row(terminal, row)[col], 4); + cairo_show_text(cr, toShow); } - d = terminal->focused ? 0 : 0.5; + if (terminal->show_cursor && !terminal->focused) { + d = 0.5; - cairo_set_line_width(cr, 1); - cairo_move_to(cr, side_margin + terminal->column * extents.max_x_advance + d, - top_margin + terminal->row * extents.height + d); - cairo_rel_line_to(cr, extents.max_x_advance - 2 * d, 0); - cairo_rel_line_to(cr, 0, extents.height - 2 * d); - cairo_rel_line_to(cr, -extents.max_x_advance + 2 * d, 0); - cairo_close_path(cr); - - if (terminal->focused) - cairo_fill(cr); - else + cairo_set_line_width(cr, 1); + cairo_move_to(cr, side_margin + (terminal->column * extents.max_x_advance) + d, + top_margin + (terminal->row * extents.height) + d); + cairo_rel_line_to(cr, extents.max_x_advance - (2 * d), 0); + cairo_rel_line_to(cr, 0, extents.height - (2 * d)); + cairo_rel_line_to(cr, -extents.max_x_advance + (2 * d), 0); + cairo_close_path(cr); cairo_stroke(cr); + } cairo_destroy(cr); @@ -205,14 +777,6 @@ terminal_draw(struct terminal *terminal) (int32_t) terminal->extents.height; terminal_resize(terminal, width, height); - if (!terminal->fullscreen) { - rectangle.width = terminal->width * - terminal->extents.max_x_advance + 2 * terminal->margin; - rectangle.height = terminal->height * - terminal->extents.height + 2 * terminal->margin; - window_set_child_size(terminal->window, &rectangle); - } - window_draw(terminal->window); terminal_draw_contents(terminal); window_flush(terminal->window); @@ -228,22 +792,111 @@ redraw_handler(struct window *window, void *data) #define STATE_NORMAL 0 #define STATE_ESCAPE 1 +#define STATE_ESCAPE_SPECIAL 2 +#define STATE_ESCAPE_CSI 3 static void terminal_data(struct terminal *terminal, const char *data, size_t length); +static void +handle_char(struct terminal *terminal, union utf8_char utf8); + +static void +handle_sgr(struct terminal *terminal, int code); + +static void +handle_term_parameter(struct terminal *terminal, int code, int sr) +{ + int i; + + if (terminal->qmark_flag) { + switch(code) { + case 1: /* DECCKM */ + if (sr) + terminal->key_mode = KM_APPLICATION; + else + terminal->key_mode = KM_NORMAL; + break; + case 2: /* DECANM */ + /* No VT52 support yet */ + terminal->g0 = CS_US; + terminal->g1 = CS_US; + terminal->cs = terminal->g0; + break; + case 3: /* DECCOLM */ + if (sr) + terminal_resize(terminal, 132, 24); + else + terminal_resize(terminal, 80, 24); + + /* set columns, but also home cursor and clear screen */ + terminal->row = 0; terminal->column = 0; + for (i = 0; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->pitch); + memset(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + break; + case 5: /* DECSCNM */ + terminal->inverse_mode = sr; + break; + case 6: /* DECOM */ + terminal->origin_mode = sr; + if (terminal->origin_mode) + terminal->row = terminal->margin_top; + else + terminal->row = 0; + terminal->column = 0; + break; + case 7: /* DECAWM */ + terminal->autowrap = sr; + break; + case 8: /* DECARM */ + terminal->auto_repeat = sr; + break; + case 25: + terminal->show_cursor = sr; + break; + case 1037: /* deleteSendsDel */ + terminal->deleteSendsDel = sr; + break; + case 1039: /* altSendsEscape */ + terminal->altSendsEsc = sr; + break; + default: + break; + } + } else { + switch(code) { + case 4: /* IRM */ + terminal->irm = sr; + break; + case 20: /* LNM */ + terminal->normal_newline = sr; + break; + } + } +} + static void handle_escape(struct terminal *terminal) { - char *row, *p; - int i, count; + union utf8_char *row; + char *p, *attr_row; + int i, count, x, y, top, bottom; int args[10], set[10] = { 0, }; + char response[MAX_RESPONSE] = {0, }; terminal->escape[terminal->escape_length++] = '\0'; i = 0; p = &terminal->escape[2]; while ((isdigit(*p) || *p == ';') && i < 10) { if (*p == ';') { + if (!set[i]) { + args[i] = 0; + set[i] = 1; + } p++; i++; } else { @@ -251,135 +904,697 @@ handle_escape(struct terminal *terminal) set[i] = 1; } } - + switch (*p) { - case 'A': + case '@': /* ICH */ count = set[0] ? args[0] : 1; - if (terminal->row - count >= 0) + if (count == 0) count = 1; + terminal_shift_line(terminal, count); + break; + case 'A': /* CUU */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->row - count >= terminal->margin_top) terminal->row -= count; else - terminal->row = 0; + terminal->row = terminal->margin_top; break; - case 'B': + case 'B': /* CUD */ count = set[0] ? args[0] : 1; - if (terminal->row + count < terminal->height) + if (count == 0) count = 1; + if (terminal->row + count <= terminal->margin_bottom) terminal->row += count; else - terminal->row = terminal->height; + terminal->row = terminal->margin_bottom; break; - case 'C': + case 'C': /* CUF */ count = set[0] ? args[0] : 1; - if (terminal->column + count < terminal->width) + if (count == 0) count = 1; + if ((terminal->column + count) < terminal->width) terminal->column += count; else - terminal->column = terminal->width; + terminal->column = terminal->width - 1; break; - case 'D': + case 'D': /* CUB */ count = set[0] ? args[0] : 1; - if (terminal->column - count >= 0) + if (count == 0) count = 1; + if ((terminal->column - count) >= 0) terminal->column -= count; else terminal->column = 0; break; - case 'J': + case 'E': /* CNL */ + count = set[0] ? args[0] : 1; + if(terminal->row + count <= terminal->margin_bottom) + terminal->row += count; + else + terminal->row = terminal->margin_bottom; + terminal->column = 0; + break; + case 'F': /* CPL */ + count = set[0] ? args[0] : 1; + if(terminal->row - count >= terminal->margin_top) + terminal->row -= count; + else + terminal->row = terminal->margin_top; + terminal->column = 0; + break; + case 'G': /* CHA */ + y = set[0] ? args[0] : 1; + y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; + + terminal->column = y - 1; + break; + case 'f': /* HVP */ + case 'H': /* CUP */ + x = (set[1] ? args[1] : 1) - 1; + x = x < 0 ? 0 : + (x >= terminal->width ? terminal->width - 1 : x); + + y = (set[0] ? args[0] : 1) - 1; + if(terminal->origin_mode) { + y += terminal->margin_top; + y = y < terminal->margin_top ? terminal->margin_top : + (y > terminal->margin_bottom ? terminal->margin_bottom : y); + } else { + y = y < 0 ? 0 : + (y >= terminal->height ? terminal->height - 1 : y); + } + + terminal->row = y; + terminal->column = x; + break; + case 'I': /* CHT */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + while (count > 0 && terminal->column < terminal->width) { + if (terminal->tab_ruler[terminal->column]) count--; + terminal->column++; + } + terminal->column--; + break; + case 'J': /* ED */ row = terminal_get_row(terminal, terminal->row); - memset(&row[terminal->column], 0, terminal->width - terminal->column); - for (i = terminal->row + 1; i < terminal->height; i++) - memset(terminal_get_row(terminal, i), 0, terminal->width); - break; - case 'G': - if (set[0]) - terminal->column = args[0] - 1; - break; - case 'H': - case 'f': - terminal->row = set[0] ? args[0] - 1 : 0; - terminal->column = set[1] ? args[1] - 1 : 0; - break; - case 'K': - row = terminal_get_row(terminal, terminal->row); - memset(&row[terminal->column], 0, terminal->width - terminal->column); - break; - case 'm': - /* color, blink, bold etc*/ - break; - case '?': - if (strcmp(p, "?25l") == 0) { - /* hide cursor */ - } else if (strcmp(p, "?25h") == 0) { - /* show cursor */ + attr_row = terminal_get_attr_row(terminal, terminal->row); + if(!set[0] || args[0] == 0 || args[0] > 2) { + memset(&row[terminal->column], + 0, (terminal->width - terminal->column) * MAX_CHAR_WIDTH); + memset(&attr_row[terminal->column], + terminal->curr_attr, terminal->width - terminal->column); + for (i = terminal->row + 1; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->pitch); + memset(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } else if(args[0] == 1) { + memset(row, 0, (terminal->column+1) * MAX_CHAR_WIDTH); + memset(attr_row, terminal->curr_attr, terminal->column+1); + for (i = 0; i < terminal->row; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->pitch); + memset(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } else if(args[0] == 2) { + for (i = 0; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->pitch); + memset(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } } break; - default: - terminal_data(terminal, - terminal->escape + 1, - terminal->escape_length - 2); + case 'K': /* EL */ + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + if(!set[0] || args[0] == 0 || args[0] > 2) { + memset(&row[terminal->column], 0, + (terminal->width - terminal->column) * MAX_CHAR_WIDTH); + memset(&attr_row[terminal->column], terminal->curr_attr, + terminal->width - terminal->column); + } else if(args[0] == 1) { + memset(row, 0, (terminal->column+1) * MAX_CHAR_WIDTH); + memset(attr_row, terminal->curr_attr, terminal->column+1); + } else if(args[0] == 2) { + memset(row, 0, terminal->pitch); + memset(attr_row, terminal->curr_attr, terminal->width); + } break; - } + case 'L': /* IL */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->row >= terminal->margin_top && + terminal->row < terminal->margin_bottom) + { + top = terminal->margin_top; + terminal->margin_top = terminal->row; + terminal_scroll(terminal, 0 - count); + terminal->margin_top = top; + } else if (terminal->row == terminal->margin_bottom) { + memset(terminal_get_row(terminal, terminal->row), + 0, terminal->pitch); + memset(terminal_get_attr_row(terminal, terminal->row), + terminal->curr_attr, terminal->width); + } + break; + case 'M': /* DL */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->row >= terminal->margin_top && + terminal->row < terminal->margin_bottom) + { + top = terminal->margin_top; + terminal->margin_top = terminal->row; + terminal_scroll(terminal, count); + terminal->margin_top = top; + } else if (terminal->row == terminal->margin_bottom) { + memset(terminal_get_row(terminal, terminal->row), + 0, terminal->pitch); + } + break; + case 'P': /* DCH */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + terminal_shift_line(terminal, 0 - count); + break; + case 'S': /* SU */ + terminal_scroll(terminal, set[0] ? args[0] : 1); + break; + case 'T': /* SD */ + terminal_scroll(terminal, 0 - (set[0] ? args[0] : 1)); + break; + case 'X': /* ECH */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if ((terminal->column + count) > terminal->width) + count = terminal->width - terminal->column; + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + memset(&row[terminal->column], 0, count * MAX_CHAR_WIDTH); + memset(&attr_row[terminal->column], terminal->curr_attr, count); + break; + case 'Z': /* CBT */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + while (count > 0 && terminal->column >= 0) { + if (terminal->tab_ruler[terminal->column]) count--; + terminal->column--; + } + terminal->column++; + break; + case '`': /* HPA */ + y = set[0] ? args[0] : 1; + y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; + + terminal->column = y - 1; + break; + case 'b': /* REP */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->last_char.byte[0]) + for (i = 0; i < count; i++) + handle_char(terminal, terminal->last_char); + terminal->last_char.byte[0] = 0; + break; + case 'c': /* Primary DA */ + write(terminal->master, "\e[?1;2c", 7); + sleep(1); + break; + case 'd': /* VPA */ + x = set[0] ? args[0] : 1; + x = x <= 0 ? 1 : x > terminal->height ? terminal->height : x; + + terminal->row = x - 1; + break; + case 'g': /* TBC */ + if (!set[0] || args[0] == 0) { + terminal->tab_ruler[terminal->column] = 0; + } else if (args[0] == 3) { + memset(terminal->tab_ruler, 0, terminal->width); + } + break; + case 'h': /* SM */ + for(i = 0; i < 10 && set[i]; i++) { + handle_term_parameter(terminal, args[i], 1); + } + break; + case 'l': /* RM */ + for(i = 0; i < 10 && set[i]; i++) { + handle_term_parameter(terminal, args[i], 0); + } + break; + case 'm': /* SGR */ + for(i = 0; i < 10; i++) { + if(set[i]) { + handle_sgr(terminal, args[i]); + } else if(i == 0) { + handle_sgr(terminal, 0); + break; + } else { + break; + } + } + break; + case 'n': /* DSR */ + i = set[0] ? args[0] : 0; + if (i == 0 || i == 5) { + write(terminal->master, "\e[0n", 4); + } else if (i == 6) { + snprintf(response, MAX_RESPONSE, "\e[%d;%dR", + terminal->origin_mode ? + terminal->row+terminal->margin_top : terminal->row+1, + terminal->column+1); + write(terminal->master, response, strlen(response)); + } + sleep(1); /* is this required? why? */ + break; + case 'r': + if(!set[0]) { + terminal->margin_top = 0; + terminal->margin_bottom = terminal->height-1; + terminal->row = 0; + terminal->column = 0; + } else { + top = (set[0] ? args[0] : 1) - 1; + top = top < 0 ? 0 : + (top >= terminal->height ? terminal->height - 1 : top); + bottom = (set[1] ? args[1] : 1) - 1; + bottom = bottom < 0 ? 0 : + (bottom >= terminal->height ? terminal->height - 1 : bottom); + if(bottom > top) { + terminal->margin_top = top; + terminal->margin_bottom = bottom; + } else { + terminal->margin_top = 0; + terminal->margin_bottom = terminal->height-1; + } + if(terminal->origin_mode) + terminal->row = terminal->margin_top; + else + terminal->row = 0; + terminal->column = 0; + } + break; + case 's': + terminal->saved_row = terminal->row; + terminal->saved_column = terminal->column; + break; + case 'u': + terminal->row = terminal->saved_row; + terminal->column = terminal->saved_column; + break; + default: + break; + } +} + +static char +inverse_video(struct terminal *terminal, char attr) { + int foreground, background; + + foreground = (attr & (~ATTRMASK_INTENSITY & ATTRMASK_FOREGROUND)) >> ATTRSHIFT_FOREGROUND; + background = (attr & ATTRMASK_BACKGROUND) >> ATTRSHIFT_BACKGROUND; + return (attr & ~(ATTRMASK_FOREGROUND & ~ATTRMASK_INTENSITY) & ~ATTRMASK_BACKGROUND) | + (foreground << ATTRSHIFT_BACKGROUND) | (background << ATTRSHIFT_FOREGROUND); +} + +static void +handle_sgr(struct terminal *terminal, int code) +{ + int foreground, background; + + switch(code) { + case 0: + terminal->curr_attr = terminal->color_scheme->default_attr; + terminal->inverse_video = 0; + break; + case 1: + terminal->curr_attr |= ATTRMASK_INTENSITY; + break; + case 4: + terminal->curr_attr |= ATTRMASK_UNDERLINE; + break; + case 2: + case 21: + case 22: + terminal->curr_attr &= ~ATTRMASK_INTENSITY; + break; + case 24: + terminal->curr_attr &= ~ATTRMASK_UNDERLINE; + break; + case 7: + case 26: + if(!terminal->inverse_video) + terminal->curr_attr = + inverse_video(terminal, terminal->curr_attr); + terminal->inverse_video = 1; + break; + case 27: + if(terminal->inverse_video) + terminal->curr_attr = + inverse_video(terminal, terminal->curr_attr); + terminal->inverse_video = 0; + break; + case 39: + terminal->curr_attr &= ~(ATTRMASK_FOREGROUND & ~ATTRMASK_INTENSITY); + terminal->curr_attr |= terminal->color_scheme->default_attr & + (ATTRMASK_FOREGROUND & ~ATTRMASK_INTENSITY); + break; + case 49: + terminal->curr_attr &= ~ATTRMASK_BACKGROUND; + terminal->curr_attr |= terminal->color_scheme->default_attr & + ATTRMASK_BACKGROUND; + break; + default: + if(code >= 30 && code <= 37) { + foreground = code - 30; + terminal->curr_attr &= ~(ATTRMASK_FOREGROUND & ~ATTRMASK_INTENSITY); + terminal->curr_attr |= + (foreground << ATTRSHIFT_FOREGROUND) & ATTRMASK_FOREGROUND; + if(terminal->inverse_video) + terminal->curr_attr = + inverse_video(terminal, terminal->curr_attr); + } else if(code >= 40 && code <= 47) { + background = code - 40; + terminal->curr_attr &= ~ATTRMASK_BACKGROUND; + terminal->curr_attr |= + (background << ATTRSHIFT_BACKGROUND) & ATTRMASK_BACKGROUND; + if(terminal->inverse_video) + terminal->curr_attr = + inverse_video(terminal, terminal->curr_attr); + } + break; + } +} + +static void +handle_non_csi_escape(struct terminal *terminal, char code) +{ + switch(code) { + case 'M': /* RI */ + terminal->row -= 1; + if(terminal->row < terminal->margin_top) { + terminal->row = terminal->margin_top; + terminal_scroll(terminal, -1); + } + break; + case 'E': /* NEL */ + terminal->column = 0; + /* fallthrough */ + case 'D': /* IND */ + terminal->row += 1; + if(terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); + } + break; + case 'c': /* RIS */ + terminal_init(terminal); + break; + case 'H': /* HTS */ + terminal->tab_ruler[terminal->column] = 1; + break; + case '7': /* DECSC */ + terminal->saved_row = terminal->row; + terminal->saved_column = terminal->column; + terminal->saved_attr = terminal->curr_attr; + terminal->saved_inverse_video = terminal->inverse_video; + terminal->saved_origin_mode = terminal->origin_mode; + terminal->saved_cs = terminal->cs; + terminal->saved_g0 = terminal->g0; + terminal->saved_g1 = terminal->g1; + break; + case '8': /* DECRC */ + terminal->row = terminal->saved_row; + terminal->column = terminal->saved_column; + terminal->curr_attr = terminal->saved_attr; + terminal->inverse_video = terminal->saved_inverse_video; + terminal->origin_mode = terminal->saved_origin_mode; + terminal->cs = terminal->saved_cs; + terminal->g0 = terminal->saved_g0; + terminal->g1 = terminal->saved_g1; + break; + case '=': /* DECPAM */ + terminal->key_mode = KM_APPLICATION; + break; + case '>': /* DECPNM */ + terminal->key_mode = KM_NORMAL; + break; + default: + break; + } +} + +static void +handle_special_escape(struct terminal *terminal, char special, char code) +{ + int i, numChars; + + if (special == '#') { + switch(code) { + case '8': + /* fill with 'E', no cheap way to do this */ + memset(terminal->data, 0, terminal->pitch * terminal->height); + numChars = terminal->width * terminal->height; + for(i = 0; i < numChars; i++) { + terminal->data[i].byte[0] = 'E'; + terminal->data[i].byte[1] = '\0'; + } + break; + default: + break; + } + } else if (special == '(' || special == ')') { + switch(code) { + case '0': + if (special == '(') + terminal->g0 = CS_SPECIAL; + else + terminal->g1 = CS_SPECIAL; + break; + case 'A': + if (special == '(') + terminal->g0 = CS_UK; + else + terminal->g1 = CS_UK; + break; + case 'B': + if (special == '(') + terminal->g0 = CS_US; + else + terminal->g1 = CS_US; + break; + default: + break; + } + } +} + +/* Returns 1 if c was special, otherwise 0 */ +static int +handle_special_char(struct terminal *terminal, char c) +{ + union utf8_char *row; + char *attr_row; + + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + + switch(c) { + case '\r': + terminal->column = 0; + break; + case '\n': + if (terminal->normal_newline) { + terminal->column = 0; + } + /* fallthrough */ + case '\v': + case '\f': + terminal->row++; + if(terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); + } + break; + case '\t': + while (terminal->column < terminal->width) { + if (terminal->tab_ruler[terminal->column]) + break; + if (terminal->irm) terminal_shift_line(terminal, +1); + row[terminal->column].byte[0] = ' '; + row[terminal->column].byte[1] = '\0'; + attr_row[terminal->column] = terminal->curr_attr; + terminal->column++; + } + if (terminal->column >= terminal->width) { + terminal->column = terminal->width - 1; + } + + break; + case '\b': + if (terminal->column >= terminal->width) { + terminal->column = terminal->width - 2; + } else if (terminal->column > 0) { + terminal->column--; + } else if (terminal->autowrap) { + terminal->column = terminal->width - 1; + terminal->row -= 1; + if (terminal->row < terminal->margin_top) { + terminal_scroll(terminal, -1); + } + } + break; + case '\a': + /* Bell */ + break; + case '\x0E': /* SO */ + terminal->cs = terminal->g1; + break; + case '\x0F': /* SI */ + terminal->cs = terminal->g0; + break; + default: + return 0; + } + + return 1; +} + +static void +handle_char(struct terminal *terminal, union utf8_char utf8) +{ + union utf8_char *row; + char *attr_row; + + if (handle_special_char(terminal, utf8.byte[0])) return; + + apply_char_set(terminal->cs, utf8.byte); + + /* There are a whole lot of non-characters, control codes, + * and formatting codes that should probably be ignored, + * for example: */ + if (strncmp((char*) utf8.byte, "\xEF\xBB\xBF", 3) == 0) { + /* BOM, ignore */ + return; + } + + /* Some of these non-characters should be translated, e.g.: */ + if (utf8.byte[0] < 32) { + utf8.byte[0] = utf8.byte[0] + 64; + } + + /* handle right margin effects */ + if (terminal->column >= terminal->width) { + if (terminal->autowrap) { + terminal->column = 0; + terminal->row += 1; + if (terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); + } + } else { + terminal->column--; + } + } + + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + + if (terminal->irm) terminal_shift_line(terminal, +1); + row[terminal->column] = utf8; + attr_row[terminal->column++] = terminal->curr_attr; + + if (utf8.ch != terminal->last_char.ch) + terminal->last_char = utf8; } static void terminal_data(struct terminal *terminal, const char *data, size_t length) { int i; - char *row; + union utf8_char utf8; + enum utf8_state parser_state; for (i = 0; i < length; i++) { - row = terminal_get_row(terminal, terminal->row); - - if (terminal->state == STATE_ESCAPE) { - terminal->escape[terminal->escape_length++] = data[i]; - if (terminal->escape_length == 2 && data[i] != '[') { - /* Bad escape sequence. */ - terminal->state = STATE_NORMAL; - goto cancel_escape; - } - - if (isalpha(data[i])) { - terminal->state = STATE_NORMAL; - handle_escape(terminal); - } + parser_state = + utf8_next_char(&terminal->state_machine, data[i]); + switch(parser_state) { + case UTF8STATE_ACCEPT: + /* memcpy(utf8_char, terminal->state_machine.s, 4); */ + utf8.ch = terminal->state_machine.s.ch; + break; + case UTF8STATE_REJECT: + /* the unicode replacement character */ + utf8.byte[0] = 0xEF; + utf8.byte[1] = 0xBF; + utf8.byte[2] = 0xBD; + utf8.byte[3] = 0x00; + break; + default: continue; } - cancel_escape: - switch (data[i]) { - case '\r': - terminal->column = 0; - break; - case '\n': - terminal->column = 0; - if (terminal->row + 1 < terminal->height) { - terminal->row++; + /* assume escape codes never use non-ASCII characters */ + if (terminal->state == STATE_ESCAPE) { + terminal->escape[terminal->escape_length++] = utf8.byte[0]; + if (utf8.byte[0] == '[') { + terminal->state = STATE_ESCAPE_CSI; + continue; + } else if (utf8.byte[0] == '#' || utf8.byte[0] == '(' || + utf8.byte[0] == ')') + { + terminal->state = STATE_ESCAPE_SPECIAL; + continue; } else { - terminal->start++; - if (terminal->start == terminal->height) - terminal->start = 0; - memset(terminal_get_row(terminal, terminal->row), - 0, terminal->width); + terminal->state = STATE_NORMAL; + handle_non_csi_escape(terminal, utf8.byte[0]); + continue; + } + } else if (terminal->state == STATE_ESCAPE_SPECIAL) { + terminal->escape[terminal->escape_length++] = utf8.byte[0]; + terminal->state = STATE_NORMAL; + if (isdigit(utf8.byte[0]) || isalpha(utf8.byte[0])) { + handle_special_escape(terminal, terminal->escape[1], + utf8.byte[0]); + continue; + } + } else if (terminal->state == STATE_ESCAPE_CSI) { + if (handle_special_char(terminal, utf8.byte[0]) != 0) { + /* do nothing */ + } else if (utf8.byte[0] == '?') { + terminal->qmark_flag = 1; + } else { + /* Don't overflow the buffer */ + if (terminal->escape_length < MAX_ESCAPE) { + terminal->escape[terminal->escape_length++] = utf8.byte[0]; + } else { + terminal->state = STATE_NORMAL; + continue; + } } - break; - case '\t': - memset(&row[terminal->column], ' ', -terminal->column & 7); - terminal->column = (terminal->column + 7) & ~7; - break; - case '\e': + if (isalpha(utf8.byte[0]) || utf8.byte[0] == '@' || + utf8.byte[0] == '`') + { + terminal->state = STATE_NORMAL; + handle_escape(terminal); + continue; + } else { + continue; + } + } + + /* this is valid, because ASCII characters are never used to + * introduce a multibyte sequence in UTF-8 */ + if (utf8.byte[0] == '\e') { terminal->state = STATE_ESCAPE; terminal->escape[0] = '\e'; terminal->escape_length = 1; - break; - case '\b': - if (terminal->column > 0) - terminal->column--; - break; - case '\a': - /* Bell */ - break; - default: - if (terminal->column < terminal->width) - row[terminal->column++] = data[i] < 32 ? data[i] + 64 : data[i]; - break; + terminal->qmark_flag = 0; + } else { + handle_char(terminal, utf8); } } @@ -391,10 +1606,10 @@ key_handler(struct window *window, uint32_t key, uint32_t sym, uint32_t state, uint32_t modifiers, void *data) { struct terminal *terminal = data; - char ch[2]; + char ch[MAX_RESPONSE]; int len = 0; - switch (sym) { + switch(sym) { case XK_F11: if (!state) break; @@ -402,21 +1617,24 @@ key_handler(struct window *window, uint32_t key, uint32_t sym, window_set_fullscreen(window, terminal->fullscreen); window_schedule_redraw(terminal->window); break; - - case XK_Delete: - sym = 0x04; case XK_BackSpace: case XK_Tab: case XK_Linefeed: case XK_Clear: - case XK_Return: case XK_Pause: case XK_Scroll_Lock: case XK_Sys_Req: case XK_Escape: ch[len++] = sym & 0x7f; break; - + case XK_Return: + if (terminal->normal_newline) { + ch[len++] = 0x0D; + ch[len++] = 0x0A; + } else { + ch[len++] = 0x0D; + } + break; case XK_Shift_L: case XK_Shift_R: case XK_Control_L: @@ -424,12 +1642,81 @@ key_handler(struct window *window, uint32_t key, uint32_t sym, case XK_Alt_L: case XK_Alt_R: break; - + case XK_Insert: + ch[0] = '\e';ch[1] = '[';ch[2] = '2'; ch[3] = '~'; + len += 4; + break; + case XK_Delete: + if (terminal->deleteSendsDel) { + ch[len++] = '\x7F'; + } else { + ch[0] = '\e';ch[1] = '[';ch[2] = '3'; ch[3] = '~'; + len += 4; + } + break; + case XK_Page_Up: + ch[0] = '\e';ch[1] = '[';ch[2] = '5'; ch[3] = '~'; + len += 4; + break; + case XK_Page_Down: + ch[0] = '\e';ch[1] = '[';ch[2] = '6'; ch[3] = '~'; + len += 4; + break; + case XK_F1: + ch[0] = '\e';ch[1] = 'O';ch[2] = 'P'; + len += 3; + break; + case XK_F2: + ch[0] = '\e';ch[1] = 'O';ch[2] = 'Q'; + len += 3; + break; + case XK_F3: + ch[0] = '\e';ch[1] = 'O';ch[2] = 'R'; + len += 3; + break; + case XK_F4: + ch[0] = '\e';ch[1] = 'O';ch[2] = 'S'; + len += 3; + break; + case XK_F5: + ch[0] = '\e';ch[1] = '[';ch[2] = '1';ch[3] = '5';ch[4] = '~'; + len += 5; + break; + case XK_F6: + ch[0] = '\e';ch[1] = '[';ch[2] = '1';ch[3] = '7';ch[4] = '~'; + len += 5; + break; + case XK_F7: + ch[0] = '\e';ch[1] = '[';ch[2] = '1';ch[3] = '8';ch[4] = '~'; + len += 5; + break; + case XK_F8: + ch[0] = '\e';ch[1] = '[';ch[2] = '1';ch[3] = '9';ch[4] = '~'; + len += 5; + break; + case XK_F9: + ch[0] = '\e';ch[1] = '[';ch[2] = '2';ch[3] = '0';ch[4] = '~'; + len += 5; + break; + case XK_F10: + ch[0] = '\e';ch[1] = '[';ch[2] = '2';ch[3] = '1';ch[4] = '~'; + len += 5; + break; + case XK_F12: + ch[0] = '\e';ch[1] = '[';ch[2] = '2';ch[3] = '4';ch[4] = '~'; + len += 5; + break; default: + /* Handle special keys with alternate mappings */ + len = apply_key_map(terminal->key_mode, sym, ch); + if (len != 0) break; + if (modifiers & WINDOW_MODIFIER_CONTROL) sym = sym & 0x1f; - else if (modifiers & WINDOW_MODIFIER_ALT) + else if (terminal->altSendsEsc && (modifiers & WINDOW_MODIFIER_ALT)) ch[len++] = 0x1b; + else if (modifiers & WINDOW_MODIFIER_ALT) + sym = sym | 0x80; if (sym < 256) ch[len++] = sym; break; @@ -462,9 +1749,17 @@ terminal_create(struct display *display, int fullscreen) memset(terminal, 0, sizeof *terminal); terminal->fullscreen = fullscreen; - terminal->color_scheme = &jbarnes_colors; - terminal->window = window_create(display, "Wayland Terminal", - 500, 400); + + terminal->color_scheme = &DEFAULT_COLORS; + terminal_init(terminal); + terminal->margin_top = 0; + terminal->margin_bottom = 10000; /* much too large, will be + * trimmed down by + * terminal_resize */ + + init_state_machine(&terminal->state_machine); + + terminal->window = window_create(display, "Wayland Terminal", 500, 400); terminal->display = display; terminal->margin = 5; @@ -478,14 +1773,24 @@ terminal_create(struct display *display, int fullscreen) surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); cr = cairo_create(surface); - cairo_select_font_face (cr, "mono", - CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); + + terminal->font_bold = + cairo_toy_font_face_create ("mono", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_font_face_reference(terminal->font_bold); + terminal->font_normal = + cairo_toy_font_face_create ("mono", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_font_face_reference(terminal->font_normal); + cairo_set_font_face(cr, terminal->font_normal); cairo_set_font_size(cr, 14); cairo_font_extents(cr, &terminal->extents); cairo_destroy(cr); cairo_surface_destroy(surface); + terminal_resize(terminal, 80, 24); terminal_draw(terminal); return terminal; @@ -517,13 +1822,13 @@ terminal_run(struct terminal *terminal, const char *path) pid = forkpty(&master, NULL, NULL, NULL); if (pid == 0) { - setenv("TERM", "vt100", 1); + setenv("TERM", "xterm", 1); + setenv("COLORTERM", "xterm", 1); if (execl(path, path, NULL)) { printf("exec failed: %m\n"); exit(EXIT_FAILURE); } } else if (pid < 0) { - fprintf(stderr, "failed to fork and create pty (%m).\n"); return -1; } @@ -549,7 +1854,6 @@ int main(int argc, char *argv[]) d = display_create(&argc, &argv, option_entries); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); return -1; }