diff --git a/csi.c b/csi.c index 647d5052..4b545d31 100644 --- a/csi.c +++ b/csi.c @@ -10,7 +10,7 @@ #endif #define LOG_MODULE "csi" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "grid.h" @@ -57,10 +57,6 @@ initialize_colors256(void) b * 51 / 255.0, 1.0, }; -#if 0 - colors256[16 + r * 6 * 6 + g * 6 + b] = - (51 * r) << 24 | (51 * g) << 16 | (51 * b) << 8 | 0xff; -#endif } } } @@ -72,7 +68,6 @@ initialize_colors256(void) i * 11 / 255.0, 1.0 }; - //(11 * i) << 24 | (11 * i) << 16 | (11 * i) << 8 | 0xff; } } @@ -280,7 +275,7 @@ csi_dispatch(struct terminal *term, uint8_t final) i == term->vt.params.idx - 1 ? "" : ";"); } - c += snprintf(&log[c], sizeof(log) - c, "%c (%zu parameters)", + c += snprintf(&log[c], sizeof(log) - c, "%c (%d parameters)", final, term->vt.params.idx); LOG_DBG("%s", log); #endif @@ -292,11 +287,7 @@ csi_dispatch(struct terminal *term, uint8_t final) case 'd': { /* VPA - vertical line position absolute */ - int row = param_get(term, 0, 1); - - if (row > term->rows) - row = term->rows; - + int row = min(param_get(term, 0, 1), term->rows); term_cursor_to(term, row - 1, term->cursor.col); break; } @@ -323,25 +314,15 @@ csi_dispatch(struct terminal *term, uint8_t final) case 'G': { /* Cursor horizontal absolute */ - int col = param_get(term, 0, 1); - - if (col > term->cols) - col = term->cols; - - term_cursor_to(term, term->cursor.row, col); + int col = min(param_get(term, 0, 1), term->cols); + term_cursor_to(term, term->cursor.row, col - 1); break; } case 'H': { /* Move cursor */ - int row = param_get(term, 0, 1); - int col = param_get(term, 1, 1); - - if (row > term->rows) - row = term->rows; - if (col > term->cols) - col = term->cols; - + int row = min(param_get(term, 0, 1), term->rows); + int col = min(param_get(term, 1, 1), term->cols); term_cursor_to(term, row - 1, col - 1); break; } @@ -452,27 +433,49 @@ csi_dispatch(struct terminal *term, uint8_t final) case 'P': { /* DCH: Delete character */ - int count = param_get(term, 0, 1); - /* Only delete up to the right margin */ - const int max_end = term_cursor_linear( - term, term->cursor.row, term->cols); + /* Number of characters to delete */ + int count = min( + param_get(term, 0, 1), term->cols - term->cursor.col); - int start = term->cursor.linear; - int end = min(start + count, max_end); + /* Number of characters left after deletion (on current line) */ + int remaining = term->cols - (term->cursor.col + count); - /* Erase the requested number of characters */ - term_erase(term, start, end); - - /* Move remaining (up til the right margin) characters */ - int remaining = max_end - end; - memmove(&term->grid->cells[start], - &term->grid->cells[end], - remaining * sizeof(term->grid->cells[0])); + /* 'Delete' characters by moving the remaining ones */ + memmove(&term->grid->cur_line[term->cursor.col], + &term->grid->cur_line[term->cursor.col + count], + remaining * sizeof(term->grid->cur_line[0])); term_damage_update(term, term->cursor.linear, remaining); + + /* Erase the remainder of the line */ + term_erase( + term, term->cursor.linear + remaining, + term->cursor.linear + remaining + count); break; } + case 'X': { + /* Erase chars */ + int count = min( + param_get(term, 0, 1), term->cols - term->cursor.col); + + memset(&term->grid->cur_line[term->cursor.col], + 0, count * sizeof(term->grid->cur_line[0])); + term_damage_erase(term, term->cursor.linear, count); + break; + } + + case 'h': + /* smir - insert mode enable */ + assert(false && "untested"); + term->insert_mode = true; + break; + + case 'l': + /* rmir - insert mode disable */ + term->insert_mode = false; + break; + case 'r': { int start = param_get(term, 0, 1); int end = param_get(term, 1, term->rows); @@ -481,9 +484,11 @@ csi_dispatch(struct terminal *term, uint8_t final) term->scroll_region.start = start - 1; term->scroll_region.end = end; - LOG_INFO("scroll region: %d-%d", - term->scroll_region.start, - term->scroll_region.end); + term_cursor_to(term, start - 1, 0); + + LOG_DBG("scroll region: %d-%d", + term->scroll_region.start, + term->scroll_region.end); break; } @@ -560,12 +565,16 @@ csi_dispatch(struct terminal *term, uint8_t final) LOG_WARN("unimplemented: flash"); break; + case 7: + term->auto_margin = true; + break; + case 12: LOG_WARN("unimplemented: cursor blinking"); break; case 25: - LOG_WARN("unimplemented: civis"); + term->hide_cursor = false; break; case 1000: @@ -576,15 +585,28 @@ csi_dispatch(struct terminal *term, uint8_t final) LOG_WARN("unimplemented: report cell mouse motion"); break; + case 1005: + LOG_WARN("unimplemented: UTF-8 mouse"); + break; + case 1006: LOG_WARN("unimplemented: SGR mouse"); break; + case 1015: + LOG_WARN("unimplemented: URXVT mosue"); + break; + case 1049: if (term->grid != &term->alt) { term->grid = &term->alt; term->saved_cursor = term->cursor; - term_damage_all(term); + + term_cursor_to(term, term->cursor.row, term->cursor.col); + + tll_free(term->alt.damage); + tll_free(term->alt.scroll_damage); + term_erase(term, 0, term->rows * term->cols); } break; @@ -613,12 +635,16 @@ csi_dispatch(struct terminal *term, uint8_t final) LOG_WARN("unimplemented: flash"); break; + case 7: + term->auto_margin = false; + break; + case 12: LOG_WARN("unimplemented: cursor blinking"); break; case 25: - LOG_WARN("unimplemented: civis"); + term->hide_cursor = true; break; case 1000: @@ -629,19 +655,27 @@ csi_dispatch(struct terminal *term, uint8_t final) LOG_WARN("unimplemented: report cell mouse motion"); break; + case 1005: + LOG_WARN("unimplemented: UTF-8 mouse"); + break; + case 1006: LOG_WARN("unimplemented: SGR mouse"); break; + case 1015: + LOG_WARN("unimplemented: URXVT mosue"); + break; + case 1049: if (term->grid == &term->alt) { term->grid = &term->normal; term->cursor = term->saved_cursor; + term_cursor_to(term, term->cursor.row, term->cursor.col); - /* Should these be restored from saved values? */ - term->scroll_region.start = 0; - term->scroll_region.end = term->rows; + tll_free(term->alt.damage); + tll_free(term->alt.scroll_damage); term_damage_all(term); } @@ -661,8 +695,40 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } + case 's': + for (size_t i = 0; i < term->vt.params.idx; i++) { + switch (term->vt.params.v[i].value) { + case 1001: /* save old highlight mouse tracking mode? */ + LOG_WARN( + "unimplemented: CSI ?1001s " + "(save 'highlight mouse tracking' mode)"); + break; + + default: + LOG_ERR("unimplemented: CSI ?%ds", term->vt.params.v[i].value); + abort(); + } + } + break; + + case 'r': + for (size_t i = 0; i < term->vt.params.idx; i++) { + switch (term->vt.params.v[i].value) { + case 1001: /* restore old highlight mouse tracking mode? */ + LOG_WARN( + "unimplemented: CSI ?1001r " + "(restore 'highlight mouse tracking' mode)"); + break; + + default: + LOG_ERR("unimplemented: CSI ?%dr", term->vt.params.v[i].value); + abort(); + } + } + break; + default: - LOG_ERR("CSI: intermediate '?': unimplemented final: %c", final); + LOG_ERR("unimplemented: CSI: ?%c", final); abort(); } diff --git a/grid.c b/grid.c index 67f6e57a..178883e4 100644 --- a/grid.c +++ b/grid.c @@ -4,5 +4,75 @@ #include #define LOG_MODULE "grid" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" + +struct cell * +grid_get_range(struct grid *grid, int start, int *length) +{ +#define min(x, y) ((x) < (y) ? (x) : (y)) + assert(*length <= grid->size); + + int real_start = (grid->offset + start) % grid->size; + if (real_start < 0) + real_start += grid->size; + assert(real_start >= 0); + assert(real_start < grid->size); + + *length = min(*length, grid->size - real_start); + assert(real_start + *length <= grid->size); + + return &grid->cells[real_start]; +#undef min +} + +void +grid_memset(struct grid *grid, int start, int c, int length) +{ + int left = length; + while (left > 0) { + int count = left; + struct cell *cells = grid_get_range(grid, start, &count); + + assert(count > 0); + assert(count <= left); + + memset(cells, c, count * sizeof(cells[0])); + + left -= count; + start += count; + } +} + +void +grid_memmove(struct grid *grid, int dst, int src, int length) +{ + int left = length; + int copy_idx = 0; + struct cell copy[left]; + + while (left > 0) { + int count = left; + struct cell *src_cells = grid_get_range(grid, src, &count); + + memcpy(©[copy_idx], src_cells, count * sizeof(copy[0])); + + left -= count; + src += count; + copy_idx += count; + } + + left = length; + copy_idx = 0; + + while (left > 0) { + int count = left; + struct cell *dst_cells = grid_get_range(grid, dst, &count); + + memcpy(dst_cells, ©[copy_idx], count * sizeof(copy[0])); + + left -= count; + dst += count; + copy_idx += count; + } +} diff --git a/grid.h b/grid.h index 580ef343..59d9e58b 100644 --- a/grid.h +++ b/grid.h @@ -1,3 +1,8 @@ #pragma once +#include #include "terminal.h" + +struct cell *grid_get_range(struct grid *grid, int start, int *length); +void grid_memset(struct grid *grid, int start, int c, int length); +void grid_memmove(struct grid *grid, int dst, int src, int length); diff --git a/input.c b/input.c index 5e61d49b..319de914 100644 --- a/input.c +++ b/input.c @@ -10,7 +10,7 @@ #include #define LOG_MODULE "input" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "terminal.h" diff --git a/main.c b/main.c index f4f9eaa7..a87598c4 100644 --- a/main.c +++ b/main.c @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include //#include @@ -15,7 +17,7 @@ #include #define LOG_MODULE "main" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "font.h" @@ -48,7 +50,7 @@ struct wayland { struct context { bool quit; - cairo_scaled_font_t *fonts[8]; + cairo_scaled_font_t *fonts[4]; cairo_font_extents_t fextents; int width; @@ -61,7 +63,6 @@ struct context { bool frame_is_scheduled; }; - static void frame_callback( void *data, struct wl_callback *wl_callback, uint32_t callback_data); @@ -76,27 +77,66 @@ attrs_to_font(struct context *c, const struct attributes *attrs) return c->fonts[idx]; } +struct glyph_sequence { + cairo_glyph_t glyphs[10000]; + cairo_glyph_t *g; + int count; + + struct attributes attrs; + struct rgba foreground; +}; + static void grid_render_update(struct context *c, struct buffer *buf, const struct damage *dmg) { - LOG_DBG("damage: UPDATE: %d -> %d", - dmg->range.start, dmg->range.start + dmg->range.length); + LOG_DBG("damage: UPDATE: %d -> %d (offset = %d)", + (dmg->range.start - c->term.grid->offset) % c->term.grid->size, + (dmg->range.start - c->term.grid->offset) % c->term.grid->size + dmg->range.length, + c->term.grid->offset); + + int start = dmg->range.start; + int length = dmg->range.length; + + if (start < c->term.grid->offset) { + int end = start + length; + if (end >= c->term.grid->offset) { + start = c->term.grid->offset; + length = end - start; + } else + return; + } const int cols = c->term.cols; - for (int linear_cursor = dmg->range.start, - row = dmg->range.start / cols, - col = dmg->range.start % cols; - linear_cursor < dmg->range.start + dmg->range.length; + struct glyph_sequence gseq = {.g = gseq.glyphs}; + + for (int linear_cursor = start, + row = ((start - c->term.grid->offset) % c->term.grid->size) / cols, + col = ((start - c->term.grid->offset) % c->term.grid->size) % cols; + linear_cursor < start + length; linear_cursor++, - //col = (col + 1) % cols, col = col + 1 >= c->term.cols ? 0 : col + 1, row += col == 0 ? 1 : 0) { - //LOG_DBG("UPDATE: %d (%dx%d)", linear_cursor, row, col); - const struct cell *cell = &c->term.grid->cells[linear_cursor]; - bool has_cursor = c->term.cursor.linear == linear_cursor; + assert(row >= 0); + assert(row < c->term.rows); + assert(col >= 0); + assert(col < c->term.cols); + + int cell_idx = linear_cursor % c->term.grid->size; + if (cell_idx < 0) + cell_idx += c->term.grid->size; + + assert(cell_idx >= 0); + assert(cell_idx < c->term.rows * c->term.cols); + + const struct cell *cell = &c->term.grid->cells[cell_idx]; + + /* Cursor here? */ + bool has_cursor + = (!c->term.hide_cursor && + (c->term.cursor.linear == linear_cursor - c->term.grid->offset)); int x = col * c->term.cell_width; int y = row * c->term.cell_height; @@ -120,15 +160,9 @@ grid_render_update(struct context *c, struct buffer *buf, const struct damage *d background = swap; } - //LOG_DBG("cell %dx%d dirty: c=0x%02x (%c)", - // row, col, cell->c[0], cell->c[0]); - - cairo_scaled_font_t *font = attrs_to_font(c, &cell->attrs); - cairo_set_scaled_font(buf->cairo, font); + /* Background */ cairo_set_source_rgba( buf->cairo, background.r, background.g, background.b, background.a); - - /* Background */ cairo_rectangle(buf->cairo, x, y, width, height); cairo_fill(buf->cairo); @@ -138,37 +172,75 @@ grid_render_update(struct context *c, struct buffer *buf, const struct damage *d if (cell->attrs.conceal) continue; - cairo_glyph_t *glyphs = NULL; - int num_glyphs = 0; + /* + * cairo_show_glyphs() apparently works *much* faster when + * called once with a large array of glyphs, compared to + * multiple calls with a single glyph. + * + * So, collect glyphs until cell attributes change, then we + * 'flush' (render) the glyphs. + */ - cairo_status_t status = cairo_scaled_font_text_to_glyphs( - font, x, y + c->fextents.ascent, - cell->c, strlen(cell->c), &glyphs, &num_glyphs, - NULL, NULL, NULL); + if (memcmp(&cell->attrs, &gseq.attrs, sizeof(cell->attrs)) != 0 || + gseq.count >= sizeof(gseq.glyphs) / sizeof(gseq.glyphs[0]) - 10 || + memcmp(&gseq.foreground, &foreground, sizeof(foreground)) != 0) + { + if (gseq.count >= sizeof(gseq.glyphs) / sizeof(gseq.glyphs[0]) - 10) + LOG_WARN("hit glyph limit"); + cairo_set_scaled_font(buf->cairo, attrs_to_font(c, &gseq.attrs)); + cairo_set_source_rgba( + buf->cairo, gseq.foreground.r, gseq.foreground.g, + gseq.foreground.b, gseq.foreground.a); - if (status != CAIRO_STATUS_SUCCESS) { - if (glyphs != NULL) - cairo_glyph_free(glyphs); - continue; + cairo_set_operator(buf->cairo, CAIRO_OPERATOR_OVER); + cairo_show_glyphs(buf->cairo, gseq.glyphs, gseq.count); + + gseq.g = gseq.glyphs; + gseq.count = 0; + gseq.attrs = cell->attrs; + gseq.foreground = foreground; } + int new_glyphs + = sizeof(gseq.glyphs) / sizeof(gseq.glyphs[0]) - gseq.count; + + cairo_status_t status = cairo_scaled_font_text_to_glyphs( + attrs_to_font(c, &cell->attrs), x, y + c->fextents.ascent, + cell->c, strlen(cell->c), &gseq.g, &new_glyphs, + NULL, NULL, NULL); + + if (status != CAIRO_STATUS_SUCCESS) + continue; + + gseq.g += new_glyphs; + gseq.count += new_glyphs; + assert(gseq.count <= sizeof(gseq.glyphs) / sizeof(gseq.glyphs[0])); + } + + if (gseq.count > 0) { + cairo_set_scaled_font(buf->cairo, attrs_to_font(c, &gseq.attrs)); cairo_set_source_rgba( - buf->cairo, foreground.r, foreground.g, foreground.b, foreground.a); - cairo_show_glyphs(buf->cairo, glyphs, num_glyphs); - cairo_glyph_free(glyphs); + buf->cairo, gseq.foreground.r, gseq.foreground.g, + gseq.foreground.b, gseq.foreground.a); + cairo_set_operator(buf->cairo, CAIRO_OPERATOR_OVER); + cairo_show_glyphs(buf->cairo, gseq.glyphs, gseq.count); } wl_surface_damage_buffer( c->wl.surface, - 0, (dmg->range.start / cols) * c->term.cell_height, + 0, ((dmg->range.start - c->term.grid->offset) / cols) * c->term.cell_height, buf->width, (dmg->range.length + cols - 1) / cols * c->term.cell_height); } static void grid_render_erase(struct context *c, struct buffer *buf, const struct damage *dmg) { - LOG_DBG("damage: ERASE: %d -> %d", - dmg->range.start, dmg->range.start + dmg->range.length); + LOG_DBG("damage: ERASE: %d -> %d (offset = %d)", + (dmg->range.start - c->term.grid->offset) % c->term.grid->size, + (dmg->range.start - c->term.grid->offset) % c->term.grid->size + dmg->range.length, + c->term.grid->offset); + + assert(dmg->range.start >= c->term.grid->offset); cairo_set_source_rgba( buf->cairo, default_background.r, default_background.g, @@ -176,7 +248,7 @@ grid_render_erase(struct context *c, struct buffer *buf, const struct damage *dm const int cols = c->term.cols; - int start = dmg->range.start; + int start = (dmg->range.start - c->term.grid->offset) % c->term.grid->size; int left = dmg->range.length; int row = start / cols; @@ -238,17 +310,6 @@ grid_render_erase(struct context *c, struct buffer *buf, const struct damage *dm cairo_fill(buf->cairo); wl_surface_damage_buffer(c->wl.surface, x, y, width, height); } - - /* Redraw cursor, if it's inside the erased range */ - if (c->term.cursor.linear >= dmg->range.start && - c->term.cursor.linear < dmg->range.start + dmg->range.length) - { - grid_render_update( - c, buf, - &(struct damage){ - .type = DAMAGE_UPDATE, - .range = {.start = c->term.cursor.linear, .length = 1}}); - } } static void @@ -284,7 +345,7 @@ grid_render_scroll(struct context *c, struct buffer *buf, struct damage erase = { .type = DAMAGE_ERASE, .range = { - .start = max(dmg->scroll.region.end - dmg->scroll.lines, + .start = c->term.grid->offset + max(dmg->scroll.region.end - dmg->scroll.lines, dmg->scroll.region.start) * cols, .length = min(dmg->scroll.region.end - dmg->scroll.region.start, dmg->scroll.lines) * cols, @@ -326,7 +387,7 @@ grid_render_scroll_reverse(struct context *c, struct buffer *buf, struct damage erase = { .type = DAMAGE_ERASE, .range = { - .start = dmg->scroll.region.start * cols, + .start = c->term.grid->offset + dmg->scroll.region.start * cols, .length = min(dmg->scroll.region.end - dmg->scroll.region.start, dmg->scroll.lines) * cols, }, @@ -337,8 +398,11 @@ grid_render_scroll_reverse(struct context *c, struct buffer *buf, static void grid_render(struct context *c) { + static int last_cursor; + if (tll_length(c->term.grid->damage) == 0 && - tll_length(c->term.grid->scroll_damage) == 0) + tll_length(c->term.grid->scroll_damage) == 0 && + last_cursor == c->term.grid->offset + c->term.cursor.linear) { return; } @@ -347,9 +411,15 @@ grid_render(struct context *c) assert(c->height > 0); struct buffer *buf = shm_get_buffer(c->wl.shm, c->width, c->height); - cairo_set_operator(buf->cairo, CAIRO_OPERATOR_SOURCE); + static struct buffer *last_buf = NULL; + if (last_buf != buf) { + if (last_buf != NULL) + LOG_WARN("new buffer"); + last_buf = buf; + } + tll_foreach(c->term.grid->scroll_damage, it) { switch (it->item.type) { case DAMAGE_SCROLL: @@ -383,6 +453,28 @@ grid_render(struct context *c) tll_remove(c->term.grid->damage, it); } + /* TODO: break out to function */ + /* Re-render last cursor cell and current cursor cell */ + /* Make sure previous cursor is refreshed (to avoid "ghost" cursors) */ + if (last_cursor != c->term.cursor.linear) { + struct damage prev_cursor = { + .type = DAMAGE_UPDATE, + .range = {.start = last_cursor, .length = 1}, + }; + grid_render_update(c, buf, &prev_cursor); + } + + struct damage cursor = { + .type = DAMAGE_UPDATE, + .range = {.start = c->term.grid->offset + c->term.cursor.linear, .length = 1}, + }; + grid_render_update(c, buf, &cursor); + last_cursor = c->term.grid->offset + c->term.cursor.linear; + + c->term.grid->offset %= c->term.grid->size; + if (c->term.grid->offset < 0) + c->term.grid->offset += c->term.grid->size; + //cairo_surface_flush(buf->cairo_surface); wl_surface_attach(c->wl.surface, buf->wl_buf, 0, 0); @@ -416,8 +508,6 @@ resize(struct context *c, int width, int height) const size_t normal_old_size = c->term.normal.size; const size_t alt_old_size = c->term.alt.size; - c->term.cell_width = (int)ceil(c->fextents.max_x_advance); - c->term.cell_height = (int)ceil(c->fextents.height); c->term.cols = c->width / c->term.cell_width; c->term.rows = c->height / c->term.cell_height; @@ -426,10 +516,10 @@ resize(struct context *c, int width, int height) c->term.normal.cells = realloc( c->term.normal.cells, - c->term.cols * c->term.rows * sizeof(c->term.normal.cells[0])); + c->term.normal.size * sizeof(c->term.normal.cells[0])); c->term.alt.cells = realloc( c->term.alt.cells, - c->term.cols * c->term.rows * sizeof(c->term.alt.cells[0])); + c->term.alt.size * sizeof(c->term.alt.cells[0])); for (size_t i = normal_old_size; i < c->term.normal.size; i++) { c->term.normal.cells[i] = (struct cell){ @@ -445,13 +535,13 @@ resize(struct context *c, int width, int height) }; } - LOG_DBG("resize: %dx%d, grid: cols=%d, rows=%d", - c->width, c->height, c->term.cols, c->term.rows); + LOG_INFO("resize: %dx%d, grid: cols=%d, rows=%d", + c->width, c->height, c->term.cols, c->term.rows); /* Update environment variables */ char cols_s[12], rows_s[12]; - sprintf(cols_s, "%u", c->term.cols); - sprintf(rows_s, "%u", c->term.rows); + sprintf(cols_s, "%d", c->term.cols); + sprintf(rows_s, "%d", c->term.rows); setenv("COLUMNS", cols_s, 1); setenv("LINES", rows_s, 1); @@ -469,6 +559,11 @@ resize(struct context *c, int width, int height) if (c->term.scroll_region.end == old_rows) c->term.scroll_region.end = c->term.rows; + term_cursor_to( + &c->term, + min(c->term.cursor.row, c->term.rows), + min(c->term.cursor.col, c->term.cols)); + term_damage_all(&c->term); if (!c->frame_is_scheduled) @@ -721,10 +816,40 @@ keyboard_repeater(void *arg) } int -main(int argc, const char *const *argv) +main(int argc, char *const *argv) { int ret = EXIT_FAILURE; + static const struct option longopts[] = { + {"font", required_argument, 0, 'f'}, + {NULL, no_argument, 0, 0}, + }; + + const char *font_name = "Dina:pixelsize=12"; + + while (true) { + int c = getopt_long(argc, argv, ":f:h", longopts, NULL); + if (c == -1) + break; + + switch (c) { + case 'f': + font_name = optarg; + break; + + case 'h': + break; + + case ':': + fprintf(stderr, "error: -%c: missing required argument\n", optopt); + return EXIT_FAILURE; + + case '?': + fprintf(stderr, "error: -%c: invalid option\n", optopt); + return EXIT_FAILURE; + } + } + setlocale(LC_ALL, ""); int repeat_pipe_fds[2] = {-1, -1}; @@ -739,6 +864,7 @@ main(int argc, const char *const *argv) .ptmx = posix_openpt(O_RDWR | O_NOCTTY), .decckm = DECCKM_CSI, .keypad_mode = KEYPAD_NUMERICAL, /* TODO: verify */ + .auto_margin = true, .vt = { .state = 1, /* STATE_GROUND */ }, @@ -764,7 +890,6 @@ main(int argc, const char *const *argv) thrd_t keyboard_repeater_id; thrd_create(&keyboard_repeater_id, &keyboard_repeater, &c.term); - const char *font_name = "Dina:pixelsize=12"; c.fonts[0] = font_from_name(font_name); if (c.fonts[0] == NULL) goto out; @@ -779,11 +904,11 @@ main(int argc, const char *const *argv) snprintf(fname, sizeof(fname), "%s:style=bold italic", font_name); c.fonts[3] = font_from_name(fname); - - /* TODO; underline */ } cairo_scaled_font_extents(c.fonts[0], &c.fextents); + c.term.cell_width = (int)ceil(c.fextents.max_x_advance); + c.term.cell_height = (int)ceil(c.fextents.height); LOG_DBG("font: height: %.2f, x-advance: %.2f", c.fextents.height, c.fextents.max_x_advance); @@ -864,6 +989,20 @@ main(int argc, const char *const *argv) break; } + /* Read logic requires non-blocking mode */ + { + int fd_flags = fcntl(c.term.ptmx, F_GETFL); + if (fd_flags == -1) { + LOG_ERRNO("failed to set non blocking mode on PTY master"); + goto out; + } + + if (fcntl(c.term.ptmx, F_SETFL, fd_flags | O_NONBLOCK) == -1) { + LOG_ERRNO("failed to set non blocking mode on PTY master"); + goto out; + } + } + while (true) { struct pollfd fds[] = { {.fd = wl_display_get_fd(c.wl.display), .events = POLLIN}, @@ -888,16 +1027,18 @@ main(int argc, const char *const *argv) } if (fds[1].revents & POLLIN) { - uint8_t data[8192]; - ssize_t count = read(c.term.ptmx, data, sizeof(data)); - if (count < 0) { - LOG_ERRNO("failed to read from pseudo terminal"); - break; + for (size_t i = 0; i < 3; i++) { + uint8_t data[8192]; + ssize_t count = read(c.term.ptmx, data, sizeof(data)); + if (count < 0) { + if (errno != EAGAIN) + LOG_ERRNO("failed to read from pseudo terminal"); + break; + } + + vt_from_slave(&c.term, data, count); } - //LOG_DBG("%.*s", (int)count, data); - - vt_from_slave(&c.term, data, count); if (!c.frame_is_scheduled) grid_render(&c); } diff --git a/osc.c b/osc.c index 5f71287e..2882d546 100644 --- a/osc.c +++ b/osc.c @@ -1,7 +1,7 @@ #include "osc.h" #define LOG_MODULE "osc" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" bool diff --git a/slave.c b/slave.c index edb5e9c6..676d4ce2 100644 --- a/slave.c +++ b/slave.c @@ -9,7 +9,7 @@ #include #define LOG_MODULE "slave" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" void diff --git a/terminal.c b/terminal.c index 482452c5..4de5a1a8 100644 --- a/terminal.c +++ b/terminal.c @@ -4,8 +4,9 @@ #include #define LOG_MODULE "terminal" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" +#include "grid.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) @@ -37,10 +38,6 @@ damage_merge_range(struct terminal *term, const struct damage *dmg) old->range.start = new_start; old->range.length = new_end - new_start; - assert(old->range.start >= 0); - assert(old->range.start < term->rows * term->cols); - assert(old->range.length >= 0); - assert(old->range.start + old->range.length <= term->rows * term->cols); return true; } @@ -51,16 +48,18 @@ static void term_damage_update_or_erase(struct terminal *term, enum damage_type damage_type, int start, int length) { +#if 1 + if (tll_length(term->grid->damage) > 1024) { + term_damage_all(term); + return; + } +#endif + struct damage dmg = { .type = damage_type, - .range = {.start = start, .length = length}, + .range = {.start = term->grid->offset + start, .length = length}, }; - assert(dmg.range.start >= 0); - assert(dmg.range.start < term->rows * term->cols); - assert(dmg.range.length >= 0); - assert(dmg.range.start + dmg.range.length <= term->rows * term->cols); - if (damage_merge_range(term, &dmg)) return; @@ -70,12 +69,14 @@ term_damage_update_or_erase(struct terminal *term, enum damage_type damage_type, void term_damage_update(struct terminal *term, int start, int length) { + assert(start + length <= term->rows * term->cols); term_damage_update_or_erase(term, DAMAGE_UPDATE, start, length); } void term_damage_erase(struct terminal *term, int start, int length) { + assert(start + length <= term->rows * term->cols); term_damage_update_or_erase(term, DAMAGE_ERASE, start, length); } @@ -87,82 +88,53 @@ term_damage_all(struct terminal *term) term_damage_update(term, 0, term->rows * term->cols); } +#if 0 static void damage_adjust_after_scroll(struct terminal *term, enum damage_type damage_type, struct scroll_region region, int lines) { - const int adjustment - = lines * term->cols * (damage_type == DAMAGE_SCROLL_REVERSE ? -1 : 1); - - const int scroll_start = region.start * term->cols; - const int scroll_end = region.end * term->cols; - tll_foreach(term->grid->damage, it) { +#if 0 + if (it->item.range.start < term->grid->offset) { + int end = it->item.range.start + it->item.range.length; + if (end >= term->grid->offset) { + it->item.range.start = term->grid->offset; + it->item.range.length = end - it->item.range.start; + } else { + tll_remove(term->grid->damage, it); + } + } +#endif + int start = it->item.range.start; - int length = it->item.range.length; - int end = start + length; + int end = start + it->item.range.length; - if (start < scroll_start && end > scroll_start) { - /* Start outside, end either inside or on the other side */ - struct damage outside = { - .type = it->item.type, - .range = {.start = start, .length = scroll_start - start}, - }; - - tll_push_back(term->grid->damage, outside); - start = scroll_start; - length = end - start; - - } - - if (start < scroll_end && end > scroll_end) { - /* End outside, start either inside or on the other side */ - struct damage outside = { - .type = it->item.type, - .range = {.start = scroll_end, .length = length - scroll_end}, - }; - - tll_push_back(term->grid->damage, outside); - end = scroll_end; - length = end - start; - } - - if (start >= scroll_start && end <= scroll_end) { - /* Completely inside scroll region */ - start -= adjustment; - it->item.range.start = start; - - if (start < scroll_start) { - /* Scrolled up outside scroll region */ - int new_length = length - (scroll_start - start); - assert(new_length < length); - - if (new_length <= 0) - tll_remove(term->grid->damage, it); - else { - it->item.range.start = scroll_start; - it->item.range.length = new_length; - } - } - - if (start + length > scroll_end) { - /* Scrolled down outside scroll region */ - if (start >= scroll_end) - tll_remove(term->grid->damage, it); - else { - it->item.range.start = start; - it->item.range.length = scroll_end - start; - } - } - } + if (start - } } +#endif void term_damage_scroll(struct terminal *term, enum damage_type damage_type, struct scroll_region region, int lines) { - damage_adjust_after_scroll(term, damage_type, region, lines); + //damage_adjust_after_scroll(term, damage_type, region, lines); + if (damage_type == DAMAGE_SCROLL) { + tll_foreach(term->grid->damage, it) { + int start = it->item.range.start; + int length = it->item.range.length; + + if (start < term->grid->offset) { + int end = start + length; + if (end >= term->grid->offset) { + it->item.range.start = term->grid->offset; + it->item.range.length = end - it->item.range.start; + } else + tll_remove(term->grid->damage, it); + } else + break; + } + } if (tll_length(term->grid->scroll_damage) > 0) { struct damage *dmg = &tll_back(term->grid->scroll_damage); @@ -185,8 +157,11 @@ term_damage_scroll(struct terminal *term, enum damage_type damage_type, void term_erase(struct terminal *term, int start, int end) { + LOG_DBG("erase: %d-%d", start, end); assert(end >= start); - memset(&term->grid->cells[start], 0, (end - start) * sizeof(term->grid->cells[0])); + assert(end <= term->rows * term->cols); + + grid_memset(term->grid, start, 0, end - start); term_damage_erase(term, start, end - start); } @@ -199,36 +174,41 @@ term_cursor_linear(const struct terminal *term, int row, int col) void term_cursor_to(struct terminal *term, int row, int col) { - assert(row >= 0); assert(row < term->rows); - assert(col >= 0); assert(col < term->cols); int new_linear = row * term->cols + col; - assert(new_linear >= 0); assert(new_linear < term->rows * term->cols); - term_damage_update(term, term->cursor.linear, 1); - term_damage_update(term, new_linear, 1); term->print_needs_wrap = false; term->cursor.linear = new_linear; term->cursor.col = col; term->cursor.row = row; + + int len = term->cols; + term->grid->cur_line = grid_get_range( + term->grid, term->cursor.linear - col, &len); + + assert(len == term->cols); } void term_cursor_left(struct terminal *term, int count) { int move_amount = min(term->cursor.col, count); - term_cursor_to(term, term->cursor.row, term->cursor.col - move_amount); + term->cursor.linear -= move_amount; + term->cursor.col -= move_amount; + term->print_needs_wrap = false; } void term_cursor_right(struct terminal *term, int count) { int move_amount = min(term->cols - term->cursor.col - 1, count); - term_cursor_to(term, term->cursor.row, term->cursor.col + move_amount); + term->cursor.linear += move_amount; + term->cursor.col += move_amount; + term->print_needs_wrap = false; } void @@ -248,27 +228,61 @@ term_cursor_down(struct terminal *term, int count) void term_scroll_partial(struct terminal *term, struct scroll_region region, int rows) { - if (rows >= region.end - region.start) { - assert(false && "untested"); - return; + LOG_DBG("scroll: %d rows", rows); + + if (region.start > 0) { + /* TODO: check if it's worth memoving the scroll area instead, + * under certain circumstances */ + + grid_memmove(term->grid, rows * term->cols, 0, region.start * term->cols); + + tll_foreach(term->grid->damage, it) { + int start = it->item.range.start - term->grid->offset; + int end __attribute__((unused)) = start + it->item.range.length; + + if (start < region.start * term->cols) { + assert(end <= region.start * term->cols); + it->item.range.start += rows * term->cols; + } + } } - int cell_dst = (region.start + 0) * term->cols; - int cell_src = (region.start + rows) * term->cols; - int cell_count = (region.end - region.start - rows) * term->cols; + if (region.end < term->rows) { + /* Copy scrolled-up bottom region to new bottom region */ + grid_memmove( + term->grid, + (region.end + rows) * term->cols, + region.end * term->cols, + (term->rows - region.end) * term->cols); - LOG_DBG("moving %d lines from row %d to row %d", cell_count / term->cols, - cell_src / term->cols, cell_dst / term->cols); + tll_foreach(term->grid->damage, it) { + int start = it->item.range.start - term->grid->offset; + int end = start + it->item.range.length; - const size_t bytes = cell_count * sizeof(term->grid->cells[0]); - memmove( - &term->grid->cells[cell_dst], &term->grid->cells[cell_src], - bytes); + if (end > region.end * term->cols) { + assert(start >= region.end * term->cols); + it->item.range.start += rows * term->cols; + } + } + } - memset(&term->grid->cells[(region.end - rows) * term->cols], 0, - rows * term->cols * sizeof(term->grid->cells[0])); + /* Offset grid origin */ + term->grid->offset += rows * term->cols; + + /* Clear scrolled-in lines */ + grid_memset( + term->grid, + max(0, region.end - rows) * term->cols, + 0, + min(rows, term->rows) * term->cols); term_damage_scroll(term, DAMAGE_SCROLL, region, rows); + + int len = term->cols; + term->grid->cur_line = grid_get_range( + term->grid, term->cursor.linear - term->cursor.col, &len); + + assert(len == term->cols); } void @@ -281,27 +295,56 @@ void term_scroll_reverse_partial(struct terminal *term, struct scroll_region region, int rows) { - if (rows >= region.end - region.start) { - assert(false && "todo"); - return; + if (region.end < term->rows) { + grid_memmove( + term->grid, + (region.end - rows) * term->cols, + region.end * term->cols, + (term->rows - region.end) * term->cols); + + tll_foreach(term->grid->damage, it) { + int start = it->item.range.start - term->grid->offset; + int end = start + it->item.range.length; + + if (end > region.end * term->cols) { + assert(start >= region.end * term->cols); + it->item.range.start -= rows * term->cols; + } + } + } - int cell_dst = (region.start + rows) * term->cols; - int cell_src = (region.start + 0) * term->cols; - int cell_count = (region.end - region.start - rows) * term->cols; + if (region.start > 0) { + grid_memmove( + term->grid, -rows * term->cols, 0, region.start * term->cols); - LOG_DBG("moving %d lines from row %d to row %d", cell_count / term->cols, - cell_src / term->cols, cell_dst / term->cols); + tll_foreach(term->grid->damage, it) { + int start = it->item.range.start - term->grid->offset; + int end __attribute__((unused)) = start + it->item.range.length; - const size_t bytes = cell_count * sizeof(term->grid->cells[0]); - memmove( - &term->grid->cells[cell_dst], &term->grid->cells[cell_src], - bytes); + if (start < region.start * term->cols) { + if (end > region.start * term->cols) { + LOG_ERR("region.start = %d, rows = %d, damage.start = %d, damage.end = %d (%s)", + region.start, rows, start, end, it->item.type == DAMAGE_UPDATE ? "UPDATE" : "ERASE"); + abort(); + } + assert(end <= region.start * term->cols); + it->item.range.start -= rows * term->cols; + } + } + } - memset(&term->grid->cells[cell_src], 0, - rows * term->cols * sizeof(term->grid->cells[0])); + term->grid->offset -= rows * term->cols; + + grid_memset(term->grid, region.start * term->cols, 0, rows * term->cols); term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows); + + int len = term->cols; + term->grid->cur_line = grid_get_range( + term->grid, term->cursor.linear - term->cursor.col, &len); + + assert(len == term->cols); } void diff --git a/terminal.h b/terminal.h index cceaa6b6..f48158e5 100644 --- a/terminal.h +++ b/terminal.h @@ -62,10 +62,11 @@ struct damage { }; struct grid { - size_t size; - size_t offset; + int size; + int offset; struct cell *cells; + struct cell *cur_line; tll(struct damage) damage; tll(struct damage) scroll_damage; @@ -73,7 +74,7 @@ struct grid { struct vt_subparams { unsigned value[16]; - size_t idx; + int idx; }; struct vt_param { @@ -85,20 +86,20 @@ struct vt { int state; /* enum state */ struct { struct vt_param v[16]; - size_t idx; + int idx; } params; struct { uint8_t data[2]; - size_t idx; + int idx; } intermediates; struct { uint8_t data[1024]; - size_t idx; + int idx; } osc; struct { uint8_t data[4]; - size_t idx; - size_t left; + int idx; + int left; } utf8; struct attributes attrs; bool dim; @@ -132,6 +133,9 @@ struct terminal { enum decckm decckm; enum keypad_mode keypad_mode; + bool hide_cursor; + bool auto_margin; + bool insert_mode; bool bracketed_paste; struct vt vt; diff --git a/vt.c b/vt.c index c7e75a94..8f1db921 100644 --- a/vt.c +++ b/vt.c @@ -5,7 +5,7 @@ #include #define LOG_MODULE "vt" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "csi.h" #include "osc.h" @@ -555,7 +555,7 @@ static const enum action exit_actions[] = { static bool esc_dispatch(struct terminal *term, uint8_t final) { -#if defined(_DEBUG) && defined(LOG_ENABLE_DBG) && LOG_ENABLED_DBG +#if defined(_DEBUG) && defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG char log[1024]; int c = snprintf(log, sizeof(log), "ESC: "); @@ -568,6 +568,7 @@ esc_dispatch(struct terminal *term, uint8_t final) switch (final) { case 'B': { + /* Configure G0-G3 to use ASCII */ char param = term->vt.params.idx > 0 ? term->vt.params.v[0].value : '('; switch (param) { @@ -578,11 +579,30 @@ esc_dispatch(struct terminal *term, uint8_t final) case ')': case '*': case '+': - LOG_ERR("unimplemented: character charset: %c", param); + LOG_WARN("unimplemented: charset %c uses ASCII", param); return false; default: - LOG_ERR("ESC B: invalid charset identifier: %c", param); + LOG_ERR("%cB: invalid charset identifier", param); + return false; + } + break; + } + + case '0': { + /* Configure G0-G3 to use special chars + line drawing */ + char param = term->vt.params.idx > 0 ? term->vt.params.v[0].value : '('; + + switch (param) { + case '(': + case ')': + case '*': + case '+': + LOG_WARN("unimplemented: charset %c uses special characters and line drawings", param); + break; + + default: + LOG_ERR("%c0: invalid charset identifier", param); return false; } break; @@ -664,13 +684,13 @@ action(struct terminal *term, enum action action, uint8_t c) case ACTION_CLEAR: memset(&term->vt.params, 0, sizeof(term->vt.params)); - memset(&term->vt.intermediates, 0, sizeof(term->vt.intermediates)); - memset(&term->vt.osc, 0, sizeof(term->vt.osc)); - memset(&term->vt.utf8, 0, sizeof(term->vt.utf8)); + term->vt.intermediates.idx = 0; + term->vt.osc.idx = 0; + term->vt.utf8.idx = 0; break; case ACTION_PRINT: { - if (term->print_needs_wrap) { + if (term->auto_margin && term->print_needs_wrap) { if (term->cursor.row == term->scroll_region.end - 1) { term_scroll(term, 1); term_cursor_to(term, term->cursor.row, 0); @@ -678,14 +698,22 @@ action(struct terminal *term, enum action action, uint8_t c) term_cursor_to(term, term->cursor.row + 1, 0); } - struct cell *cell = &term->grid->cells[term->cursor.linear]; + struct cell *cell = &term->grid->cur_line[term->cursor.col]; term_damage_update(term, term->cursor.linear, 1); + if (term->insert_mode) { + assert(false && "untested"); + grid_memmove( + term->grid, term->cursor.linear + 1, term->cursor.linear, + term->cols - term->cursor.col - 1); + term_damage_update( + term, term->cursor.linear + 1, term->cols - term->cursor.col - 1); + } + if (term->vt.utf8.idx > 0) { //LOG_DBG("print: UTF8: %.*s", (int)term->vt.utf8.idx, term->vt.utf8.data); memcpy(cell->c, term->vt.utf8.data, term->vt.utf8.idx); cell->c[term->vt.utf8.idx] = '\0'; - memset(&term->vt.utf8, 0, sizeof(term->vt.utf8)); } else { //LOG_DBG("print: ASCII: %c", c); cell->c[0] = c;