2 * libtsm - Screen Management
4 * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files
8 * (the "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 * This provides the abstracted screen management. It does not do any
29 * terminal-emulation, instead it provides a resizable table of cells. You can
30 * insert, remove and modify the cells freely.
31 * A screen has always a fixed, but changeable, width and height. This defines
32 * the number of columns and rows. The screen doesn't care for pixels, glyphs or
33 * framebuffers. The screen only contains information about each cell.
35 * Screens are the logical model behind a real screen of a terminal emulator.
36 * Users usually allocate a screen for each terminal-emulator they run. All they
37 * have to do is render the screen onto their widget on each change and forward
38 * any widget-events to the screen.
40 * The screen object already includes scrollback-buffers, selection support and
41 * more. This simplifies terminal emulators a lot, but also prevents them from
42 * accessing the real screen data. However, terminal emulators should have no
43 * reason to access the data directly. The screen API should provide everything
47 * Each cell, line and screen has an "age" field. This field describes when it
48 * was changed the last time. After drawing a screen, the current screen age is
49 * returned. This allows users to skip drawing specific cells, if their
50 * framebuffer was already drawn with a newer age than a given cell.
51 * However, the screen-age might overflow. This is properly detected and causes
52 * drawing functions to return "0" as age. Users must reset all their
53 * framebuffer ages then. Otherwise, further drawing operations might
54 * incorrectly skip cells.
55 * Furthermore, if a cell has age "0", it means it _has_ to be drawn. No ageing
56 * information is available.
65 #include "libtsm_int.h"
68 #define LLOG_SUBSYSTEM "tsm_screen"
73 struct tsm_screen_attr attr;
87 #define SELECTION_TOP -1
88 struct selection_pos {
100 struct tsm_symbol_table *sym_table;
102 /* default attributes for new cells */
103 struct tsm_screen_attr def_attr;
107 unsigned int age_reset : 1;
112 unsigned int margin_top;
113 unsigned int margin_bottom;
114 unsigned int line_num;
116 struct line **main_lines;
117 struct line **alt_lines;
120 /* scroll-back buffer */
121 unsigned int sb_count; /* number of lines in sb */
122 struct line *sb_first; /* first line; was moved first */
123 struct line *sb_last; /* last line; was moved last*/
124 unsigned int sb_max; /* max-limit of lines in sb */
125 struct line *sb_pos; /* current position in sb or NULL */
126 uint64_t sb_last_id; /* last id given to sb-line */
129 unsigned int cursor_x;
130 unsigned int cursor_y;
137 struct selection_pos sel_start;
138 struct selection_pos sel_end;
141 static void inc_age(struct tsm_screen *con)
143 if (!++con->age_cnt) {
149 static struct cell *get_cursor_cell(struct tsm_screen *con)
151 unsigned int cur_x, cur_y;
153 cur_x = con->cursor_x;
154 if (cur_x >= con->size_x)
155 cur_x = con->size_x - 1;
157 cur_y = con->cursor_y;
158 if (cur_y >= con->size_y)
159 cur_y = con->size_y - 1;
161 return &con->lines[cur_y]->cells[cur_x];
164 static void move_cursor(struct tsm_screen *con, unsigned int x, unsigned int y)
168 if (con->cursor_x == x && con->cursor_y == y)
171 c = get_cursor_cell(con);
172 c->age = con->age_cnt;
177 c = get_cursor_cell(con);
178 c->age = con->age_cnt;
181 static void cell_init(struct tsm_screen *con, struct cell *cell)
185 cell->age = con->age_cnt;
186 memcpy(&cell->attr, &con->def_attr, sizeof(cell->attr));
189 static int line_new(struct tsm_screen *con, struct line **out,
198 line = malloc(sizeof(*line));
204 line->age = con->age_cnt;
206 line->cells = malloc(sizeof(struct cell) * width);
212 for (i = 0; i < width; ++i)
213 cell_init(con, &line->cells[i]);
219 static void line_free(struct line *line)
225 static int line_resize(struct tsm_screen *con, struct line *line,
233 if (line->size < width) {
234 tmp = realloc(line->cells, width * sizeof(struct cell));
240 while (line->size < width) {
241 cell_init(con, &line->cells[line->size]);
249 /* This links the given line into the scrollback-buffer */
250 static void link_to_scrollback(struct tsm_screen *con, struct line *line)
254 /* TODO: more sophisticated ageing */
255 con->age = con->age_cnt;
257 if (con->sb_max == 0) {
258 if (con->sel_active) {
259 if (con->sel_start.line == line) {
260 con->sel_start.line = NULL;
261 con->sel_start.y = SELECTION_TOP;
263 if (con->sel_end.line == line) {
264 con->sel_end.line = NULL;
265 con->sel_end.y = SELECTION_TOP;
272 /* Remove a line from the scrollback buffer if it reaches its maximum.
273 * We must take care to correctly keep the current position as the new
274 * line is linked in after we remove the top-most line here.
275 * sb_max == 0 is tested earlier so we can assume sb_max > 0 here. In
276 * other words, buf->sb_first is a valid line if sb_count >= sb_max. */
277 if (con->sb_count >= con->sb_max) {
279 con->sb_first = tmp->next;
281 tmp->next->prev = NULL;
286 /* (position == tmp && !next) means we have sb_max=1 so set
287 * position to the new line. Otherwise, set to new first line.
288 * If position!=tmp and we have a fixed-position then nothing
289 * needs to be done because we can stay at the same line. If we
290 * have no fixed-position, we need to set the position to the
291 * next inserted line, which can be "line", too. */
293 if (con->sb_pos == tmp ||
294 !(con->flags & TSM_SCREEN_FIXED_POS)) {
295 if (con->sb_pos->next)
296 con->sb_pos = con->sb_pos->next;
302 if (con->sel_active) {
303 if (con->sel_start.line == tmp) {
304 con->sel_start.line = NULL;
305 con->sel_start.y = SELECTION_TOP;
307 if (con->sel_end.line == tmp) {
308 con->sel_end.line = NULL;
309 con->sel_end.y = SELECTION_TOP;
315 line->sb_id = ++con->sb_last_id;
317 line->prev = con->sb_last;
319 con->sb_last->next = line;
321 con->sb_first = line;
326 static void screen_scroll_up(struct tsm_screen *con, unsigned int num)
328 unsigned int i, j, max, pos;
334 /* TODO: more sophisticated ageing */
335 con->age = con->age_cnt;
337 max = con->margin_bottom + 1 - con->margin_top;
341 /* We cache lines on the stack to speed up the scrolling. However, if
342 * num is too big we might get overflows here so use recursion if num
343 * exceeds a hard-coded limit.
344 * 128 seems to be a sane limit that should never be reached but should
345 * also be small enough so we do not get stack overflows. */
347 screen_scroll_up(con, 128);
348 return screen_scroll_up(con, num - 128);
350 struct line *cache[num];
352 for (i = 0; i < num; ++i) {
353 pos = con->margin_top + i;
354 if (!(con->flags & TSM_SCREEN_ALTERNATE))
355 ret = line_new(con, &cache[i], con->size_x);
360 link_to_scrollback(con, con->lines[pos]);
362 cache[i] = con->lines[pos];
363 for (j = 0; j < con->size_x; ++j)
364 cell_init(con, &cache[i]->cells[j]);
369 memmove(&con->lines[con->margin_top],
370 &con->lines[con->margin_top + num],
371 (max - num) * sizeof(struct line*));
374 memcpy(&con->lines[con->margin_top + (max - num)],
375 cache, num * sizeof(struct line*));
377 if (con->sel_active) {
378 if (!con->sel_start.line && con->sel_start.y >= 0) {
379 con->sel_start.y -= num;
380 if (con->sel_start.y < 0) {
381 con->sel_start.line = con->sb_last;
382 while (con->sel_start.line && ++con->sel_start.y < 0)
383 con->sel_start.line = con->sel_start.line->prev;
384 con->sel_start.y = SELECTION_TOP;
387 if (!con->sel_end.line && con->sel_end.y >= 0) {
388 con->sel_end.y -= num;
389 if (con->sel_end.y < 0) {
390 con->sel_end.line = con->sb_last;
391 while (con->sel_end.line && ++con->sel_end.y < 0)
392 con->sel_end.line = con->sel_end.line->prev;
393 con->sel_end.y = SELECTION_TOP;
399 static void screen_scroll_down(struct tsm_screen *con, unsigned int num)
401 unsigned int i, j, max;
406 /* TODO: more sophisticated ageing */
407 con->age = con->age_cnt;
409 max = con->margin_bottom + 1 - con->margin_top;
413 /* see screen_scroll_up() for an explanation */
415 screen_scroll_down(con, 128);
416 return screen_scroll_down(con, num - 128);
418 struct line *cache[num];
420 for (i = 0; i < num; ++i) {
421 cache[i] = con->lines[con->margin_bottom - i];
422 for (j = 0; j < con->size_x; ++j)
423 cell_init(con, &cache[i]->cells[j]);
427 memmove(&con->lines[con->margin_top + num],
428 &con->lines[con->margin_top],
429 (max - num) * sizeof(struct line*));
432 memcpy(&con->lines[con->margin_top],
433 cache, num * sizeof(struct line*));
435 if (con->sel_active) {
436 if (!con->sel_start.line && con->sel_start.y >= 0)
437 con->sel_start.y += num;
438 if (!con->sel_end.line && con->sel_end.y >= 0)
439 con->sel_end.y += num;
443 static void screen_write(struct tsm_screen *con, unsigned int x,
444 unsigned int y, tsm_symbol_t ch, unsigned int len,
445 const struct tsm_screen_attr *attr)
453 if (x >= con->size_x || y >= con->size_y) {
454 llog_warn(con, "writing beyond buffer boundary");
458 line = con->lines[y];
460 if ((con->flags & TSM_SCREEN_INSERT_MODE) &&
461 (int)x < ((int)con->size_x - len)) {
462 line->age = con->age_cnt;
463 memmove(&line->cells[x + len], &line->cells[x],
464 sizeof(struct cell) * (con->size_x - len - x));
467 line->cells[x].age = con->age_cnt;
468 line->cells[x].ch = ch;
469 line->cells[x].width = len;
470 memcpy(&line->cells[x].attr, attr, sizeof(*attr));
472 for (i = 1; i < len && i + x < con->size_x; ++i) {
473 line->cells[x + i].age = con->age_cnt;
474 line->cells[x + i].width = 0;
478 static void screen_erase_region(struct tsm_screen *con,
488 /* TODO: more sophisticated ageing */
489 con->age = con->age_cnt;
491 if (y_to >= con->size_y)
492 y_to = con->size_y - 1;
493 if (x_to >= con->size_x)
494 x_to = con->size_x - 1;
496 for ( ; y_from <= y_to; ++y_from) {
497 line = con->lines[y_from];
506 to = con->size_x - 1;
507 for ( ; x_from <= to; ++x_from) {
508 if (protect && line->cells[x_from].attr.protect)
511 cell_init(con, &line->cells[x_from]);
517 static inline unsigned int to_abs_x(struct tsm_screen *con, unsigned int x)
522 static inline unsigned int to_abs_y(struct tsm_screen *con, unsigned int y)
524 if (!(con->flags & TSM_SCREEN_REL_ORIGIN))
527 return con->margin_top + y;
531 int tsm_screen_new(struct tsm_screen **out, tsm_log_t log, void *log_data)
533 struct tsm_screen *con;
540 con = malloc(sizeof(*con));
544 memset(con, 0, sizeof(*con));
547 con->llog_data = log_data;
549 con->age = con->age_cnt;
550 con->def_attr.fr = 255;
551 con->def_attr.fg = 255;
552 con->def_attr.fb = 255;
554 ret = tsm_symbol_table_new(&con->sym_table);
558 ret = tsm_screen_resize(con, 80, 24);
562 llog_debug(con, "new screen");
568 for (i = 0; i < con->line_num; ++i) {
569 line_free(con->main_lines[i]);
570 line_free(con->alt_lines[i]);
572 free(con->main_lines);
573 free(con->alt_lines);
574 free(con->tab_ruler);
575 tsm_symbol_table_unref(con->sym_table);
581 void tsm_screen_ref(struct tsm_screen *con)
590 void tsm_screen_unref(struct tsm_screen *con)
594 if (!con || !con->ref || --con->ref)
597 llog_debug(con, "destroying screen");
599 for (i = 0; i < con->line_num; ++i) {
600 line_free(con->main_lines[i]);
601 line_free(con->alt_lines[i]);
603 free(con->main_lines);
604 free(con->alt_lines);
605 free(con->tab_ruler);
606 tsm_symbol_table_unref(con->sym_table);
610 void tsm_screen_set_opts(struct tsm_screen *scr, unsigned int opts)
618 void tsm_screen_reset_opts(struct tsm_screen *scr, unsigned int opts)
626 unsigned int tsm_screen_get_opts(struct tsm_screen *scr)
635 unsigned int tsm_screen_get_width(struct tsm_screen *con)
644 unsigned int tsm_screen_get_height(struct tsm_screen *con)
653 int tsm_screen_resize(struct tsm_screen *con, unsigned int x,
657 unsigned int i, j, width, diff, start;
661 if (!con || !x || !y)
664 if (con->size_x == x && con->size_y == y)
667 /* First make sure the line buffer is big enough for our new screen.
668 * That is, allocate all new lines and make sure each line has enough
669 * cells to hold the new screen or the current screen. If we fail, we
670 * can safely return -ENOMEM and the buffer is still valid. We must
671 * allocate the new lines to at least the same size as the current
672 * lines. Otherwise, if this function fails in later turns, we will have
673 * invalid lines in the buffer. */
674 if (y > con->line_num) {
675 /* resize main buffer */
676 cache = realloc(con->main_lines, sizeof(struct line*) * y);
680 if (con->lines == con->main_lines)
682 con->main_lines = cache;
684 /* resize alt buffer */
685 cache = realloc(con->alt_lines, sizeof(struct line*) * y);
689 if (con->lines == con->alt_lines)
691 con->alt_lines = cache;
693 /* allocate new lines */
699 while (con->line_num < y) {
700 ret = line_new(con, &con->main_lines[con->line_num],
705 ret = line_new(con, &con->alt_lines[con->line_num],
708 line_free(con->main_lines[con->line_num]);
716 /* Resize all lines in the buffer if we increase screen width. This
717 * will guarantee that all lines are big enough so we can resize the
718 * buffer without reallocating them later. */
719 if (x > con->size_x) {
720 tab_ruler = realloc(con->tab_ruler, sizeof(bool) * x);
723 con->tab_ruler = tab_ruler;
725 for (i = 0; i < con->line_num; ++i) {
726 ret = line_resize(con, con->main_lines[i], x);
730 ret = line_resize(con, con->alt_lines[i], x);
737 /* TODO: more sophisticated ageing */
738 con->age = con->age_cnt;
740 /* clear expansion/padding area */
744 for (j = 0; j < con->line_num; ++j) {
745 /* main-lines may go into SB, so clear all cells */
750 for ( ; i < con->main_lines[j]->size; ++i)
751 cell_init(con, &con->main_lines[j]->cells[i]);
753 /* alt-lines never go into SB, only clear visible cells */
759 cell_init(con, &con->alt_lines[j]->cells[i]);
762 /* xterm destroys margins on resize, so do we */
764 con->margin_bottom = con->size_y - 1;
767 for (i = 0; i < x; ++i) {
769 con->tab_ruler[i] = true;
771 con->tab_ruler[i] = false;
774 /* We need to adjust x-size first as screen_scroll_up() and friends may
775 * have to reallocate lines. The y-size is adjusted after them to avoid
776 * missing lines when shrinking y-size.
777 * We need to carefully look for the functions that we call here as they
778 * have stronger invariants as when called normally. */
781 if (con->cursor_x >= con->size_x)
782 con->cursor_x = con->size_x - 1;
784 /* scroll buffer if screen height shrinks */
785 if (y < con->size_y) {
786 diff = con->size_y - y;
787 screen_scroll_up(con, diff);
788 if (con->cursor_y > diff)
789 con->cursor_y -= diff;
795 con->margin_bottom = con->size_y - 1;
796 if (con->cursor_y >= con->size_y)
797 con->cursor_y = con->size_y - 1;
803 int tsm_screen_set_margins(struct tsm_screen *con,
804 unsigned int top, unsigned int bottom)
806 unsigned int upper, lower;
816 lower = con->size_y - 1;
817 } else if (bottom > con->size_y) {
819 lower = con->size_y - 1;
825 con->margin_top = upper;
826 con->margin_bottom = lower;
830 /* set maximum scrollback buffer size */
832 void tsm_screen_set_max_sb(struct tsm_screen *con,
841 /* TODO: more sophisticated ageing */
842 con->age = con->age_cnt;
844 while (con->sb_count > max) {
845 line = con->sb_first;
846 con->sb_first = line->next;
848 line->next->prev = NULL;
853 /* We treat fixed/unfixed position the same here because we
854 * remove lines from the TOP of the scrollback buffer. */
855 if (con->sb_pos == line)
856 con->sb_pos = con->sb_first;
858 if (con->sel_active) {
859 if (con->sel_start.line == line) {
860 con->sel_start.line = NULL;
861 con->sel_start.y = SELECTION_TOP;
863 if (con->sel_end.line == line) {
864 con->sel_end.line = NULL;
865 con->sel_end.y = SELECTION_TOP;
874 /* clear scrollback buffer */
876 void tsm_screen_clear_sb(struct tsm_screen *con)
878 struct line *iter, *tmp;
884 /* TODO: more sophisticated ageing */
885 con->age = con->age_cnt;
887 for (iter = con->sb_first; iter; ) {
893 con->sb_first = NULL;
898 if (con->sel_active) {
899 if (con->sel_start.line) {
900 con->sel_start.line = NULL;
901 con->sel_start.y = SELECTION_TOP;
903 if (con->sel_end.line) {
904 con->sel_end.line = NULL;
905 con->sel_end.y = SELECTION_TOP;
911 void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num)
917 /* TODO: more sophisticated ageing */
918 con->age = con->age_cnt;
922 if (!con->sb_pos->prev)
925 con->sb_pos = con->sb_pos->prev;
926 } else if (!con->sb_last) {
929 con->sb_pos = con->sb_last;
935 void tsm_screen_sb_down(struct tsm_screen *con, unsigned int num)
941 /* TODO: more sophisticated ageing */
942 con->age = con->age_cnt;
946 con->sb_pos = con->sb_pos->next;
953 void tsm_screen_sb_page_up(struct tsm_screen *con, unsigned int num)
959 tsm_screen_sb_up(con, num * con->size_y);
963 void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num)
969 tsm_screen_sb_down(con, num * con->size_y);
973 void tsm_screen_sb_reset(struct tsm_screen *con)
979 /* TODO: more sophisticated ageing */
980 con->age = con->age_cnt;
986 void tsm_screen_set_def_attr(struct tsm_screen *con,
987 const struct tsm_screen_attr *attr)
992 memcpy(&con->def_attr, attr, sizeof(*attr));
996 void tsm_screen_reset(struct tsm_screen *con)
1004 con->age = con->age_cnt;
1007 con->margin_top = 0;
1008 con->margin_bottom = con->size_y - 1;
1009 con->lines = con->main_lines;
1011 for (i = 0; i < con->size_x; ++i) {
1013 con->tab_ruler[i] = true;
1015 con->tab_ruler[i] = false;
1020 void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags)
1031 con->flags |= flags;
1033 if (!(old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) {
1034 con->age = con->age_cnt;
1035 con->lines = con->alt_lines;
1038 if (!(old & TSM_SCREEN_HIDE_CURSOR) &&
1039 (flags & TSM_SCREEN_HIDE_CURSOR)) {
1040 c = get_cursor_cell(con);
1041 c->age = con->age_cnt;
1044 if (!(old & TSM_SCREEN_INVERSE) && (flags & TSM_SCREEN_INVERSE))
1045 con->age = con->age_cnt;
1049 void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags)
1060 con->flags &= ~flags;
1062 if ((old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) {
1063 con->age = con->age_cnt;
1064 con->lines = con->main_lines;
1067 if ((old & TSM_SCREEN_HIDE_CURSOR) &&
1068 (flags & TSM_SCREEN_HIDE_CURSOR)) {
1069 c = get_cursor_cell(con);
1070 c->age = con->age_cnt;
1073 if ((old & TSM_SCREEN_INVERSE) && (flags & TSM_SCREEN_INVERSE))
1074 con->age = con->age_cnt;
1078 unsigned int tsm_screen_get_flags(struct tsm_screen *con)
1087 unsigned int tsm_screen_get_cursor_x(struct tsm_screen *con)
1092 return con->cursor_x;
1096 unsigned int tsm_screen_get_cursor_y(struct tsm_screen *con)
1101 return con->cursor_y;
1105 void tsm_screen_set_tabstop(struct tsm_screen *con)
1107 if (!con || con->cursor_x >= con->size_x)
1110 con->tab_ruler[con->cursor_x] = true;
1114 void tsm_screen_reset_tabstop(struct tsm_screen *con)
1116 if (!con || con->cursor_x >= con->size_x)
1119 con->tab_ruler[con->cursor_x] = false;
1123 void tsm_screen_reset_all_tabstops(struct tsm_screen *con)
1130 for (i = 0; i < con->size_x; ++i)
1131 con->tab_ruler[i] = false;
1135 void tsm_screen_write(struct tsm_screen *con, tsm_symbol_t ch,
1136 const struct tsm_screen_attr *attr)
1138 unsigned int last, len;
1143 len = tsm_symbol_get_width(con->sym_table, ch);
1149 if (con->cursor_y <= con->margin_bottom ||
1150 con->cursor_y >= con->size_y)
1151 last = con->margin_bottom;
1153 last = con->size_y - 1;
1155 if (con->cursor_x >= con->size_x) {
1156 if (con->flags & TSM_SCREEN_AUTO_WRAP)
1157 move_cursor(con, 0, con->cursor_y + 1);
1159 move_cursor(con, con->size_x - 1, con->cursor_y);
1162 if (con->cursor_y > last) {
1163 move_cursor(con, con->cursor_x, last);
1164 screen_scroll_up(con, 1);
1167 screen_write(con, con->cursor_x, con->cursor_y, ch, len, attr);
1168 move_cursor(con, con->cursor_x + len, con->cursor_y);
1172 void tsm_screen_newline(struct tsm_screen *con)
1179 tsm_screen_move_down(con, 1, true);
1180 tsm_screen_move_line_home(con);
1184 void tsm_screen_scroll_up(struct tsm_screen *con, unsigned int num)
1191 screen_scroll_up(con, num);
1195 void tsm_screen_scroll_down(struct tsm_screen *con, unsigned int num)
1202 screen_scroll_down(con, num);
1206 void tsm_screen_move_to(struct tsm_screen *con, unsigned int x,
1216 if (con->flags & TSM_SCREEN_REL_ORIGIN)
1217 last = con->margin_bottom;
1219 last = con->size_y - 1;
1221 x = to_abs_x(con, x);
1222 if (x >= con->size_x)
1223 x = con->size_x - 1;
1225 y = to_abs_y(con, y);
1229 move_cursor(con, x, y);
1233 void tsm_screen_move_up(struct tsm_screen *con, unsigned int num,
1236 unsigned int diff, size;
1243 if (con->cursor_y >= con->margin_top)
1244 size = con->margin_top;
1248 diff = con->cursor_y - size;
1252 screen_scroll_down(con, num);
1253 move_cursor(con, con->cursor_x, size);
1255 move_cursor(con, con->cursor_x, con->cursor_y - num);
1260 void tsm_screen_move_down(struct tsm_screen *con, unsigned int num,
1263 unsigned int diff, size;
1270 if (con->cursor_y <= con->margin_bottom)
1271 size = con->margin_bottom + 1;
1275 diff = size - con->cursor_y - 1;
1279 screen_scroll_up(con, num);
1280 move_cursor(con, con->cursor_x, size - 1);
1282 move_cursor(con, con->cursor_x, con->cursor_y + num);
1287 void tsm_screen_move_left(struct tsm_screen *con, unsigned int num)
1296 if (num > con->size_x)
1300 if (x >= con->size_x)
1301 x = con->size_x - 1;
1304 move_cursor(con, 0, con->cursor_y);
1306 move_cursor(con, x - num, con->cursor_y);
1310 void tsm_screen_move_right(struct tsm_screen *con, unsigned int num)
1317 if (num > con->size_x)
1320 if (num + con->cursor_x >= con->size_x)
1321 move_cursor(con, con->size_x - 1, con->cursor_y);
1323 move_cursor(con, con->cursor_x + num, con->cursor_y);
1327 void tsm_screen_move_line_end(struct tsm_screen *con)
1334 move_cursor(con, con->size_x - 1, con->cursor_y);
1338 void tsm_screen_move_line_home(struct tsm_screen *con)
1345 move_cursor(con, 0, con->cursor_y);
1349 void tsm_screen_tab_right(struct tsm_screen *con, unsigned int num)
1351 unsigned int i, j, x;
1359 for (i = 0; i < num; ++i) {
1360 for (j = x + 1; j < con->size_x; ++j) {
1361 if (con->tab_ruler[j])
1366 if (x + 1 >= con->size_x)
1370 /* tabs never cause pending new-lines */
1371 if (x >= con->size_x)
1372 x = con->size_x - 1;
1374 move_cursor(con, x, con->cursor_y);
1378 void tsm_screen_tab_left(struct tsm_screen *con, unsigned int num)
1389 for (i = 0; i < num; ++i) {
1390 for (j = x - 1; j > 0; --j) {
1391 if (con->tab_ruler[j])
1402 move_cursor(con, x, con->cursor_y);
1406 void tsm_screen_insert_lines(struct tsm_screen *con, unsigned int num)
1408 unsigned int i, j, max;
1413 if (con->cursor_y < con->margin_top ||
1414 con->cursor_y > con->margin_bottom)
1418 /* TODO: more sophisticated ageing */
1419 con->age = con->age_cnt;
1421 max = con->margin_bottom - con->cursor_y + 1;
1425 struct line *cache[num];
1427 for (i = 0; i < num; ++i) {
1428 cache[i] = con->lines[con->margin_bottom - i];
1429 for (j = 0; j < con->size_x; ++j)
1430 cell_init(con, &cache[i]->cells[j]);
1434 memmove(&con->lines[con->cursor_y + num],
1435 &con->lines[con->cursor_y],
1436 (max - num) * sizeof(struct line*));
1438 memcpy(&con->lines[con->cursor_y],
1439 cache, num * sizeof(struct line*));
1446 void tsm_screen_delete_lines(struct tsm_screen *con, unsigned int num)
1448 unsigned int i, j, max;
1453 if (con->cursor_y < con->margin_top ||
1454 con->cursor_y > con->margin_bottom)
1458 /* TODO: more sophisticated ageing */
1459 con->age = con->age_cnt;
1461 max = con->margin_bottom - con->cursor_y + 1;
1465 struct line *cache[num];
1467 for (i = 0; i < num; ++i) {
1468 cache[i] = con->lines[con->cursor_y + i];
1469 for (j = 0; j < con->size_x; ++j)
1470 cell_init(con, &cache[i]->cells[j]);
1474 memmove(&con->lines[con->cursor_y],
1475 &con->lines[con->cursor_y + num],
1476 (max - num) * sizeof(struct line*));
1478 memcpy(&con->lines[con->cursor_y + (max - num)],
1479 cache, num * sizeof(struct line*));
1486 void tsm_screen_insert_chars(struct tsm_screen *con, unsigned int num)
1489 unsigned int max, mv, i;
1491 if (!con || !num || !con->size_y || !con->size_x)
1495 /* TODO: more sophisticated ageing */
1496 con->age = con->age_cnt;
1498 if (con->cursor_x >= con->size_x)
1499 con->cursor_x = con->size_x - 1;
1500 if (con->cursor_y >= con->size_y)
1501 con->cursor_y = con->size_y - 1;
1503 max = con->size_x - con->cursor_x;
1508 cells = con->lines[con->cursor_y]->cells;
1510 memmove(&cells[con->cursor_x + num],
1511 &cells[con->cursor_x],
1512 mv * sizeof(*cells));
1514 for (i = 0; i < num; ++i)
1515 cell_init(con, &cells[con->cursor_x + i]);
1519 void tsm_screen_delete_chars(struct tsm_screen *con, unsigned int num)
1522 unsigned int max, mv, i;
1524 if (!con || !num || !con->size_y || !con->size_x)
1528 /* TODO: more sophisticated ageing */
1529 con->age = con->age_cnt;
1531 if (con->cursor_x >= con->size_x)
1532 con->cursor_x = con->size_x - 1;
1533 if (con->cursor_y >= con->size_y)
1534 con->cursor_y = con->size_y - 1;
1536 max = con->size_x - con->cursor_x;
1541 cells = con->lines[con->cursor_y]->cells;
1543 memmove(&cells[con->cursor_x],
1544 &cells[con->cursor_x + num],
1545 mv * sizeof(*cells));
1547 for (i = 0; i < num; ++i)
1548 cell_init(con, &cells[con->cursor_x + mv + i]);
1552 void tsm_screen_erase_cursor(struct tsm_screen *con)
1561 if (con->cursor_x >= con->size_x)
1562 x = con->size_x - 1;
1566 screen_erase_region(con, x, con->cursor_y, x, con->cursor_y, false);
1570 void tsm_screen_erase_chars(struct tsm_screen *con, unsigned int num)
1579 if (con->cursor_x >= con->size_x)
1580 x = con->size_x - 1;
1584 screen_erase_region(con, x, con->cursor_y, x + num - 1, con->cursor_y,
1589 void tsm_screen_erase_cursor_to_end(struct tsm_screen *con,
1599 if (con->cursor_x >= con->size_x)
1600 x = con->size_x - 1;
1604 screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1605 con->cursor_y, protect);
1609 void tsm_screen_erase_home_to_cursor(struct tsm_screen *con,
1617 screen_erase_region(con, 0, con->cursor_y, con->cursor_x,
1618 con->cursor_y, protect);
1622 void tsm_screen_erase_current_line(struct tsm_screen *con,
1630 screen_erase_region(con, 0, con->cursor_y, con->size_x - 1,
1631 con->cursor_y, protect);
1635 void tsm_screen_erase_screen_to_cursor(struct tsm_screen *con,
1643 screen_erase_region(con, 0, 0, con->cursor_x, con->cursor_y, protect);
1647 void tsm_screen_erase_cursor_to_screen(struct tsm_screen *con,
1657 if (con->cursor_x >= con->size_x)
1658 x = con->size_x - 1;
1662 screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1663 con->size_y - 1, protect);
1667 void tsm_screen_erase_screen(struct tsm_screen *con, bool protect)
1674 screen_erase_region(con, 0, 0, con->size_x - 1, con->size_y - 1,
1680 * If a running pty-client does not support mouse-tracking extensions, a
1681 * terminal can manually mark selected areas if it does mouse-tracking itself.
1682 * This tracking is slightly different than the integrated client-tracking:
1684 * Initial state is no-selection. At any time selection_reset() can be called to
1685 * clear the selection and go back to initial state.
1686 * If the user presses a mouse-button, the terminal can calculate the selected
1687 * cell and call selection_start() to notify the terminal that the user started
1688 * the selection. While the mouse-button is held down, the terminal should call
1689 * selection_target() whenever a mouse-event occurs. This will tell the screen
1690 * layer to draw the selection from the initial start up to the last given
1692 * Please note that the selection-start cannot be modified by the terminal
1693 * during a selection. Instead, the screen-layer automatically moves it along
1694 * with any scroll-operations or inserts/deletes. This also means, the terminal
1695 * must _not_ cache the start-position itself as it may change under the hood.
1696 * This selection takes also care of scrollback-buffer selections and correctly
1697 * moves selection state along.
1699 * Please note that this is not the kind of selection that some PTY applications
1700 * support. If the client supports the mouse-protocol, then it can also control
1701 * a separate screen-selection which is always inside of the actual screen. This
1702 * is a totally different selection.
1705 static void selection_set(struct tsm_screen *con, struct selection_pos *sel,
1706 unsigned int x, unsigned int y)
1726 void tsm_screen_selection_reset(struct tsm_screen *con)
1732 /* TODO: more sophisticated ageing */
1733 con->age = con->age_cnt;
1735 con->sel_active = false;
1739 void tsm_screen_selection_start(struct tsm_screen *con,
1747 /* TODO: more sophisticated ageing */
1748 con->age = con->age_cnt;
1750 con->sel_active = true;
1751 selection_set(con, &con->sel_start, posx, posy);
1752 memcpy(&con->sel_end, &con->sel_start, sizeof(con->sel_end));
1756 void tsm_screen_selection_target(struct tsm_screen *con,
1760 if (!con || !con->sel_active)
1764 /* TODO: more sophisticated ageing */
1765 con->age = con->age_cnt;
1767 selection_set(con, &con->sel_end, posx, posy);
1770 /* TODO: tsm_ucs4_to_utf8 expects UCS4 characters, but a cell contains a
1771 * tsm-symbol (which can contain multiple UCS4 chars). Fix this when introducing
1772 * support for combining characters. */
1773 static unsigned int copy_line(struct line *line, char *buf,
1774 unsigned int start, unsigned int len)
1776 unsigned int i, end;
1780 for (i = start; i < line->size && i < end; ++i) {
1781 if (i < line->size || !line->cells[i].ch)
1782 pos += tsm_ucs4_to_utf8(line->cells[i].ch, pos);
1784 pos += tsm_ucs4_to_utf8(' ', pos);
1790 /* TODO: This beast definitely needs some "beautification", however, it's meant
1791 * as a "proof-of-concept" so its enough for now. */
1793 int tsm_screen_selection_copy(struct tsm_screen *con, char **out)
1795 unsigned int len, i;
1796 struct selection_pos *start, *end;
1803 if (!con->sel_active)
1806 /* check whether sel_start or sel_end comes first */
1807 if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP) {
1808 if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
1815 start = &con->sel_start;
1816 end = &con->sel_end;
1817 } else if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
1818 start = &con->sel_end;
1819 end = &con->sel_start;
1820 } else if (con->sel_start.line && con->sel_end.line) {
1821 if (con->sel_start.line->sb_id < con->sel_end.line->sb_id) {
1822 start = &con->sel_start;
1823 end = &con->sel_end;
1824 } else if (con->sel_start.line->sb_id > con->sel_end.line->sb_id) {
1825 start = &con->sel_end;
1826 end = &con->sel_start;
1827 } else if (con->sel_start.x < con->sel_end.x) {
1828 start = &con->sel_start;
1829 end = &con->sel_end;
1831 start = &con->sel_end;
1832 end = &con->sel_start;
1834 } else if (con->sel_start.line) {
1835 start = &con->sel_start;
1836 end = &con->sel_end;
1837 } else if (con->sel_end.line) {
1838 start = &con->sel_end;
1839 end = &con->sel_start;
1840 } else if (con->sel_start.y < con->sel_end.y) {
1841 start = &con->sel_start;
1842 end = &con->sel_end;
1843 } else if (con->sel_start.y > con->sel_end.y) {
1844 start = &con->sel_end;
1845 end = &con->sel_start;
1846 } else if (con->sel_start.x < con->sel_end.x) {
1847 start = &con->sel_start;
1848 end = &con->sel_end;
1850 start = &con->sel_end;
1851 end = &con->sel_start;
1854 /* calculate size of buffer */
1857 if (!iter && start->y == SELECTION_TOP)
1858 iter = con->sb_first;
1861 if (iter == start->line && iter == end->line) {
1862 if (iter->size > start->x) {
1863 if (iter->size > end->x)
1864 len += end->x - start->x + 1;
1866 len += iter->size - start->x;
1869 } else if (iter == start->line) {
1870 if (iter->size > start->x)
1871 len += iter->size - start->x;
1872 } else if (iter == end->line) {
1873 if (iter->size > end->x)
1887 if (start->line || start->y == SELECTION_TOP)
1891 for ( ; i < con->size_y; ++i) {
1892 if (!start->line && start->y == i && end->y == i) {
1893 if (con->size_x > start->x) {
1894 if (con->size_x > end->x)
1895 len += end->x - start->x + 1;
1897 len += con->size_x - start->x;
1900 } else if (!start->line && start->y == i) {
1901 if (con->size_x > start->x)
1902 len += con->size_x - start->x;
1903 } else if (end->y == i) {
1904 if (con->size_x > end->x)
1917 /* allocate buffer */
1925 /* copy data into buffer */
1927 if (!iter && start->y == SELECTION_TOP)
1928 iter = con->sb_first;
1931 if (iter == start->line && iter == end->line) {
1932 if (iter->size > start->x) {
1933 if (iter->size > end->x)
1934 len = end->x - start->x + 1;
1936 len = iter->size - start->x;
1937 pos += copy_line(iter, pos, start->x, len);
1940 } else if (iter == start->line) {
1941 if (iter->size > start->x)
1942 pos += copy_line(iter, pos, start->x,
1943 iter->size - start->x);
1944 } else if (iter == end->line) {
1945 if (iter->size > end->x)
1949 pos += copy_line(iter, pos, 0, len);
1952 pos += copy_line(iter, pos, 0, iter->size);
1960 if (start->line || start->y == SELECTION_TOP)
1964 for ( ; i < con->size_y; ++i) {
1965 iter = con->lines[i];
1966 if (!start->line && start->y == i && end->y == i) {
1967 if (con->size_x > start->x) {
1968 if (con->size_x > end->x)
1969 len = end->x - start->x + 1;
1971 len = con->size_x - start->x;
1972 pos += copy_line(iter, pos, start->x, len);
1975 } else if (!start->line && start->y == i) {
1976 if (con->size_x > start->x)
1977 pos += copy_line(iter, pos, start->x,
1978 con->size_x - start->x);
1979 } else if (end->y == i) {
1980 if (con->size_x > end->x)
1984 pos += copy_line(iter, pos, 0, len);
1987 pos += copy_line(iter, pos, 0, con->size_x);
2001 tsm_age_t tsm_screen_draw(struct tsm_screen *con, tsm_screen_draw_cb draw_cb,
2004 unsigned int cur_x, cur_y;
2005 unsigned int i, j, k;
2006 struct line *iter, *line = NULL;
2008 struct tsm_screen_attr attr;
2009 int ret, warned = 0;
2012 bool in_sel = false, sel_start = false, sel_end = false;
2013 bool was_sel = false;
2016 if (!con || !draw_cb)
2019 cur_x = con->cursor_x;
2020 if (con->cursor_x >= con->size_x)
2021 cur_x = con->size_x - 1;
2022 cur_y = con->cursor_y;
2023 if (con->cursor_y >= con->size_y)
2024 cur_y = con->size_y - 1;
2026 /* push each character into rendering pipeline */
2031 if (con->sel_active) {
2032 if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP)
2034 if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP)
2037 if (con->sel_start.line &&
2038 (!iter || con->sel_start.line->sb_id < iter->sb_id))
2040 if (con->sel_end.line &&
2041 (!iter || con->sel_end.line->sb_id < iter->sb_id))
2045 for (i = 0; i < con->size_y; ++i) {
2050 line = con->lines[k];
2054 if (con->sel_active) {
2055 if (con->sel_start.line == line ||
2056 (!con->sel_start.line &&
2057 con->sel_start.y == k - 1))
2061 if (con->sel_end.line == line ||
2062 (!con->sel_end.line &&
2063 con->sel_end.y == k - 1))
2071 for (j = 0; j < con->size_x; ++j) {
2072 cell = &line->cells[j];
2073 memcpy(&attr, &cell->attr, sizeof(attr));
2075 if (con->sel_active) {
2077 j == con->sel_start.x) {
2082 j == con->sel_end.x) {
2088 if (k == cur_y + 1 && j == cur_x &&
2089 !(con->flags & TSM_SCREEN_HIDE_CURSOR))
2090 attr.inverse = !attr.inverse;
2092 /* TODO: do some more sophisticated inverse here. When
2093 * INVERSE mode is set, we should instead just select
2094 * inverse colors instead of switching background and
2096 if (con->flags & TSM_SCREEN_INVERSE)
2097 attr.inverse = !attr.inverse;
2099 if (in_sel || was_sel) {
2101 attr.inverse = !attr.inverse;
2104 if (con->age_reset) {
2108 if (line->age > age)
2114 ch = tsm_symbol_get(con->sym_table, &cell->ch, &len);
2115 if (cell->ch == ' ' || cell->ch == 0)
2117 ret = draw_cb(con, cell->ch, ch, len, cell->width,
2118 j, i, &attr, age, data);
2119 if (ret && warned++ < 3) {
2121 "cannot draw glyph at %ux%u via text-renderer",
2125 "suppressing further warnings during this rendering round");
2130 if (con->age_reset) {
2134 return con->age_cnt;