2 * TSM - Screen Management
4 * Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
5 * Copyright (c) 2011 University of Tuebingen
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files
9 * (the "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
15 * The above copyright notice and this permission notice shall be included
16 * in all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 * This provides the screen drawing and manipulation functions. It does not
30 * provide the terminal emulation. It is just an abstraction layer to draw text
31 * to a framebuffer as used by terminals and consoles.
40 #include "shl_timer.h"
41 #include "tsm_screen.h"
42 #include "tsm_unicode.h"
44 #define LLOG_SUBSYSTEM "tsm_screen"
48 struct tsm_screen_attr attr;
60 #define SELECTION_TOP -1
61 struct selection_pos {
72 struct shl_timer *timer;
74 /* default attributes for new cells */
75 struct tsm_screen_attr def_attr;
80 unsigned int margin_top;
81 unsigned int margin_bottom;
82 unsigned int line_num;
85 /* scroll-back buffer */
86 unsigned int sb_count; /* number of lines in sb */
87 struct line *sb_first; /* first line; was moved first */
88 struct line *sb_last; /* last line; was moved last*/
89 unsigned int sb_max; /* max-limit of lines in sb */
90 struct line *sb_pos; /* current position in sb or NULL */
91 uint64_t sb_last_id; /* last id given to sb-line */
94 unsigned int cursor_x;
95 unsigned int cursor_y;
102 struct selection_pos sel_start;
103 struct selection_pos sel_end;
106 static void cell_init(struct tsm_screen *con, struct cell *cell)
109 memcpy(&cell->attr, &con->def_attr, sizeof(cell->attr));
112 static int line_new(struct tsm_screen *con, struct line **out,
121 line = malloc(sizeof(*line));
128 line->cells = malloc(sizeof(struct cell) * width);
134 for (i = 0; i < width; ++i)
135 cell_init(con, &line->cells[i]);
141 static void line_free(struct line *line)
147 static int line_resize(struct tsm_screen *con, struct line *line,
155 if (line->size < width) {
156 tmp = realloc(line->cells, width * sizeof(struct cell));
163 while (line->size < width) {
164 cell_init(con, &line->cells[line->size]);
172 /* This links the given line into the scrollback-buffer */
173 static void link_to_scrollback(struct tsm_screen *con, struct line *line)
177 if (con->sb_max == 0) {
178 if (con->sel_active) {
179 if (con->sel_start.line == line) {
180 con->sel_start.line = NULL;
181 con->sel_start.y = SELECTION_TOP;
183 if (con->sel_end.line == line) {
184 con->sel_end.line = NULL;
185 con->sel_end.y = SELECTION_TOP;
192 /* Remove a line from the scrollback buffer if it reaches its maximum.
193 * We must take care to correctly keep the current position as the new
194 * line is linked in after we remove the top-most line here.
195 * sb_max == 0 is tested earlier so we can assume sb_max > 0 here. In
196 * other words, buf->sb_first is a valid line if sb_count >= sb_max. */
197 if (con->sb_count >= con->sb_max) {
199 con->sb_first = tmp->next;
201 tmp->next->prev = NULL;
206 /* (position == tmp && !next) means we have sb_max=1 so set
207 * position to the new line. Otherwise, set to new first line.
208 * If position!=tmp and we have a fixed-position then nothing
209 * needs to be done because we can stay at the same line. If we
210 * have no fixed-position, we need to set the position to the
211 * next inserted line, which can be "line", too. */
213 if (con->sb_pos == tmp ||
214 !(con->flags & TSM_SCREEN_FIXED_POS)) {
215 if (con->sb_pos->next)
216 con->sb_pos = con->sb_pos->next;
222 if (con->sel_active) {
223 if (con->sel_start.line == tmp) {
224 con->sel_start.line = NULL;
225 con->sel_start.y = SELECTION_TOP;
227 if (con->sel_end.line == tmp) {
228 con->sel_end.line = NULL;
229 con->sel_end.y = SELECTION_TOP;
235 line->sb_id = ++con->sb_last_id;
237 line->prev = con->sb_last;
239 con->sb_last->next = line;
241 con->sb_first = line;
246 static void screen_scroll_up(struct tsm_screen *con, unsigned int num)
248 unsigned int i, j, max, pos;
254 max = con->margin_bottom + 1 - con->margin_top;
258 /* We cache lines on the stack to speed up the scrolling. However, if
259 * num is too big we might get overflows here so use recursion if num
260 * exceeds a hard-coded limit.
261 * 128 seems to be a sane limit that should never be reached but should
262 * also be small enough so we do not get stack overflows. */
264 screen_scroll_up(con, 128);
265 return screen_scroll_up(con, num - 128);
267 struct line *cache[num];
269 for (i = 0; i < num; ++i) {
270 pos = con->margin_top + i;
271 ret = line_new(con, &cache[i], con->size_x);
273 link_to_scrollback(con, con->lines[pos]);
275 cache[i] = con->lines[pos];
276 for (j = 0; j < con->size_x; ++j)
277 cell_init(con, &cache[i]->cells[j]);
282 memmove(&con->lines[con->margin_top],
283 &con->lines[con->margin_top + num],
284 (max - num) * sizeof(struct line*));
287 memcpy(&con->lines[con->margin_top + (max - num)],
288 cache, num * sizeof(struct line*));
290 if (con->sel_active) {
291 if (!con->sel_start.line && con->sel_start.y >= 0) {
292 con->sel_start.y -= num;
293 if (con->sel_start.y < 0) {
294 con->sel_start.line = con->sb_last;
295 while (con->sel_start.line && ++con->sel_start.y < 0)
296 con->sel_start.line = con->sel_start.line->prev;
297 con->sel_start.y = SELECTION_TOP;
300 if (!con->sel_end.line && con->sel_end.y >= 0) {
301 con->sel_end.y -= num;
302 if (con->sel_end.y < 0) {
303 con->sel_end.line = con->sb_last;
304 while (con->sel_end.line && ++con->sel_end.y < 0)
305 con->sel_end.line = con->sel_end.line->prev;
306 con->sel_end.y = SELECTION_TOP;
312 static void screen_scroll_down(struct tsm_screen *con, unsigned int num)
314 unsigned int i, j, max;
319 max = con->margin_bottom + 1 - con->margin_top;
323 /* see screen_scroll_up() for an explanation */
325 screen_scroll_down(con, 128);
326 return screen_scroll_down(con, num - 128);
328 struct line *cache[num];
330 for (i = 0; i < num; ++i) {
331 cache[i] = con->lines[con->margin_bottom - i];
332 for (j = 0; j < con->size_x; ++j)
333 cell_init(con, &cache[i]->cells[j]);
337 memmove(&con->lines[con->margin_top + num],
338 &con->lines[con->margin_top],
339 (max - num) * sizeof(struct line*));
342 memcpy(&con->lines[con->margin_top],
343 cache, num * sizeof(struct line*));
345 if (con->sel_active) {
346 if (!con->sel_start.line && con->sel_start.y >= 0)
347 con->sel_start.y += num;
348 if (!con->sel_end.line && con->sel_end.y >= 0)
349 con->sel_end.y += num;
353 static void screen_write(struct tsm_screen *con, unsigned int x,
354 unsigned int y, tsm_symbol_t ch,
355 const struct tsm_screen_attr *attr)
359 if (x >= con->size_x || y >= con->size_y) {
360 llog_warn(con, "writing beyond buffer boundary");
364 line = con->lines[y];
366 if ((con->flags & TSM_SCREEN_INSERT_MODE) && x < (con->size_x - 1))
367 memmove(&line->cells[x + 1], &line->cells[x],
368 sizeof(struct cell) * (con->size_x - 1 - x));
369 line->cells[x].ch = ch;
370 memcpy(&line->cells[x].attr, attr, sizeof(*attr));
373 static void screen_erase_region(struct tsm_screen *con,
383 if (y_to >= con->size_y)
384 y_to = con->size_y - 1;
385 if (x_to >= con->size_x)
386 x_to = con->size_x - 1;
388 for ( ; y_from <= y_to; ++y_from) {
389 line = con->lines[y_from];
398 to = con->size_x - 1;
399 for ( ; x_from <= to; ++x_from) {
400 if (protect && line->cells[x_from].attr.protect)
403 cell_init(con, &line->cells[x_from]);
409 static inline unsigned int to_abs_x(struct tsm_screen *con, unsigned int x)
414 static inline unsigned int to_abs_y(struct tsm_screen *con, unsigned int y)
416 if (!(con->flags & TSM_SCREEN_REL_ORIGIN))
419 return con->margin_top + y;
422 int tsm_screen_new(struct tsm_screen **out, tsm_log_t log)
424 struct tsm_screen *con;
431 con = malloc(sizeof(*con));
435 memset(con, 0, sizeof(*con));
438 con->def_attr.fr = 255;
439 con->def_attr.fg = 255;
440 con->def_attr.fb = 255;
442 ret = shl_timer_new(&con->timer);
446 ret = tsm_screen_resize(con, 80, 24);
450 llog_debug(con, "new screen");
456 shl_timer_free(con->timer);
457 for (i = 0; i < con->line_num; ++i)
458 line_free(con->lines[i]);
460 free(con->tab_ruler);
466 void tsm_screen_ref(struct tsm_screen *con)
474 void tsm_screen_unref(struct tsm_screen *con)
478 if (!con || !con->ref || --con->ref)
481 llog_debug(con, "destroying screen");
483 for (i = 0; i < con->line_num; ++i)
484 line_free(con->lines[i]);
486 free(con->tab_ruler);
487 shl_timer_free(con->timer);
491 void tsm_screen_set_opts(struct tsm_screen *scr, unsigned int opts)
499 void tsm_screen_reset_opts(struct tsm_screen *scr, unsigned int opts)
507 unsigned int tsm_screen_get_opts(struct tsm_screen *scr)
515 unsigned int tsm_screen_get_width(struct tsm_screen *con)
523 unsigned int tsm_screen_get_height(struct tsm_screen *con)
531 int tsm_screen_resize(struct tsm_screen *con, unsigned int x,
535 unsigned int i, j, width, diff;
539 if (!con || !x || !y)
542 if (con->size_x == x && con->size_y == y)
545 /* First make sure the line buffer is big enough for our new screen.
546 * That is, allocate all new lines and make sure each line has enough
547 * cells to hold the new screen or the current screen. If we fail, we
548 * can safely return -ENOMEM and the buffer is still valid. We must
549 * allocate the new lines to at least the same size as the current
550 * lines. Otherwise, if this function fails in later turns, we will have
551 * invalid lines in the buffer. */
552 if (y > con->line_num) {
553 cache = realloc(con->lines, sizeof(struct line*) * y);
563 while (con->line_num < y) {
564 ret = line_new(con, &cache[con->line_num], width);
571 /* Resize all lines in the buffer if we increase screen width. This
572 * will guarantee that all lines are big enough so we can resize the
573 * buffer without reallocating them later. */
574 if (x > con->size_x) {
575 tab_ruler = realloc(con->tab_ruler, sizeof(bool) * x);
578 con->tab_ruler = tab_ruler;
580 for (i = 0; i < con->line_num; ++i) {
581 ret = line_resize(con, con->lines[i], x);
587 /* When resizing, we need to reset all the new cells, otherwise, the old
588 * data that was written there will reoccur on the screen.
589 * TODO: we overwrite way to much here; most of it should already be
590 * cleaned. Maybe it does more sense cleaning when _allocating_ or when
591 * _shrinking_, then we never clean twice (for performance reasons). */
592 for (j = 0; j < con->line_num; ++j) {
593 if (j >= con->size_y)
597 if (x < con->lines[j]->size)
600 width = con->lines[j]->size;
601 for (; i < width; ++i)
602 cell_init(con, &con->lines[j]->cells[i]);
605 /* xterm destroys margins on resize, so do we */
607 con->margin_bottom = con->size_y - 1;
610 for (i = 0; i < x; ++i) {
612 con->tab_ruler[i] = true;
614 con->tab_ruler[i] = false;
617 /* We need to adjust x-size first as screen_scroll_up() and friends may
618 * have to reallocate lines. The y-size is adjusted after them to avoid
619 * missing lines when shrinking y-size.
620 * We need to carefully look for the functions that we call here as they
621 * have stronger invariants as when called normally. */
624 if (con->cursor_x >= con->size_x)
625 con->cursor_x = con->size_x - 1;
627 /* scroll buffer if screen height shrinks */
628 if (con->size_y != 0 && y < con->size_y) {
629 diff = con->size_y - y;
630 screen_scroll_up(con, diff);
631 if (con->cursor_y > diff)
632 con->cursor_y -= diff;
638 con->margin_bottom = con->size_y - 1;
639 if (con->cursor_y >= con->size_y)
640 con->cursor_y = con->size_y - 1;
645 int tsm_screen_set_margins(struct tsm_screen *con,
646 unsigned int top, unsigned int bottom)
648 unsigned int upper, lower;
658 lower = con->size_y - 1;
659 } else if (bottom > con->size_y) {
661 lower = con->size_y - 1;
667 con->margin_top = upper;
668 con->margin_bottom = lower;
672 /* set maximum scrollback buffer size */
673 void tsm_screen_set_max_sb(struct tsm_screen *con,
681 while (con->sb_count > max) {
682 line = con->sb_first;
683 con->sb_first = line->next;
685 line->next->prev = NULL;
690 /* We treat fixed/unfixed position the same here because we
691 * remove lines from the TOP of the scrollback buffer. */
692 if (con->sb_pos == line)
693 con->sb_pos = con->sb_first;
695 if (con->sel_active) {
696 if (con->sel_start.line == line) {
697 con->sel_start.line = NULL;
698 con->sel_start.y = SELECTION_TOP;
700 if (con->sel_end.line == line) {
701 con->sel_end.line = NULL;
702 con->sel_end.y = SELECTION_TOP;
711 /* clear scrollback buffer */
712 void tsm_screen_clear_sb(struct tsm_screen *con)
714 struct line *iter, *tmp;
719 for (iter = con->sb_first; iter; ) {
725 con->sb_first = NULL;
730 if (con->sel_active) {
731 if (con->sel_start.line) {
732 con->sel_start.line = NULL;
733 con->sel_start.y = SELECTION_TOP;
735 if (con->sel_end.line) {
736 con->sel_end.line = NULL;
737 con->sel_end.y = SELECTION_TOP;
742 void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num)
749 if (!con->sb_pos->prev)
752 con->sb_pos = con->sb_pos->prev;
753 } else if (!con->sb_last) {
756 con->sb_pos = con->sb_last;
761 void tsm_screen_sb_down(struct tsm_screen *con, unsigned int num)
768 con->sb_pos = con->sb_pos->next;
777 void tsm_screen_sb_page_up(struct tsm_screen *con, unsigned int num)
782 tsm_screen_sb_up(con, num * con->size_y);
785 void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num)
790 tsm_screen_sb_down(con, num * con->size_y);
793 void tsm_screen_sb_reset(struct tsm_screen *con)
801 void tsm_screen_set_def_attr(struct tsm_screen *con,
802 const struct tsm_screen_attr *attr)
807 memcpy(&con->def_attr, attr, sizeof(*attr));
810 void tsm_screen_reset(struct tsm_screen *con)
819 con->margin_bottom = con->size_y - 1;
821 for (i = 0; i < con->size_x; ++i) {
823 con->tab_ruler[i] = true;
825 con->tab_ruler[i] = false;
829 void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags)
837 void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags)
842 con->flags &= ~flags;
845 unsigned int tsm_screen_get_flags(struct tsm_screen *con)
853 unsigned int tsm_screen_get_cursor_x(struct tsm_screen *con)
858 return con->cursor_x;
861 unsigned int tsm_screen_get_cursor_y(struct tsm_screen *con)
866 return con->cursor_y;
869 void tsm_screen_set_tabstop(struct tsm_screen *con)
871 if (!con || con->cursor_x >= con->size_x)
874 con->tab_ruler[con->cursor_x] = true;
877 void tsm_screen_reset_tabstop(struct tsm_screen *con)
879 if (!con || con->cursor_x >= con->size_x)
882 con->tab_ruler[con->cursor_x] = false;
885 void tsm_screen_reset_all_tabstops(struct tsm_screen *con)
892 for (i = 0; i < con->size_x; ++i)
893 con->tab_ruler[i] = false;
896 void tsm_screen_write(struct tsm_screen *con, tsm_symbol_t ch,
897 const struct tsm_screen_attr *attr)
904 if (con->cursor_y <= con->margin_bottom ||
905 con->cursor_y >= con->size_y)
906 last = con->margin_bottom;
908 last = con->size_y - 1;
910 if (con->cursor_x >= con->size_x) {
911 if (con->flags & TSM_SCREEN_AUTO_WRAP) {
915 con->cursor_x = con->size_x - 1;
919 if (con->cursor_y > last) {
920 con->cursor_y = last;
921 screen_scroll_up(con, 1);
924 screen_write(con, con->cursor_x, con->cursor_y, ch, attr);
928 void tsm_screen_newline(struct tsm_screen *con)
933 tsm_screen_move_down(con, 1, true);
934 tsm_screen_move_line_home(con);
937 void tsm_screen_scroll_up(struct tsm_screen *con, unsigned int num)
942 screen_scroll_up(con, num);
945 void tsm_screen_scroll_down(struct tsm_screen *con, unsigned int num)
950 screen_scroll_down(con, num);
953 void tsm_screen_move_to(struct tsm_screen *con, unsigned int x,
961 if (con->flags & TSM_SCREEN_REL_ORIGIN)
962 last = con->margin_bottom;
964 last = con->size_y - 1;
966 con->cursor_x = to_abs_x(con, x);
967 if (con->cursor_x >= con->size_x)
968 con->cursor_x = con->size_x - 1;
970 con->cursor_y = to_abs_y(con, y);
971 if (con->cursor_y > last)
972 con->cursor_y = last;
975 void tsm_screen_move_up(struct tsm_screen *con, unsigned int num,
978 unsigned int diff, size;
983 if (con->cursor_y >= con->margin_top)
984 size = con->margin_top;
988 diff = con->cursor_y - size;
992 screen_scroll_down(con, num);
993 con->cursor_y = size;
995 con->cursor_y -= num;
999 void tsm_screen_move_down(struct tsm_screen *con, unsigned int num,
1002 unsigned int diff, size;
1007 if (con->cursor_y <= con->margin_bottom)
1008 size = con->margin_bottom + 1;
1012 diff = size - con->cursor_y - 1;
1016 screen_scroll_up(con, num);
1017 con->cursor_y = size - 1;
1019 con->cursor_y += num;
1023 void tsm_screen_move_left(struct tsm_screen *con, unsigned int num)
1028 if (num > con->size_x)
1031 if (con->cursor_x >= con->size_x)
1032 con->cursor_x = con->size_x - 1;
1034 if (num > con->cursor_x)
1037 con->cursor_x -= num;
1040 void tsm_screen_move_right(struct tsm_screen *con, unsigned int num)
1045 if (num > con->size_x)
1048 if (num + con->cursor_x >= con->size_x)
1049 con->cursor_x = con->size_x - 1;
1051 con->cursor_x += num;
1054 void tsm_screen_move_line_end(struct tsm_screen *con)
1059 con->cursor_x = con->size_x - 1;
1062 void tsm_screen_move_line_home(struct tsm_screen *con)
1070 void tsm_screen_tab_right(struct tsm_screen *con, unsigned int num)
1077 for (i = 0; i < num; ++i) {
1078 for (j = con->cursor_x + 1; j < con->size_x; ++j) {
1079 if (con->tab_ruler[j])
1084 if (con->cursor_x + 1 >= con->size_x)
1088 /* tabs never cause pending new-lines */
1089 if (con->cursor_x >= con->size_x)
1090 con->cursor_x = con->size_x - 1;
1093 void tsm_screen_tab_left(struct tsm_screen *con, unsigned int num)
1101 for (i = 0; i < num; ++i) {
1102 for (j = con->cursor_x - 1; j > 0; --j) {
1103 if (con->tab_ruler[j])
1115 void tsm_screen_insert_lines(struct tsm_screen *con, unsigned int num)
1117 unsigned int i, j, max;
1122 if (con->cursor_y < con->margin_top ||
1123 con->cursor_y > con->margin_bottom)
1126 max = con->margin_bottom - con->cursor_y + 1;
1130 struct line *cache[num];
1132 for (i = 0; i < num; ++i) {
1133 cache[i] = con->lines[con->margin_bottom - i];
1134 for (j = 0; j < con->size_x; ++j)
1135 cell_init(con, &cache[i]->cells[j]);
1139 memmove(&con->lines[con->cursor_y + num],
1140 &con->lines[con->cursor_y],
1141 (max - num) * sizeof(struct line*));
1143 memcpy(&con->lines[con->cursor_y],
1144 cache, num * sizeof(struct line*));
1150 void tsm_screen_delete_lines(struct tsm_screen *con, unsigned int num)
1152 unsigned int i, j, max;
1157 if (con->cursor_y < con->margin_top ||
1158 con->cursor_y > con->margin_bottom)
1161 max = con->margin_bottom - con->cursor_y + 1;
1165 struct line *cache[num];
1167 for (i = 0; i < num; ++i) {
1168 cache[i] = con->lines[con->cursor_y + i];
1169 for (j = 0; j < con->size_x; ++j)
1170 cell_init(con, &cache[i]->cells[j]);
1174 memmove(&con->lines[con->cursor_y],
1175 &con->lines[con->cursor_y + num],
1176 (max - num) * sizeof(struct line*));
1178 memcpy(&con->lines[con->cursor_y + (max - num)],
1179 cache, num * sizeof(struct line*));
1185 void tsm_screen_insert_chars(struct tsm_screen *con, unsigned int num)
1188 unsigned int max, mv, i;
1190 if (!con || !num || !con->size_y || !con->size_x)
1193 if (con->cursor_x >= con->size_x)
1194 con->cursor_x = con->size_x - 1;
1195 if (con->cursor_y >= con->size_y)
1196 con->cursor_y = con->size_y - 1;
1198 max = con->size_x - con->cursor_x;
1203 cells = con->lines[con->cursor_y]->cells;
1205 memmove(&cells[con->cursor_x + num],
1206 &cells[con->cursor_x],
1207 mv * sizeof(*cells));
1209 for (i = 0; i < num; ++i) {
1210 cell_init(con, &cells[con->cursor_x + i]);
1214 void tsm_screen_delete_chars(struct tsm_screen *con, unsigned int num)
1217 unsigned int max, mv, i;
1219 if (!con || !num || !con->size_y || !con->size_x)
1222 if (con->cursor_x >= con->size_x)
1223 con->cursor_x = con->size_x - 1;
1224 if (con->cursor_y >= con->size_y)
1225 con->cursor_y = con->size_y - 1;
1227 max = con->size_x - con->cursor_x;
1232 cells = con->lines[con->cursor_y]->cells;
1234 memmove(&cells[con->cursor_x],
1235 &cells[con->cursor_x + num],
1236 mv * sizeof(*cells));
1238 for (i = 0; i < num; ++i) {
1239 cell_init(con, &cells[con->cursor_x + mv + i]);
1243 void tsm_screen_erase_cursor(struct tsm_screen *con)
1250 if (con->cursor_x >= con->size_x)
1251 x = con->size_x - 1;
1255 screen_erase_region(con, x, con->cursor_y, x, con->cursor_y, false);
1258 void tsm_screen_erase_chars(struct tsm_screen *con, unsigned int num)
1265 if (con->cursor_x >= con->size_x)
1266 x = con->size_x - 1;
1270 screen_erase_region(con, x, con->cursor_y, x + num - 1, con->cursor_y,
1274 void tsm_screen_erase_cursor_to_end(struct tsm_screen *con,
1282 if (con->cursor_x >= con->size_x)
1283 x = con->size_x - 1;
1287 screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1288 con->cursor_y, protect);
1291 void tsm_screen_erase_home_to_cursor(struct tsm_screen *con,
1297 screen_erase_region(con, 0, con->cursor_y, con->cursor_x,
1298 con->cursor_y, protect);
1301 void tsm_screen_erase_current_line(struct tsm_screen *con,
1307 screen_erase_region(con, 0, con->cursor_y, con->size_x - 1,
1308 con->cursor_y, protect);
1311 void tsm_screen_erase_screen_to_cursor(struct tsm_screen *con,
1317 screen_erase_region(con, 0, 0, con->cursor_x, con->cursor_y, protect);
1320 void tsm_screen_erase_cursor_to_screen(struct tsm_screen *con,
1328 if (con->cursor_x >= con->size_x)
1329 x = con->size_x - 1;
1333 screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1334 con->size_y - 1, protect);
1337 void tsm_screen_erase_screen(struct tsm_screen *con, bool protect)
1342 screen_erase_region(con, 0, 0, con->size_x - 1, con->size_y - 1,
1348 * If a running pty-client does not support mouse-tracking extensions, a
1349 * terminal can manually mark selected areas if it does mouse-tracking itself.
1350 * This tracking is slightly different than the integrated client-tracking:
1352 * Initial state is no-selection. At any time selection_reset() can be called to
1353 * clear the selection and go back to initial state.
1354 * If the user presses a mouse-button, the terminal can calculate the selected
1355 * cell and call selection_start() to notify the terminal that the user started
1356 * the selection. While the mouse-button is held down, the terminal should call
1357 * selection_target() whenever a mouse-event occurs. This will tell the screen
1358 * layer to draw the selection from the initial start up to the last given
1360 * Please note that the selection-start cannot be modified by the terminal
1361 * during a selection. Instead, the screen-layer automatically moves it along
1362 * with any scroll-operations or inserts/deletes. This also means, the terminal
1363 * must _not_ cache the start-position itself as it may change under the hood.
1364 * This selection takes also care of scrollback-buffer selections and correctly
1365 * moves selection state along.
1367 * Please note that this is not the kind of selection that some PTY applications
1368 * support. If the client supports the mouse-protocol, then it can also control
1369 * a separate screen-selection which is always inside of the actual screen. This
1370 * is a totally different selection.
1373 static void selection_set(struct tsm_screen *con, struct selection_pos *sel,
1374 unsigned int x, unsigned int y)
1393 void tsm_screen_selection_reset(struct tsm_screen *con)
1398 con->sel_active = false;
1401 void tsm_screen_selection_start(struct tsm_screen *con,
1408 con->sel_active = true;
1409 selection_set(con, &con->sel_start, posx, posy);
1410 memcpy(&con->sel_end, &con->sel_start, sizeof(con->sel_end));
1413 void tsm_screen_selection_target(struct tsm_screen *con,
1417 if (!con || !con->sel_active)
1420 selection_set(con, &con->sel_end, posx, posy);
1423 /* TODO: tsm_ucs4_to_utf8 expects UCS4 characters, but a cell contains a
1424 * tsm-symbol (which can contain multiple UCS4 chars). Fix this when introducing
1425 * support for combining characters. */
1426 static unsigned int copy_line(struct line *line, char *buf,
1427 unsigned int start, unsigned int len)
1429 unsigned int i, end;
1433 for (i = start; i < line->size && i < end; ++i) {
1434 if (i < line->size || !line->cells[i].ch)
1435 pos += tsm_ucs4_to_utf8(line->cells[i].ch, pos);
1437 pos += tsm_ucs4_to_utf8(' ', pos);
1443 /* TODO: This beast definitely needs some "beautification", however, it's meant
1444 * as a "proof-of-concept" so its enough for now. */
1445 int tsm_screen_selection_copy(struct tsm_screen *con, char **out)
1447 unsigned int len, i;
1448 struct selection_pos *start, *end;
1455 if (!con->sel_active)
1458 /* check whether sel_start or sel_end comes first */
1459 if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP) {
1460 if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
1467 start = &con->sel_start;
1468 end = &con->sel_end;
1469 } else if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
1470 start = &con->sel_end;
1471 end = &con->sel_start;
1472 } else if (con->sel_start.line && con->sel_end.line) {
1473 if (con->sel_start.line->sb_id < con->sel_end.line->sb_id) {
1474 start = &con->sel_start;
1475 end = &con->sel_end;
1476 } else if (con->sel_start.line->sb_id > con->sel_end.line->sb_id) {
1477 start = &con->sel_end;
1478 end = &con->sel_start;
1479 } else if (con->sel_start.x < con->sel_end.x) {
1480 start = &con->sel_start;
1481 end = &con->sel_end;
1483 start = &con->sel_end;
1484 end = &con->sel_start;
1486 } else if (con->sel_start.line) {
1487 start = &con->sel_start;
1488 end = &con->sel_end;
1489 } else if (con->sel_end.line) {
1490 start = &con->sel_end;
1491 end = &con->sel_start;
1492 } else if (con->sel_start.y < con->sel_end.y) {
1493 start = &con->sel_start;
1494 end = &con->sel_end;
1495 } else if (con->sel_start.y > con->sel_end.y) {
1496 start = &con->sel_end;
1497 end = &con->sel_start;
1498 } else if (con->sel_start.x < con->sel_end.x) {
1499 start = &con->sel_start;
1500 end = &con->sel_end;
1502 start = &con->sel_end;
1503 end = &con->sel_start;
1506 /* calculate size of buffer */
1509 if (!iter && start->y == SELECTION_TOP)
1510 iter = con->sb_first;
1513 if (iter == start->line && iter == end->line) {
1514 if (iter->size > start->x) {
1515 if (iter->size > end->x)
1516 len += end->x - start->x + 1;
1518 len += iter->size - start->x;
1521 } else if (iter == start->line) {
1522 if (iter->size > start->x)
1523 len += iter->size - start->x;
1524 } else if (iter == end->line) {
1525 if (iter->size > end->x)
1539 if (start->line || start->y == SELECTION_TOP)
1543 for ( ; i < con->size_y; ++i) {
1544 if (!start->line && start->y == i && end->y == i) {
1545 if (con->size_x > start->x) {
1546 if (con->size_x > end->x)
1547 len += end->x - start->x + 1;
1549 len += con->size_x - start->x;
1552 } else if (!start->line && start->y == i) {
1553 if (con->size_x > start->x)
1554 len += con->size_x - start->x;
1555 } else if (end->y == i) {
1556 if (con->size_x > end->x)
1569 /* allocate buffer */
1577 /* copy data into buffer */
1579 if (!iter && start->y == SELECTION_TOP)
1580 iter = con->sb_first;
1583 if (iter == start->line && iter == end->line) {
1584 if (iter->size > start->x) {
1585 if (iter->size > end->x)
1586 len = end->x - start->x + 1;
1588 len = iter->size - start->x;
1589 pos += copy_line(iter, pos, start->x, len);
1592 } else if (iter == start->line) {
1593 if (iter->size > start->x)
1594 pos += copy_line(iter, pos, start->x,
1595 iter->size - start->x);
1596 } else if (iter == end->line) {
1597 if (iter->size > end->x)
1601 pos += copy_line(iter, pos, 0, len);
1604 pos += copy_line(iter, pos, 0, iter->size);
1612 if (start->line || start->y == SELECTION_TOP)
1616 for ( ; i < con->size_y; ++i) {
1617 iter = con->lines[i];
1618 if (!start->line && start->y == i && end->y == i) {
1619 if (con->size_x > start->x) {
1620 if (con->size_x > end->x)
1621 len = end->x - start->x + 1;
1623 len = con->size_x - start->x;
1624 pos += copy_line(iter, pos, start->x, len);
1627 } else if (!start->line && start->y == i) {
1628 if (con->size_x > start->x)
1629 pos += copy_line(iter, pos, start->x,
1630 con->size_x - start->x);
1631 } else if (end->y == i) {
1632 if (con->size_x > end->x)
1636 pos += copy_line(iter, pos, 0, len);
1639 pos += copy_line(iter, pos, 0, con->size_x);
1652 void tsm_screen_draw(struct tsm_screen *con,
1653 tsm_screen_prepare_cb prepare_cb,
1654 tsm_screen_draw_cb draw_cb,
1655 tsm_screen_render_cb render_cb,
1658 unsigned int cur_x, cur_y;
1659 unsigned int i, j, k;
1660 struct line *iter, *line = NULL;
1662 struct tsm_screen_attr attr;
1663 bool cursor_done = false;
1664 int ret, warned = 0;
1665 uint64_t time_prep = 0, time_draw = 0, time_rend = 0;
1669 bool in_sel = false, sel_start = false, sel_end = false;
1670 bool was_sel = false;
1672 if (!con || !draw_cb)
1675 cell_init(con, &empty);
1677 cur_x = con->cursor_x;
1678 if (con->cursor_x >= con->size_x)
1679 cur_x = con->size_x - 1;
1680 cur_y = con->cursor_y;
1681 if (con->cursor_y >= con->size_y)
1682 cur_y = con->size_y - 1;
1684 /* render preparation */
1687 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1688 shl_timer_reset(con->timer);
1690 ret = prepare_cb(con, data);
1693 "cannot prepare text-renderer for rendering");
1697 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1698 time_prep = shl_timer_elapsed(con->timer);
1703 /* push each character into rendering pipeline */
1705 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1706 shl_timer_reset(con->timer);
1711 if (con->sel_active) {
1712 if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP)
1714 if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP)
1717 if (con->sel_start.line &&
1718 (!iter || con->sel_start.line->sb_id < iter->sb_id))
1720 if (con->sel_end.line &&
1721 (!iter || con->sel_end.line->sb_id < iter->sb_id))
1725 for (i = 0; i < con->size_y; ++i) {
1730 line = con->lines[k];
1734 if (con->sel_active) {
1735 if (con->sel_start.line == line ||
1736 (!con->sel_start.line &&
1737 con->sel_start.y == k - 1))
1741 if (con->sel_end.line == line ||
1742 (!con->sel_end.line &&
1743 con->sel_end.y == k - 1))
1751 for (j = 0; j < con->size_x; ++j) {
1753 cell = &line->cells[j];
1756 memcpy(&attr, &cell->attr, sizeof(attr));
1758 if (con->sel_active) {
1760 j == con->sel_start.x) {
1765 j == con->sel_end.x) {
1771 if (k == cur_y + 1 &&
1774 if (!(con->flags & TSM_SCREEN_HIDE_CURSOR))
1775 attr.inverse = !attr.inverse;
1778 /* TODO: do some more sophisticated inverse here. When
1779 * INVERSE mode is set, we should instead just select
1780 * inverse colors instead of switching background and
1782 if (con->flags & TSM_SCREEN_INVERSE)
1783 attr.inverse = !attr.inverse;
1785 if (in_sel || was_sel) {
1787 attr.inverse = !attr.inverse;
1790 ch = tsm_symbol_get(NULL, &cell->ch, &len);
1791 if (cell->ch == ' ' || cell->ch == 0)
1793 ret = draw_cb(con, cell->ch, ch, len, j, i, &attr,
1795 if (ret && warned++ < 3) {
1797 "cannot draw glyph at %ux%u via text-renderer",
1801 "suppressing further warnings during this rendering round");
1805 if (k == cur_y + 1 && !cursor_done) {
1807 if (!(con->flags & TSM_SCREEN_HIDE_CURSOR)) {
1808 if (!(con->flags & TSM_SCREEN_INVERSE))
1809 attr.inverse = !attr.inverse;
1810 draw_cb(con, 0, NULL, 0, cur_x, i, &attr, data);
1815 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1816 time_draw = shl_timer_elapsed(con->timer);
1818 /* perform final rendering steps */
1821 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1822 shl_timer_reset(con->timer);
1824 ret = render_cb(con, data);
1827 "cannot render via text-renderer");
1829 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1830 time_rend = shl_timer_elapsed(con->timer);
1835 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1837 "timing: sum: %" PRIu64 " prepare: %" PRIu64 " draw: %" PRIu64 " render: %" PRIu64,
1838 time_prep + time_draw + time_rend,
1839 time_prep, time_draw, time_rend);