diff --git a/clients/terminal.c b/clients/terminal.c index 26f8c9da..e9002985 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -187,7 +187,9 @@ struct terminal { union utf8_char *data; struct attr *data_attr; struct attr curr_attr; + char origin_mode; union utf8_char last_char; + int margin_top, margin_bottom; int data_pitch, attr_pitch; /* The width in bytes of a line */ int width, height, start, row, column; int fd, master; @@ -211,6 +213,7 @@ static void terminal_init(struct terminal *terminal) { terminal->curr_attr = terminal->color_scheme->default_attr; + terminal->origin_mode = 0; terminal->row = 0; terminal->column = 0; @@ -265,6 +268,94 @@ 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->data_pitch); + attr_init(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->data_pitch); + attr_init(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; + struct attr *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->data_pitch); + memcpy(terminal_get_attr_row(terminal, to_row - i), + terminal_get_attr_row(terminal, from_row - i), + terminal->attr_pitch); + } + 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->data_pitch); + if (i > terminal->margin_top) { + memcpy(terminal_get_attr_row(terminal, i), + dup_attr, terminal->attr_pitch); + } + } + } 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->data_pitch); + memcpy(terminal_get_attr_row(terminal, to_row + i), + terminal_get_attr_row(terminal, from_row + i), + terminal->attr_pitch); + } + 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->data_pitch); + if (i < terminal->margin_bottom) { + memcpy(terminal_get_attr_row(terminal, i), + dup_attr, terminal->attr_pitch); + } + } + } +} + +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_resize(struct terminal *terminal, int width, int height) { @@ -317,6 +408,8 @@ terminal_resize(struct terminal *terminal, int width, int height) terminal->attr_pitch = attr_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; @@ -530,13 +623,39 @@ 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) +{ + if (terminal->qmark_flag) { + switch(code) { + case 6: /* DECOM */ + terminal->origin_mode = sr; + if (terminal->origin_mode) + terminal->row = terminal->margin_top; + else + terminal->row = 0; + terminal->column = 0; + break; + default: + fprintf(stderr, "Unknown parameter: ?%d\n", code); + break; + } + } else { + switch(code) { + default: + fprintf(stderr, "Unknown parameter: %d\n", code); + break; + } + } +} + static void handle_escape(struct terminal *terminal) { union utf8_char *row; struct attr *attr_row; char *p; - int i, count; + int i, count, top, bottom; int args[10], set[10] = { 0, }; terminal->escape[terminal->escape_length++] = '\0'; @@ -610,6 +729,22 @@ handle_escape(struct terminal *terminal) memset(&row[terminal->column], 0, (terminal->width - terminal->column) * sizeof(union utf8_char)); attr_init(&attr_row[terminal->column], terminal->curr_attr, (terminal->width - terminal->column)); 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 '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 */ if (set[0] && set[1] && set[2] && args[1] == 5) { if (args[0] == 38) { @@ -631,11 +766,31 @@ handle_escape(struct terminal *terminal) } } break; - case '?': - if (strcmp(p, "?25l") == 0) { - /* hide cursor */ - } else if (strcmp(p, "?25h") == 0) { - /* show cursor */ + 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; default: @@ -647,6 +802,28 @@ handle_escape(struct terminal *terminal) 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; + default: + fprintf(stderr, "Unknown escape code: %c\n", code); + break; + } } static void @@ -735,16 +912,10 @@ handle_special_char(struct terminal *terminal, char c) /* fallthrough */ case '\v': case '\f': - if (terminal->row + 1 < terminal->height) { - terminal->row++; - } else { - terminal->start++; - if (terminal->start == terminal->height) - terminal->start = 0; - memset(terminal_get_row(terminal, terminal->row), - 0, terminal->width * sizeof(union utf8_char)); - attr_init(terminal_get_attr_row(terminal, terminal->row), - terminal->curr_attr, terminal->width); + terminal->row++; + if(terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); } break; @@ -969,6 +1140,9 @@ terminal_create(struct display *display, int fullscreen) terminal->fullscreen = fullscreen; 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 */ terminal->window = window_create(display, "Wayland Terminal", 500, 400);