diff --git a/commands.c b/commands.c new file mode 100644 index 00000000..472cf880 --- /dev/null +++ b/commands.c @@ -0,0 +1,121 @@ +#include "commands.h" + +#define LOG_MODULE "commands" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "terminal.h" +#include "render.h" +#include "grid.h" + +#define max(x, y) ((x) > (y) ? (x) : (y)) +#define min(x, y) ((x) < (y) ? (x) : (y)) + +void +cmd_scrollback_up(struct terminal *term, int rows) +{ + if (term->grid == &term->alt) + return; + + rows = min(rows, term->grid->num_rows - term->rows); + + assert(term->grid->offset >= 0); + assert(rows <= term->rows); + + int new_view = (term->grid->view + term->grid->num_rows - rows) % term->grid->num_rows; + assert(new_view >= 0); + assert(new_view < term->grid->num_rows); + + /* Avoid scrolling in uninitialized rows */ + while (!term->grid->rows[new_view]->initialized) + new_view = (new_view + 1) % term->grid->num_rows; + + if (new_view == term->grid->view) { + /* + * This happens when scrolling up in a newly opened terminal; + * every single line (except those already visible) are + * uninitalized, and the loop above will bring us back to + * where we started. + */ + return; + } + + /* Don't scroll past scrollback history */ + int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows; + if (end >= term->grid->offset) { + /* Not wrapped */ + if (new_view >= term->grid->offset && new_view <= end) + new_view = end + 1; + } else { + if (new_view >= term->grid->offset || new_view <= end) + new_view = end + 1; + } + + LOG_DBG("scrollback UP: %d -> %d (offset = %d, end = %d, rows = %d)", + term->grid->view, new_view, term->grid->offset, end, term->grid->num_rows); + + if (new_view == term->grid->view) + return; + + term->grid->view = new_view; + + term_damage_view(term); + if (term->frame_callback == NULL) + grid_render(term); +} + +void +cmd_scrollback_down(struct terminal *term, int rows) +{ + if (term->grid == &term->alt) + return; + + if (term->grid->view == term->grid->offset) + return; + + rows = min(rows, term->grid->num_rows - term->rows); + + assert(term->grid->offset >= 0); + + int new_view = (term->grid->view + rows) % term->grid->num_rows; + assert(new_view >= 0); + assert(new_view < term->grid->num_rows); + + /* Prevent scrolling in uninitialized rows */ + bool all_initialized = false; + do { + all_initialized = true; + + for (int i = 0; i < term->rows; i++) { + int row_no = (new_view + i) % term->grid->num_rows; + if (!term->grid->rows[row_no]->initialized) { + all_initialized = false; + new_view--; + break; + } + } + } while (!all_initialized); + + /* Don't scroll past scrollback history */ + int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows; + if (end >= term->grid->offset) { + /* Not wrapped */ + if (new_view >= term->grid->offset && new_view <= end) + new_view = term->grid->offset; + } else { + if (new_view >= term->grid->offset || new_view <= end) + new_view = term->grid->offset; + } + + + LOG_DBG("scrollback DOWN: %d -> %d (offset = %d, end = %d, rows = %d)", + term->grid->view, new_view, term->grid->offset, end, term->grid->num_rows); + + if (new_view == term->grid->view) + return; + + term->grid->view = new_view; + + term_damage_view(term); + if (term->frame_callback == NULL) + grid_render(term); +} diff --git a/commands.h b/commands.h new file mode 100644 index 00000000..644523b0 --- /dev/null +++ b/commands.h @@ -0,0 +1,6 @@ +#pragma once + +#include "terminal.h" + +void cmd_scrollback_up(struct terminal *term, int rows); +void cmd_scrollback_down(struct terminal *term, int rows); diff --git a/grid.h b/grid.h index d6ddabee..e170f6dd 100644 --- a/grid.h +++ b/grid.h @@ -10,6 +10,13 @@ grid_row(struct grid *grid, int row) return grid->rows[(grid->offset + row + grid->num_rows) % grid->num_rows]; } +static inline struct row * +grid_row_in_view(struct grid *grid, int row) +{ + assert(grid->view >= 0); + return grid->rows[(grid->view + row + grid->num_rows) % grid->num_rows]; +} + static inline void grid_swap_row(struct grid *grid, int row_a, int row_b) { diff --git a/input.c b/input.c index 4ac45bbe..33547fcd 100644 --- a/input.c +++ b/input.c @@ -7,6 +7,8 @@ #include #include +#include + #include #include #include @@ -17,6 +19,7 @@ #include "terminal.h" #include "render.h" #include "keymap.h" +#include "commands.h" static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, @@ -140,6 +143,18 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, keymap_mods |= term->kbd.alt ? MOD_ALT : MOD_NONE; keymap_mods |= term->kbd.ctrl ? MOD_CTRL : MOD_NONE; + if (effective_mods == shift) { + if (sym == XKB_KEY_Page_Up) { + cmd_scrollback_up(term, term->rows); + found_map = true; + } + + else if (sym == XKB_KEY_Page_Down) { + cmd_scrollback_down(term, term->rows); + found_map = true; + } + } + for (size_t i = 0; i < sizeof(key_map) / sizeof(key_map[0]) && !found_map; i++) { const struct key_map *k = &key_map[i]; if (k->sym != sym) @@ -160,6 +175,13 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, write(term->ptmx, info->seq, strlen(info->seq)); found_map = true; + + if (term->grid->view != term->grid->offset) { + term->grid->view = term->grid->offset; + /* TODO: damage view */ + term_damage_all(term); + } + break; } } @@ -182,6 +204,13 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, write(term->ptmx, "\x1b", 1); write(term->ptmx, buf, count); + + if (term->grid->view != term->grid->offset) { + term->grid->view = term->grid->offset; + /* TODO: damage view */ + term_damage_all(term); + } + } } @@ -315,6 +344,28 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + struct terminal *term = data; + + /* TODO: generate button event for BTN_FORWARD/BTN_BACK? */ + + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) + return; + + int amount = wl_fixed_to_int(value); + + if (amount < 0) { + for (int i = 0; i < -amount; i++) { + term_mouse_down(term, BTN_BACK, term->mouse.row, term->mouse.col, + term->kbd.shift, term->kbd.alt, term->kbd.ctrl); + } + cmd_scrollback_up(term, -amount); + } else { + for (int i = 0; i < amount; i++) { + term_mouse_down(term, BTN_FORWARD, term->mouse.row, term->mouse.col, + term->kbd.shift, term->kbd.alt, term->kbd.ctrl); + } + cmd_scrollback_down(term, amount); + } } static void diff --git a/main.c b/main.c index e454e103..dd16d277 100644 --- a/main.c +++ b/main.c @@ -495,7 +495,7 @@ main(int argc, char *const *argv) break; } - if (ret == 0 || !(timeout_ms != -1 && fds[1].revents & POLLIN)) { + if (ret == 0 || (timeout_ms != -1 && !(fds[1].revents & POLLIN))) { /* Delayed rendering */ if (term.frame_callback == NULL) grid_render(&term); diff --git a/meson.build b/meson.build index c55938b1..7cb057bf 100644 --- a/meson.build +++ b/meson.build @@ -58,6 +58,7 @@ endforeach executable( 'f00ter', + 'commands.c', 'commands.h', 'csi.c', 'csi.h', 'font.c', 'font.h', 'grid.c', 'grid.h', diff --git a/render.c b/render.c index d42a04ec..209d8f6f 100644 --- a/render.c +++ b/render.c @@ -35,13 +35,8 @@ static struct glyph_sequence gseq; static void render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell, - int col, int row) + int col, int row, bool has_cursor) { - /* Cursor here? */ - bool has_cursor - = (!term->hide_cursor && - (term->cursor.col == col && term->cursor.row == row)); - double width = term->cell_width; double height = term->cell_height; double x = col * width; @@ -243,7 +238,7 @@ grid_render(struct terminal *term) gseq.count = 0; for (int r = 0; r < term->rows; r++) { - struct row *row = grid_row(term->grid, r); + struct row *row = grid_row_in_view(term->grid, r); if (!row->dirty) continue; @@ -251,7 +246,7 @@ grid_render(struct terminal *term) //LOG_WARN("rendering line: %d", r); for (int col = 0; col < term->cols; col++) - render_cell(term, buf, &row->cells[col], col, r); + render_cell(term, buf, &row->cells[col], col, r, false); row->dirty = false; all_clean = false; @@ -269,7 +264,7 @@ grid_render(struct terminal *term) int row = last_cursor / term->cols - term->grid->offset; int col = last_cursor % term->cols; if (row >= 0 && row < term->rows) { - render_cell(term, buf, &grid_row(term->grid, row)->cells[col], col, row); + render_cell(term, buf, &grid_row_in_view(term->grid, row)->cells[col], col, row, false); all_clean = false; wl_surface_damage_buffer( @@ -284,16 +279,31 @@ grid_render(struct terminal *term) return; } - render_cell( - term, buf, - &grid_row(term->grid, term->cursor.row)->cells[term->cursor.col], - term->cursor.col, term->cursor.row); + bool cursor_is_visible = false; + int view_end = (term->grid->view + term->rows - 1) % term->grid->num_rows; + int cursor_row = (term->grid->offset + term->cursor.row) % term->grid->num_rows; + if (view_end >= term->grid->view) { + /* Not wrapped */ + if (cursor_row >= term->grid->view && cursor_row <= view_end) + cursor_is_visible = true; + } else { + /* Wrapped */ + if (cursor_row >= term->grid->view || cursor_row <= view_end) + cursor_is_visible = true; + } - wl_surface_damage_buffer( - term->wl.surface, - term->cursor.col * term->cell_width, - term->cursor.row * term->cell_height, - term->cell_width, term->cell_height); + if (cursor_is_visible) { + render_cell( + term, buf, + &grid_row_in_view(term->grid, term->cursor.row)->cells[term->cursor.col], + term->cursor.col, term->cursor.row, true); + + wl_surface_damage_buffer( + term->wl.surface, + term->cursor.col * term->cell_width, + term->cursor.row * term->cell_height, + term->cell_width, term->cell_height); + } if (gseq.count > 0) { cairo_set_scaled_font(buf->cairo, attrs_to_font(term, &gseq.attrs)); @@ -304,6 +314,7 @@ grid_render(struct terminal *term) } assert(term->grid->offset >= 0 && term->grid->offset < term->grid->num_rows); + assert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows); cairo_surface_flush(buf->cairo_surface); wl_surface_attach(term->wl.surface, buf->wl_buf, 0, 0); @@ -338,12 +349,18 @@ reflow(struct row **new_grid, int new_cols, int new_rows, struct cell *new_cells = new_grid[r]->cells; const struct cell *old_cells = old_grid[r]->cells; + new_grid[r]->initialized = old_grid[r]->initialized; + new_grid[r]->dirty = old_grid[r]->dirty; + memcpy(new_cells, old_cells, copy_cols * sizeof(new_cells[0])); memset(&new_cells[copy_cols], 0, clear_cols * sizeof(new_cells[0])); } - for (int r = min(new_rows, old_rows); r < new_rows; r++) + for (int r = min(new_rows, old_rows); r < new_rows; r++) { + new_grid[r]->initialized = false; + new_grid[r]->dirty = false; memset(new_grid[r]->cells, 0, new_cols * sizeof(new_grid[r]->cells[0])); + } } /* Move to terminal.c? */ @@ -356,6 +373,8 @@ render_resize(struct terminal *term, int width, int height) term->width = width; term->height = height; + const int scrollback_lines = 10000; + const int old_cols = term->cols; const int old_rows = term->rows; const int old_normal_grid_rows = term->normal.num_rows; @@ -363,7 +382,7 @@ render_resize(struct terminal *term, int width, int height) const int new_cols = term->width / term->cell_width; const int new_rows = term->height / term->cell_height; - const int new_normal_grid_rows = new_rows; + const int new_normal_grid_rows = new_rows + scrollback_lines; const int new_alt_grid_rows = new_rows; /* Allocate new 'normal' grid */ @@ -388,6 +407,11 @@ render_resize(struct terminal *term, int width, int height) reflow(alt, new_cols, new_alt_grid_rows, term->alt.rows, old_cols, old_alt_grid_rows); + for (int r = 0; r < new_rows; r++) { + normal[r]->initialized = true; + alt[r]->initialized = true; + } + /* Free old 'normal' grid */ for (int r = 0; r < term->normal.num_rows; r++) { free(term->normal.rows[r]->cells); @@ -430,11 +454,19 @@ render_resize(struct terminal *term, int width, int height) if (term->scroll_region.end >= old_rows) term->scroll_region.end = term->rows; + term->normal.offset %= term->normal.num_rows; + term->normal.view %= term->normal.num_rows; + + term->alt.offset %= term->alt.num_rows; + term->alt.view %= term->alt.num_rows; + term_cursor_to( term, min(term->cursor.row, term->rows - 1), min(term->cursor.col, term->cols - 1)); + term_damage_all(term); + term_damage_view(term); if (term->frame_callback == NULL) grid_render(term); diff --git a/terminal.c b/terminal.c index 1cf15cfb..8804140b 100644 --- a/terminal.c +++ b/terminal.c @@ -21,6 +21,13 @@ term_damage_all(struct terminal *term) grid_row(term->grid, i)->dirty = true; } +void +term_damage_view(struct terminal *term) +{ + for (int i = 0; i < term->rows; i++) + grid_row_in_view(term->grid, i)->dirty = true; +} + void term_damage_scroll(struct terminal *term, enum damage_type damage_type, struct scroll_region region, int lines) @@ -152,11 +159,18 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows grid_swap_row(term->grid, i, i + rows); /* Offset grid origin */ + bool view_follows = term->grid->view == term->grid->offset; term->grid->offset += rows; term->grid->offset %= term->grid->num_rows; - for (int r = max(region.end - rows, 0); r < region.end; r++) - erase_line(term, grid_row(term->grid, r)); + if (view_follows) + term->grid->view = term->grid->offset; + + for (int r = max(region.end - rows, 0); r < region.end; r++) { + struct row *row = grid_row(term->grid, r); + erase_line(term, row); + row->initialized = true; + } term_damage_scroll(term, DAMAGE_SCROLL, region, rows); term->grid->cur_row = grid_row(term->grid, term->cursor.row); @@ -174,9 +188,14 @@ term_scroll_reverse_partial(struct terminal *term, { assert(rows < term->rows && "unimplemented"); + bool view_follows = term->grid->view == term->grid->offset; + term->grid->offset += term->grid->num_rows - rows; term->grid->offset %= term->grid->num_rows; + if (view_follows) + term->grid->view = term->grid->offset; + /* Bottom non-scrolling region */ for (int i = region.end + rows; i < term->rows + rows; i++) grid_swap_row(term->grid, i, i - rows); diff --git a/terminal.h b/terminal.h index 6f42398a..7d379229 100644 --- a/terminal.h +++ b/terminal.h @@ -94,11 +94,13 @@ struct damage { struct row { struct cell *cells; bool dirty; + bool initialized; }; struct grid { int num_rows; int offset; + int view; struct row **rows; struct row *cur_row; @@ -248,6 +250,7 @@ struct terminal { }; void term_damage_all(struct terminal *term); +void term_damage_view(struct terminal *term); void term_damage_scroll( struct terminal *term, enum damage_type damage_type, struct scroll_region region, int lines);