1 /*
2  * libtsm - Screen Management
3  *
4  * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
5  *
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:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
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.
24  */
25 
26 /*
27  * Screen Management
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.
34  *
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.
39  *
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
44  * they need.
45  *
46  * AGEING:
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.
57  */
58 
59 #include <errno.h>
60 #include <inttypes.h>
61 #include <stdbool.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include "libtsm.h"
65 #include "libtsm_int.h"
66 #include "shl_llog.h"
67 
68 #define LLOG_SUBSYSTEM "tsm_screen"
69 
70 struct cell {
71 	tsm_symbol_t ch;
72 	unsigned int width;
73 	struct tsm_screen_attr attr;
74 	tsm_age_t age;
75 };
76 
77 struct line {
78 	struct line *next;
79 	struct line *prev;
80 
81 	unsigned int size;
82 	struct cell *cells;
83 	uint64_t sb_id;
84 	tsm_age_t age;
85 };
86 
87 #define SELECTION_TOP -1
88 struct selection_pos {
89 	struct line *line;
90 	unsigned int x;
91 	int y;
92 };
93 
94 struct tsm_screen {
95 	size_t ref;
96 	llog_submit_t llog;
97 	void *llog_data;
98 	unsigned int opts;
99 	unsigned int flags;
100 	struct tsm_symbol_table *sym_table;
101 
102 	/* default attributes for new cells */
103 	struct tsm_screen_attr def_attr;
104 
105 	/* ageing */
106 	tsm_age_t age_cnt;
107 	unsigned int age_reset : 1;
108 
109 	/* current buffer */
110 	unsigned int size_x;
111 	unsigned int size_y;
112 	unsigned int margin_top;
113 	unsigned int margin_bottom;
114 	unsigned int line_num;
115 	struct line **lines;
116 	struct line **main_lines;
117 	struct line **alt_lines;
118 	tsm_age_t age;
119 
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 */
127 
128 	/* cursor */
129 	unsigned int cursor_x;
130 	unsigned int cursor_y;
131 
132 	/* tab ruler */
133 	bool *tab_ruler;
134 
135 	/* selection */
136 	bool sel_active;
137 	struct selection_pos sel_start;
138 	struct selection_pos sel_end;
139 };
140 
inc_age(struct tsm_screen * con)141 static void inc_age(struct tsm_screen *con)
142 {
143 	if (!++con->age_cnt) {
144 		con->age_reset = 1;
145 		++con->age_cnt;
146 	}
147 }
148 
get_cursor_cell(struct tsm_screen * con)149 static struct cell *get_cursor_cell(struct tsm_screen *con)
150 {
151 	unsigned int cur_x, cur_y;
152 
153 	cur_x = con->cursor_x;
154 	if (cur_x >= con->size_x)
155 		cur_x = con->size_x - 1;
156 
157 	cur_y = con->cursor_y;
158 	if (cur_y >= con->size_y)
159 		cur_y = con->size_y - 1;
160 
161 	return &con->lines[cur_y]->cells[cur_x];
162 }
163 
move_cursor(struct tsm_screen * con,unsigned int x,unsigned int y)164 static void move_cursor(struct tsm_screen *con, unsigned int x, unsigned int y)
165 {
166 	struct cell *c;
167 
168 	/* if cursor is hidden, just move it */
169 	if (con->flags & TSM_SCREEN_HIDE_CURSOR) {
170 		con->cursor_x = x;
171 		con->cursor_y = y;
172 		return;
173 	}
174 
175 	/* If cursor is visible, we have to mark the current and the new cell
176 	 * as changed by resetting their age. We skip it if the cursor-position
177 	 * didn't actually change. */
178 
179 	if (con->cursor_x == x && con->cursor_y == y)
180 		return;
181 
182 	c = get_cursor_cell(con);
183 	c->age = con->age_cnt;
184 
185 	con->cursor_x = x;
186 	con->cursor_y = y;
187 
188 	c = get_cursor_cell(con);
189 	c->age = con->age_cnt;
190 }
191 
cell_init(struct tsm_screen * con,struct cell * cell)192 static void cell_init(struct tsm_screen *con, struct cell *cell)
193 {
194 	cell->ch = 0;
195 	cell->width = 1;
196 	cell->age = con->age_cnt;
197 	memcpy(&cell->attr, &con->def_attr, sizeof(cell->attr));
198 }
199 
line_new(struct tsm_screen * con,struct line ** out,unsigned int width)200 static int line_new(struct tsm_screen *con, struct line **out,
201 		    unsigned int width)
202 {
203 	struct line *line;
204 	unsigned int i;
205 
206 	if (!width)
207 		return -EINVAL;
208 
209 	line = malloc(sizeof(*line));
210 	if (!line)
211 		return -ENOMEM;
212 	line->next = NULL;
213 	line->prev = NULL;
214 	line->size = width;
215 	line->age = con->age_cnt;
216 
217 	line->cells = malloc(sizeof(struct cell) * width);
218 	if (!line->cells) {
219 		free(line);
220 		return -ENOMEM;
221 	}
222 
223 	for (i = 0; i < width; ++i)
224 		cell_init(con, &line->cells[i]);
225 
226 	*out = line;
227 	return 0;
228 }
229 
line_free(struct line * line)230 static void line_free(struct line *line)
231 {
232 	free(line->cells);
233 	free(line);
234 }
235 
line_resize(struct tsm_screen * con,struct line * line,unsigned int width)236 static int line_resize(struct tsm_screen *con, struct line *line,
237 		       unsigned int width)
238 {
239 	struct cell *tmp;
240 
241 	if (!line || !width)
242 		return -EINVAL;
243 
244 	if (line->size < width) {
245 		tmp = realloc(line->cells, width * sizeof(struct cell));
246 		if (!tmp)
247 			return -ENOMEM;
248 
249 		line->cells = tmp;
250 
251 		while (line->size < width) {
252 			cell_init(con, &line->cells[line->size]);
253 			++line->size;
254 		}
255 	}
256 
257 	return 0;
258 }
259 
260 /* This links the given line into the scrollback-buffer */
link_to_scrollback(struct tsm_screen * con,struct line * line)261 static void link_to_scrollback(struct tsm_screen *con, struct line *line)
262 {
263 	struct line *tmp;
264 
265 	/* TODO: more sophisticated ageing */
266 	con->age = con->age_cnt;
267 
268 	if (con->sb_max == 0) {
269 		if (con->sel_active) {
270 			if (con->sel_start.line == line) {
271 				con->sel_start.line = NULL;
272 				con->sel_start.y = SELECTION_TOP;
273 			}
274 			if (con->sel_end.line == line) {
275 				con->sel_end.line = NULL;
276 				con->sel_end.y = SELECTION_TOP;
277 			}
278 		}
279 		line_free(line);
280 		return;
281 	}
282 
283 	/* Remove a line from the scrollback buffer if it reaches its maximum.
284 	 * We must take care to correctly keep the current position as the new
285 	 * line is linked in after we remove the top-most line here.
286 	 * sb_max == 0 is tested earlier so we can assume sb_max > 0 here. In
287 	 * other words, buf->sb_first is a valid line if sb_count >= sb_max. */
288 	if (con->sb_count >= con->sb_max) {
289 		tmp = con->sb_first;
290 		con->sb_first = tmp->next;
291 		if (tmp->next)
292 			tmp->next->prev = NULL;
293 		else
294 			con->sb_last = NULL;
295 		--con->sb_count;
296 
297 		/* (position == tmp && !next) means we have sb_max=1 so set
298 		 * position to the new line. Otherwise, set to new first line.
299 		 * If position!=tmp and we have a fixed-position then nothing
300 		 * needs to be done because we can stay at the same line. If we
301 		 * have no fixed-position, we need to set the position to the
302 		 * next inserted line, which can be "line", too. */
303 		if (con->sb_pos) {
304 			if (con->sb_pos == tmp ||
305 			    !(con->flags & TSM_SCREEN_FIXED_POS)) {
306 				if (con->sb_pos->next)
307 					con->sb_pos = con->sb_pos->next;
308 				else
309 					con->sb_pos = line;
310 			}
311 		}
312 
313 		if (con->sel_active) {
314 			if (con->sel_start.line == tmp) {
315 				con->sel_start.line = NULL;
316 				con->sel_start.y = SELECTION_TOP;
317 			}
318 			if (con->sel_end.line == tmp) {
319 				con->sel_end.line = NULL;
320 				con->sel_end.y = SELECTION_TOP;
321 			}
322 		}
323 		line_free(tmp);
324 	}
325 
326 	line->sb_id = ++con->sb_last_id;
327 	line->next = NULL;
328 	line->prev = con->sb_last;
329 	if (con->sb_last)
330 		con->sb_last->next = line;
331 	else
332 		con->sb_first = line;
333 	con->sb_last = line;
334 	++con->sb_count;
335 }
336 
screen_scroll_up(struct tsm_screen * con,unsigned int num)337 static void screen_scroll_up(struct tsm_screen *con, unsigned int num)
338 {
339 	unsigned int i, j, max, pos;
340 	int ret;
341 
342 	if (!num)
343 		return;
344 
345 	/* TODO: more sophisticated ageing */
346 	con->age = con->age_cnt;
347 
348 	max = con->margin_bottom + 1 - con->margin_top;
349 	if (num > max)
350 		num = max;
351 
352 	/* We cache lines on the stack to speed up the scrolling. However, if
353 	 * num is too big we might get overflows here so use recursion if num
354 	 * exceeds a hard-coded limit.
355 	 * 128 seems to be a sane limit that should never be reached but should
356 	 * also be small enough so we do not get stack overflows. */
357 	if (num > 128) {
358 		screen_scroll_up(con, 128);
359 		return screen_scroll_up(con, num - 128);
360 	}
361 	struct line *cache[num];
362 
363 	for (i = 0; i < num; ++i) {
364 		pos = con->margin_top + i;
365 		if (!(con->flags & TSM_SCREEN_ALTERNATE))
366 			ret = line_new(con, &cache[i], con->size_x);
367 		else
368 			ret = -EAGAIN;
369 
370 		if (!ret) {
371 			link_to_scrollback(con, con->lines[pos]);
372 		} else {
373 			cache[i] = con->lines[pos];
374 			for (j = 0; j < con->size_x; ++j)
375 				cell_init(con, &cache[i]->cells[j]);
376 		}
377 	}
378 
379 	if (num < max) {
380 		memmove(&con->lines[con->margin_top],
381 			&con->lines[con->margin_top + num],
382 			(max - num) * sizeof(struct line*));
383 	}
384 
385 	memcpy(&con->lines[con->margin_top + (max - num)],
386 	       cache, num * sizeof(struct line*));
387 
388 	if (con->sel_active) {
389 		if (!con->sel_start.line && con->sel_start.y >= 0) {
390 			con->sel_start.y -= num;
391 			if (con->sel_start.y < 0) {
392 				con->sel_start.line = con->sb_last;
393 				while (con->sel_start.line && ++con->sel_start.y < 0)
394 					con->sel_start.line = con->sel_start.line->prev;
395 				con->sel_start.y = SELECTION_TOP;
396 			}
397 		}
398 		if (!con->sel_end.line && con->sel_end.y >= 0) {
399 			con->sel_end.y -= num;
400 			if (con->sel_end.y < 0) {
401 				con->sel_end.line = con->sb_last;
402 				while (con->sel_end.line && ++con->sel_end.y < 0)
403 					con->sel_end.line = con->sel_end.line->prev;
404 				con->sel_end.y = SELECTION_TOP;
405 			}
406 		}
407 	}
408 }
409 
screen_scroll_down(struct tsm_screen * con,unsigned int num)410 static void screen_scroll_down(struct tsm_screen *con, unsigned int num)
411 {
412 	unsigned int i, j, max;
413 
414 	if (!num)
415 		return;
416 
417 	/* TODO: more sophisticated ageing */
418 	con->age = con->age_cnt;
419 
420 	max = con->margin_bottom + 1 - con->margin_top;
421 	if (num > max)
422 		num = max;
423 
424 	/* see screen_scroll_up() for an explanation */
425 	if (num > 128) {
426 		screen_scroll_down(con, 128);
427 		return screen_scroll_down(con, num - 128);
428 	}
429 	struct line *cache[num];
430 
431 	for (i = 0; i < num; ++i) {
432 		cache[i] = con->lines[con->margin_bottom - i];
433 		for (j = 0; j < con->size_x; ++j)
434 			cell_init(con, &cache[i]->cells[j]);
435 	}
436 
437 	if (num < max) {
438 		memmove(&con->lines[con->margin_top + num],
439 			&con->lines[con->margin_top],
440 			(max - num) * sizeof(struct line*));
441 	}
442 
443 	memcpy(&con->lines[con->margin_top],
444 	       cache, num * sizeof(struct line*));
445 
446 	if (con->sel_active) {
447 		if (!con->sel_start.line && con->sel_start.y >= 0)
448 			con->sel_start.y += num;
449 		if (!con->sel_end.line && con->sel_end.y >= 0)
450 			con->sel_end.y += num;
451 	}
452 }
453 
screen_write(struct tsm_screen * con,unsigned int x,unsigned int y,tsm_symbol_t ch,unsigned int len,const struct tsm_screen_attr * attr)454 static void screen_write(struct tsm_screen *con, unsigned int x,
455 			  unsigned int y, tsm_symbol_t ch, unsigned int len,
456 			  const struct tsm_screen_attr *attr)
457 {
458 	struct line *line;
459 	unsigned int i;
460 
461 	if (!len)
462 		return;
463 
464 	if (x >= con->size_x || y >= con->size_y) {
465 		llog_warn(con, "writing beyond buffer boundary");
466 		return;
467 	}
468 
469 	line = con->lines[y];
470 
471 	if ((con->flags & TSM_SCREEN_INSERT_MODE) &&
472 	    (int)x < ((int)con->size_x - len)) {
473 		line->age = con->age_cnt;
474 		memmove(&line->cells[x + len], &line->cells[x],
475 			sizeof(struct cell) * (con->size_x - len - x));
476 	}
477 
478 	line->cells[x].age = con->age_cnt;
479 	line->cells[x].ch = ch;
480 	line->cells[x].width = len;
481 	memcpy(&line->cells[x].attr, attr, sizeof(*attr));
482 
483 	for (i = 1; i < len && i + x < con->size_x; ++i) {
484 		line->cells[x + i].age = con->age_cnt;
485 		line->cells[x + i].width = 0;
486 	}
487 }
488 
screen_erase_region(struct tsm_screen * con,unsigned int x_from,unsigned int y_from,unsigned int x_to,unsigned int y_to,bool protect)489 static void screen_erase_region(struct tsm_screen *con,
490 				 unsigned int x_from,
491 				 unsigned int y_from,
492 				 unsigned int x_to,
493 				 unsigned int y_to,
494 				 bool protect)
495 {
496 	unsigned int to;
497 	struct line *line;
498 
499 	/* TODO: more sophisticated ageing */
500 	con->age = con->age_cnt;
501 
502 	if (y_to >= con->size_y)
503 		y_to = con->size_y - 1;
504 	if (x_to >= con->size_x)
505 		x_to = con->size_x - 1;
506 
507 	for ( ; y_from <= y_to; ++y_from) {
508 		line = con->lines[y_from];
509 		if (!line) {
510 			x_from = 0;
511 			continue;
512 		}
513 
514 		if (y_from == y_to)
515 			to = x_to;
516 		else
517 			to = con->size_x - 1;
518 		for ( ; x_from <= to; ++x_from) {
519 			if (protect && line->cells[x_from].attr.protect)
520 				continue;
521 
522 			cell_init(con, &line->cells[x_from]);
523 		}
524 		x_from = 0;
525 	}
526 }
527 
to_abs_x(struct tsm_screen * con,unsigned int x)528 static inline unsigned int to_abs_x(struct tsm_screen *con, unsigned int x)
529 {
530 	return x;
531 }
532 
to_abs_y(struct tsm_screen * con,unsigned int y)533 static inline unsigned int to_abs_y(struct tsm_screen *con, unsigned int y)
534 {
535 	if (!(con->flags & TSM_SCREEN_REL_ORIGIN))
536 		return y;
537 
538 	return con->margin_top + y;
539 }
540 
541 SHL_EXPORT
tsm_screen_new(struct tsm_screen ** out,tsm_log_t log,void * log_data)542 int tsm_screen_new(struct tsm_screen **out, tsm_log_t log, void *log_data)
543 {
544 	struct tsm_screen *con;
545 	int ret;
546 	unsigned int i;
547 
548 	if (!out)
549 		return -EINVAL;
550 
551 	con = malloc(sizeof(*con));
552 	if (!con)
553 		return -ENOMEM;
554 
555 	memset(con, 0, sizeof(*con));
556 	con->ref = 1;
557 	con->llog = log;
558 	con->llog_data = log_data;
559 	con->age_cnt = 1;
560 	con->age = con->age_cnt;
561 	con->def_attr.fr = 255;
562 	con->def_attr.fg = 255;
563 	con->def_attr.fb = 255;
564 
565 	ret = tsm_symbol_table_new(&con->sym_table);
566 	if (ret)
567 		goto err_free;
568 
569 	ret = tsm_screen_resize(con, 80, 24);
570 	if (ret)
571 		goto err_free;
572 
573 	llog_debug(con, "new screen");
574 	*out = con;
575 
576 	return 0;
577 
578 err_free:
579 	for (i = 0; i < con->line_num; ++i) {
580 		line_free(con->main_lines[i]);
581 		line_free(con->alt_lines[i]);
582 	}
583 	free(con->main_lines);
584 	free(con->alt_lines);
585 	free(con->tab_ruler);
586 	tsm_symbol_table_unref(con->sym_table);
587 	free(con);
588 	return ret;
589 }
590 
591 SHL_EXPORT
tsm_screen_ref(struct tsm_screen * con)592 void tsm_screen_ref(struct tsm_screen *con)
593 {
594 	if (!con)
595 		return;
596 
597 	++con->ref;
598 }
599 
600 SHL_EXPORT
tsm_screen_unref(struct tsm_screen * con)601 void tsm_screen_unref(struct tsm_screen *con)
602 {
603 	unsigned int i;
604 
605 	if (!con || !con->ref || --con->ref)
606 		return;
607 
608 	llog_debug(con, "destroying screen");
609 
610 	for (i = 0; i < con->line_num; ++i) {
611 		line_free(con->main_lines[i]);
612 		line_free(con->alt_lines[i]);
613 	}
614 	free(con->main_lines);
615 	free(con->alt_lines);
616 	free(con->tab_ruler);
617 	tsm_symbol_table_unref(con->sym_table);
618 	free(con);
619 }
620 
tsm_screen_set_opts(struct tsm_screen * scr,unsigned int opts)621 void tsm_screen_set_opts(struct tsm_screen *scr, unsigned int opts)
622 {
623 	if (!scr || !opts)
624 		return;
625 
626 	scr->opts |= opts;
627 }
628 
tsm_screen_reset_opts(struct tsm_screen * scr,unsigned int opts)629 void tsm_screen_reset_opts(struct tsm_screen *scr, unsigned int opts)
630 {
631 	if (!scr || !opts)
632 		return;
633 
634 	scr->opts &= ~opts;
635 }
636 
tsm_screen_get_opts(struct tsm_screen * scr)637 unsigned int tsm_screen_get_opts(struct tsm_screen *scr)
638 {
639 	if (!scr)
640 		return 0;
641 
642 	return scr->opts;
643 }
644 
645 SHL_EXPORT
tsm_screen_get_width(struct tsm_screen * con)646 unsigned int tsm_screen_get_width(struct tsm_screen *con)
647 {
648 	if (!con)
649 		return 0;
650 
651 	return con->size_x;
652 }
653 
654 SHL_EXPORT
tsm_screen_get_height(struct tsm_screen * con)655 unsigned int tsm_screen_get_height(struct tsm_screen *con)
656 {
657 	if (!con)
658 		return 0;
659 
660 	return con->size_y;
661 }
662 
663 SHL_EXPORT
tsm_screen_resize(struct tsm_screen * con,unsigned int x,unsigned int y)664 int tsm_screen_resize(struct tsm_screen *con, unsigned int x,
665 		      unsigned int y)
666 {
667 	struct line **cache;
668 	unsigned int i, j, width, diff, start;
669 	int ret;
670 	bool *tab_ruler;
671 
672 	if (!con || !x || !y)
673 		return -EINVAL;
674 
675 	if (con->size_x == x && con->size_y == y)
676 		return 0;
677 
678 	/* First make sure the line buffer is big enough for our new screen.
679 	 * That is, allocate all new lines and make sure each line has enough
680 	 * cells to hold the new screen or the current screen. If we fail, we
681 	 * can safely return -ENOMEM and the buffer is still valid. We must
682 	 * allocate the new lines to at least the same size as the current
683 	 * lines. Otherwise, if this function fails in later turns, we will have
684 	 * invalid lines in the buffer. */
685 	if (y > con->line_num) {
686 		/* resize main buffer */
687 		cache = realloc(con->main_lines, sizeof(struct line*) * y);
688 		if (!cache)
689 			return -ENOMEM;
690 
691 		if (con->lines == con->main_lines)
692 			con->lines = cache;
693 		con->main_lines = cache;
694 
695 		/* resize alt buffer */
696 		cache = realloc(con->alt_lines, sizeof(struct line*) * y);
697 		if (!cache)
698 			return -ENOMEM;
699 
700 		if (con->lines == con->alt_lines)
701 			con->lines = cache;
702 		con->alt_lines = cache;
703 
704 		/* allocate new lines */
705 		if (x > con->size_x)
706 			width = x;
707 		else
708 			width = con->size_x;
709 
710 		while (con->line_num < y) {
711 			ret = line_new(con, &con->main_lines[con->line_num],
712 				       width);
713 			if (ret)
714 				return ret;
715 
716 			ret = line_new(con, &con->alt_lines[con->line_num],
717 				       width);
718 			if (ret) {
719 				line_free(con->main_lines[con->line_num]);
720 				return ret;
721 			}
722 
723 			++con->line_num;
724 		}
725 	}
726 
727 	/* Resize all lines in the buffer if we increase screen width. This
728 	 * will guarantee that all lines are big enough so we can resize the
729 	 * buffer without reallocating them later. */
730 	if (x > con->size_x) {
731 		tab_ruler = realloc(con->tab_ruler, sizeof(bool) * x);
732 		if (!tab_ruler)
733 			return -ENOMEM;
734 		con->tab_ruler = tab_ruler;
735 
736 		for (i = 0; i < con->line_num; ++i) {
737 			ret = line_resize(con, con->main_lines[i], x);
738 			if (ret)
739 				return ret;
740 
741 			ret = line_resize(con, con->alt_lines[i], x);
742 			if (ret)
743 				return ret;
744 		}
745 	}
746 
747 	inc_age(con);
748 
749 	/* clear expansion/padding area */
750 	start = x;
751 	if (x > con->size_x)
752 		start = con->size_x;
753 	for (j = 0; j < con->line_num; ++j) {
754 		/* main-lines may go into SB, so clear all cells */
755 		i = 0;
756 		if (j < con->size_y)
757 			i = start;
758 
759 		for ( ; i < con->main_lines[j]->size; ++i)
760 			cell_init(con, &con->main_lines[j]->cells[i]);
761 
762 		/* alt-lines never go into SB, only clear visible cells */
763 		i = 0;
764 		if (j < con->size_y)
765 			i = con->size_x;
766 
767 		for ( ; i < x; ++i)
768 			cell_init(con, &con->alt_lines[j]->cells[i]);
769 	}
770 
771 	/* xterm destroys margins on resize, so do we */
772 	con->margin_top = 0;
773 	con->margin_bottom = con->size_y - 1;
774 
775 	/* reset tabs */
776 	for (i = 0; i < x; ++i) {
777 		if (i % 8 == 0)
778 			con->tab_ruler[i] = true;
779 		else
780 			con->tab_ruler[i] = false;
781 	}
782 
783 	/* We need to adjust x-size first as screen_scroll_up() and friends may
784 	 * have to reallocate lines. The y-size is adjusted after them to avoid
785 	 * missing lines when shrinking y-size.
786 	 * We need to carefully look for the functions that we call here as they
787 	 * have stronger invariants as when called normally. */
788 
789 	con->size_x = x;
790 	if (con->cursor_x >= con->size_x)
791 		move_cursor(con, con->size_x - 1, con->cursor_y);
792 
793 	/* scroll buffer if screen height shrinks */
794 	if (y < con->size_y) {
795 		diff = con->size_y - y;
796 		screen_scroll_up(con, diff);
797 		if (con->cursor_y > diff)
798 			move_cursor(con, con->cursor_x, con->cursor_y - diff);
799 		else
800 			move_cursor(con, con->cursor_x, 0);
801 	}
802 
803 	con->size_y = y;
804 	con->margin_bottom = con->size_y - 1;
805 	if (con->cursor_y >= con->size_y)
806 		move_cursor(con, con->cursor_x, con->size_y - 1);
807 
808 	return 0;
809 }
810 
811 SHL_EXPORT
tsm_screen_set_margins(struct tsm_screen * con,unsigned int top,unsigned int bottom)812 int tsm_screen_set_margins(struct tsm_screen *con,
813 			       unsigned int top, unsigned int bottom)
814 {
815 	unsigned int upper, lower;
816 
817 	if (!con)
818 		return -EINVAL;
819 
820 	if (!top)
821 		top = 1;
822 
823 	if (bottom <= top) {
824 		upper = 0;
825 		lower = con->size_y - 1;
826 	} else if (bottom > con->size_y) {
827 		upper = 0;
828 		lower = con->size_y - 1;
829 	} else {
830 		upper = top - 1;
831 		lower = bottom - 1;
832 	}
833 
834 	con->margin_top = upper;
835 	con->margin_bottom = lower;
836 	return 0;
837 }
838 
839 /* set maximum scrollback buffer size */
840 SHL_EXPORT
tsm_screen_set_max_sb(struct tsm_screen * con,unsigned int max)841 void tsm_screen_set_max_sb(struct tsm_screen *con,
842 			       unsigned int max)
843 {
844 	struct line *line;
845 
846 	if (!con)
847 		return;
848 
849 	inc_age(con);
850 	/* TODO: more sophisticated ageing */
851 	con->age = con->age_cnt;
852 
853 	while (con->sb_count > max) {
854 		line = con->sb_first;
855 		con->sb_first = line->next;
856 		if (line->next)
857 			line->next->prev = NULL;
858 		else
859 			con->sb_last = NULL;
860 		con->sb_count--;
861 
862 		/* We treat fixed/unfixed position the same here because we
863 		 * remove lines from the TOP of the scrollback buffer. */
864 		if (con->sb_pos == line)
865 			con->sb_pos = con->sb_first;
866 
867 		if (con->sel_active) {
868 			if (con->sel_start.line == line) {
869 				con->sel_start.line = NULL;
870 				con->sel_start.y = SELECTION_TOP;
871 			}
872 			if (con->sel_end.line == line) {
873 				con->sel_end.line = NULL;
874 				con->sel_end.y = SELECTION_TOP;
875 			}
876 		}
877 		line_free(line);
878 	}
879 
880 	con->sb_max = max;
881 }
882 
883 /* clear scrollback buffer */
884 SHL_EXPORT
tsm_screen_clear_sb(struct tsm_screen * con)885 void tsm_screen_clear_sb(struct tsm_screen *con)
886 {
887 	struct line *iter, *tmp;
888 
889 	if (!con)
890 		return;
891 
892 	inc_age(con);
893 	/* TODO: more sophisticated ageing */
894 	con->age = con->age_cnt;
895 
896 	for (iter = con->sb_first; iter; ) {
897 		tmp = iter;
898 		iter = iter->next;
899 		line_free(tmp);
900 	}
901 
902 	con->sb_first = NULL;
903 	con->sb_last = NULL;
904 	con->sb_count = 0;
905 	con->sb_pos = NULL;
906 
907 	if (con->sel_active) {
908 		if (con->sel_start.line) {
909 			con->sel_start.line = NULL;
910 			con->sel_start.y = SELECTION_TOP;
911 		}
912 		if (con->sel_end.line) {
913 			con->sel_end.line = NULL;
914 			con->sel_end.y = SELECTION_TOP;
915 		}
916 	}
917 }
918 
919 SHL_EXPORT
tsm_screen_sb_up(struct tsm_screen * con,unsigned int num)920 void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num)
921 {
922 	if (!con || !num)
923 		return;
924 
925 	inc_age(con);
926 	/* TODO: more sophisticated ageing */
927 	con->age = con->age_cnt;
928 
929 	while (num--) {
930 		if (con->sb_pos) {
931 			if (!con->sb_pos->prev)
932 				return;
933 
934 			con->sb_pos = con->sb_pos->prev;
935 		} else if (!con->sb_last) {
936 			return;
937 		} else {
938 			con->sb_pos = con->sb_last;
939 		}
940 	}
941 }
942 
943 SHL_EXPORT
tsm_screen_sb_down(struct tsm_screen * con,unsigned int num)944 void tsm_screen_sb_down(struct tsm_screen *con, unsigned int num)
945 {
946 	if (!con || !num)
947 		return;
948 
949 	inc_age(con);
950 	/* TODO: more sophisticated ageing */
951 	con->age = con->age_cnt;
952 
953 	while (num--) {
954 		if (con->sb_pos)
955 			con->sb_pos = con->sb_pos->next;
956 		else
957 			return;
958 	}
959 }
960 
961 SHL_EXPORT
tsm_screen_sb_page_up(struct tsm_screen * con,unsigned int num)962 void tsm_screen_sb_page_up(struct tsm_screen *con, unsigned int num)
963 {
964 	if (!con || !num)
965 		return;
966 
967 	inc_age(con);
968 	tsm_screen_sb_up(con, num * con->size_y);
969 }
970 
971 SHL_EXPORT
tsm_screen_sb_page_down(struct tsm_screen * con,unsigned int num)972 void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num)
973 {
974 	if (!con || !num)
975 		return;
976 
977 	inc_age(con);
978 	tsm_screen_sb_down(con, num * con->size_y);
979 }
980 
981 SHL_EXPORT
tsm_screen_sb_reset(struct tsm_screen * con)982 void tsm_screen_sb_reset(struct tsm_screen *con)
983 {
984 	if (!con)
985 		return;
986 
987 	inc_age(con);
988 	/* TODO: more sophisticated ageing */
989 	con->age = con->age_cnt;
990 
991 	con->sb_pos = NULL;
992 }
993 
994 SHL_EXPORT
tsm_screen_set_def_attr(struct tsm_screen * con,const struct tsm_screen_attr * attr)995 void tsm_screen_set_def_attr(struct tsm_screen *con,
996 				 const struct tsm_screen_attr *attr)
997 {
998 	if (!con || !attr)
999 		return;
1000 
1001 	memcpy(&con->def_attr, attr, sizeof(*attr));
1002 }
1003 
1004 SHL_EXPORT
tsm_screen_reset(struct tsm_screen * con)1005 void tsm_screen_reset(struct tsm_screen *con)
1006 {
1007 	unsigned int i;
1008 
1009 	if (!con)
1010 		return;
1011 
1012 	inc_age(con);
1013 	con->age = con->age_cnt;
1014 
1015 	con->flags = 0;
1016 	con->margin_top = 0;
1017 	con->margin_bottom = con->size_y - 1;
1018 	con->lines = con->main_lines;
1019 
1020 	for (i = 0; i < con->size_x; ++i) {
1021 		if (i % 8 == 0)
1022 			con->tab_ruler[i] = true;
1023 		else
1024 			con->tab_ruler[i] = false;
1025 	}
1026 }
1027 
1028 SHL_EXPORT
tsm_screen_set_flags(struct tsm_screen * con,unsigned int flags)1029 void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags)
1030 {
1031 	unsigned int old;
1032 	struct cell *c;
1033 
1034 	if (!con || !flags)
1035 		return;
1036 
1037 	inc_age(con);
1038 
1039 	old = con->flags;
1040 	con->flags |= flags;
1041 
1042 	if (!(old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) {
1043 		con->age = con->age_cnt;
1044 		con->lines = con->alt_lines;
1045 	}
1046 
1047 	if (!(old & TSM_SCREEN_HIDE_CURSOR) &&
1048 	    (flags & TSM_SCREEN_HIDE_CURSOR)) {
1049 		c = get_cursor_cell(con);
1050 		c->age = con->age_cnt;
1051 	}
1052 
1053 	if (!(old & TSM_SCREEN_INVERSE) && (flags & TSM_SCREEN_INVERSE))
1054 		con->age = con->age_cnt;
1055 }
1056 
1057 SHL_EXPORT
tsm_screen_reset_flags(struct tsm_screen * con,unsigned int flags)1058 void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags)
1059 {
1060 	unsigned int old;
1061 	struct cell *c;
1062 
1063 	if (!con || !flags)
1064 		return;
1065 
1066 	inc_age(con);
1067 
1068 	old = con->flags;
1069 	con->flags &= ~flags;
1070 
1071 	if ((old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) {
1072 		con->age = con->age_cnt;
1073 		con->lines = con->main_lines;
1074 	}
1075 
1076 	if ((old & TSM_SCREEN_HIDE_CURSOR) &&
1077 	    (flags & TSM_SCREEN_HIDE_CURSOR)) {
1078 		c = get_cursor_cell(con);
1079 		c->age = con->age_cnt;
1080 	}
1081 
1082 	if ((old & TSM_SCREEN_INVERSE) && (flags & TSM_SCREEN_INVERSE))
1083 		con->age = con->age_cnt;
1084 }
1085 
1086 SHL_EXPORT
tsm_screen_get_flags(struct tsm_screen * con)1087 unsigned int tsm_screen_get_flags(struct tsm_screen *con)
1088 {
1089 	if (!con)
1090 		return 0;
1091 
1092 	return con->flags;
1093 }
1094 
1095 SHL_EXPORT
tsm_screen_get_cursor_x(struct tsm_screen * con)1096 unsigned int tsm_screen_get_cursor_x(struct tsm_screen *con)
1097 {
1098 	if (!con)
1099 		return 0;
1100 
1101 	return con->cursor_x;
1102 }
1103 
1104 SHL_EXPORT
tsm_screen_get_cursor_y(struct tsm_screen * con)1105 unsigned int tsm_screen_get_cursor_y(struct tsm_screen *con)
1106 {
1107 	if (!con)
1108 		return 0;
1109 
1110 	return con->cursor_y;
1111 }
1112 
1113 SHL_EXPORT
tsm_screen_set_tabstop(struct tsm_screen * con)1114 void tsm_screen_set_tabstop(struct tsm_screen *con)
1115 {
1116 	if (!con || con->cursor_x >= con->size_x)
1117 		return;
1118 
1119 	con->tab_ruler[con->cursor_x] = true;
1120 }
1121 
1122 SHL_EXPORT
tsm_screen_reset_tabstop(struct tsm_screen * con)1123 void tsm_screen_reset_tabstop(struct tsm_screen *con)
1124 {
1125 	if (!con || con->cursor_x >= con->size_x)
1126 		return;
1127 
1128 	con->tab_ruler[con->cursor_x] = false;
1129 }
1130 
1131 SHL_EXPORT
tsm_screen_reset_all_tabstops(struct tsm_screen * con)1132 void tsm_screen_reset_all_tabstops(struct tsm_screen *con)
1133 {
1134 	unsigned int i;
1135 
1136 	if (!con)
1137 		return;
1138 
1139 	for (i = 0; i < con->size_x; ++i)
1140 		con->tab_ruler[i] = false;
1141 }
1142 
1143 SHL_EXPORT
tsm_screen_write(struct tsm_screen * con,tsm_symbol_t ch,const struct tsm_screen_attr * attr)1144 void tsm_screen_write(struct tsm_screen *con, tsm_symbol_t ch,
1145 			  const struct tsm_screen_attr *attr)
1146 {
1147 	unsigned int last, len;
1148 
1149 	if (!con)
1150 		return;
1151 
1152 	len = tsm_symbol_get_width(con->sym_table, ch);
1153 	if (!len)
1154 		return;
1155 
1156 	inc_age(con);
1157 
1158 	if (con->cursor_y <= con->margin_bottom ||
1159 	    con->cursor_y >= con->size_y)
1160 		last = con->margin_bottom;
1161 	else
1162 		last = con->size_y - 1;
1163 
1164 	if (con->cursor_x >= con->size_x) {
1165 		if (con->flags & TSM_SCREEN_AUTO_WRAP)
1166 			move_cursor(con, 0, con->cursor_y + 1);
1167 		else
1168 			move_cursor(con, con->size_x - 1, con->cursor_y);
1169 	}
1170 
1171 	if (con->cursor_y > last) {
1172 		move_cursor(con, con->cursor_x, last);
1173 		screen_scroll_up(con, 1);
1174 	}
1175 
1176 	screen_write(con, con->cursor_x, con->cursor_y, ch, len, attr);
1177 	move_cursor(con, con->cursor_x + len, con->cursor_y);
1178 }
1179 
1180 SHL_EXPORT
tsm_screen_newline(struct tsm_screen * con)1181 void tsm_screen_newline(struct tsm_screen *con)
1182 {
1183 	if (!con)
1184 		return;
1185 
1186 	inc_age(con);
1187 
1188 	tsm_screen_move_down(con, 1, true);
1189 	tsm_screen_move_line_home(con);
1190 }
1191 
1192 SHL_EXPORT
tsm_screen_scroll_up(struct tsm_screen * con,unsigned int num)1193 void tsm_screen_scroll_up(struct tsm_screen *con, unsigned int num)
1194 {
1195 	if (!con || !num)
1196 		return;
1197 
1198 	inc_age(con);
1199 
1200 	screen_scroll_up(con, num);
1201 }
1202 
1203 SHL_EXPORT
tsm_screen_scroll_down(struct tsm_screen * con,unsigned int num)1204 void tsm_screen_scroll_down(struct tsm_screen *con, unsigned int num)
1205 {
1206 	if (!con || !num)
1207 		return;
1208 
1209 	inc_age(con);
1210 
1211 	screen_scroll_down(con, num);
1212 }
1213 
1214 SHL_EXPORT
tsm_screen_move_to(struct tsm_screen * con,unsigned int x,unsigned int y)1215 void tsm_screen_move_to(struct tsm_screen *con, unsigned int x,
1216 			    unsigned int y)
1217 {
1218 	unsigned int last;
1219 
1220 	if (!con)
1221 		return;
1222 
1223 	inc_age(con);
1224 
1225 	if (con->flags & TSM_SCREEN_REL_ORIGIN)
1226 		last = con->margin_bottom;
1227 	else
1228 		last = con->size_y - 1;
1229 
1230 	x = to_abs_x(con, x);
1231 	if (x >= con->size_x)
1232 		x = con->size_x - 1;
1233 
1234 	y = to_abs_y(con, y);
1235 	if (y > last)
1236 		y = last;
1237 
1238 	move_cursor(con, x, y);
1239 }
1240 
1241 SHL_EXPORT
tsm_screen_move_up(struct tsm_screen * con,unsigned int num,bool scroll)1242 void tsm_screen_move_up(struct tsm_screen *con, unsigned int num,
1243 			    bool scroll)
1244 {
1245 	unsigned int diff, size;
1246 
1247 	if (!con || !num)
1248 		return;
1249 
1250 	inc_age(con);
1251 
1252 	if (con->cursor_y >= con->margin_top)
1253 		size = con->margin_top;
1254 	else
1255 		size = 0;
1256 
1257 	diff = con->cursor_y - size;
1258 	if (num > diff) {
1259 		num -= diff;
1260 		if (scroll)
1261 			screen_scroll_down(con, num);
1262 		move_cursor(con, con->cursor_x, size);
1263 	} else {
1264 		move_cursor(con, con->cursor_x, con->cursor_y - num);
1265 	}
1266 }
1267 
1268 SHL_EXPORT
tsm_screen_move_down(struct tsm_screen * con,unsigned int num,bool scroll)1269 void tsm_screen_move_down(struct tsm_screen *con, unsigned int num,
1270 			      bool scroll)
1271 {
1272 	unsigned int diff, size;
1273 
1274 	if (!con || !num)
1275 		return;
1276 
1277 	inc_age(con);
1278 
1279 	if (con->cursor_y <= con->margin_bottom)
1280 		size = con->margin_bottom + 1;
1281 	else
1282 		size = con->size_y;
1283 
1284 	diff = size - con->cursor_y - 1;
1285 	if (num > diff) {
1286 		num -= diff;
1287 		if (scroll)
1288 			screen_scroll_up(con, num);
1289 		move_cursor(con, con->cursor_x, size - 1);
1290 	} else {
1291 		move_cursor(con, con->cursor_x, con->cursor_y + num);
1292 	}
1293 }
1294 
1295 SHL_EXPORT
tsm_screen_move_left(struct tsm_screen * con,unsigned int num)1296 void tsm_screen_move_left(struct tsm_screen *con, unsigned int num)
1297 {
1298 	unsigned int x;
1299 
1300 	if (!con || !num)
1301 		return;
1302 
1303 	inc_age(con);
1304 
1305 	if (num > con->size_x)
1306 		num = con->size_x;
1307 
1308 	x = con->cursor_x;
1309 	if (x >= con->size_x)
1310 		x = con->size_x - 1;
1311 
1312 	if (num > x)
1313 		move_cursor(con, 0, con->cursor_y);
1314 	else
1315 		move_cursor(con, x - num, con->cursor_y);
1316 }
1317 
1318 SHL_EXPORT
tsm_screen_move_right(struct tsm_screen * con,unsigned int num)1319 void tsm_screen_move_right(struct tsm_screen *con, unsigned int num)
1320 {
1321 	if (!con || !num)
1322 		return;
1323 
1324 	inc_age(con);
1325 
1326 	if (num > con->size_x)
1327 		num = con->size_x;
1328 
1329 	if (num + con->cursor_x >= con->size_x)
1330 		move_cursor(con, con->size_x - 1, con->cursor_y);
1331 	else
1332 		move_cursor(con, con->cursor_x + num, con->cursor_y);
1333 }
1334 
1335 SHL_EXPORT
tsm_screen_move_line_end(struct tsm_screen * con)1336 void tsm_screen_move_line_end(struct tsm_screen *con)
1337 {
1338 	if (!con)
1339 		return;
1340 
1341 	inc_age(con);
1342 
1343 	move_cursor(con, con->size_x - 1, con->cursor_y);
1344 }
1345 
1346 SHL_EXPORT
tsm_screen_move_line_home(struct tsm_screen * con)1347 void tsm_screen_move_line_home(struct tsm_screen *con)
1348 {
1349 	if (!con)
1350 		return;
1351 
1352 	inc_age(con);
1353 
1354 	move_cursor(con, 0, con->cursor_y);
1355 }
1356 
1357 SHL_EXPORT
tsm_screen_tab_right(struct tsm_screen * con,unsigned int num)1358 void tsm_screen_tab_right(struct tsm_screen *con, unsigned int num)
1359 {
1360 	unsigned int i, j, x;
1361 
1362 	if (!con || !num)
1363 		return;
1364 
1365 	inc_age(con);
1366 
1367 	x = con->cursor_x;
1368 	for (i = 0; i < num; ++i) {
1369 		for (j = x + 1; j < con->size_x; ++j) {
1370 			if (con->tab_ruler[j])
1371 				break;
1372 		}
1373 
1374 		x = j;
1375 		if (x + 1 >= con->size_x)
1376 			break;
1377 	}
1378 
1379 	/* tabs never cause pending new-lines */
1380 	if (x >= con->size_x)
1381 		x = con->size_x - 1;
1382 
1383 	move_cursor(con, x, con->cursor_y);
1384 }
1385 
1386 SHL_EXPORT
tsm_screen_tab_left(struct tsm_screen * con,unsigned int num)1387 void tsm_screen_tab_left(struct tsm_screen *con, unsigned int num)
1388 {
1389 	unsigned int i, x;
1390 	int j;
1391 
1392 	if (!con || !num)
1393 		return;
1394 
1395 	inc_age(con);
1396 
1397 	x = con->cursor_x;
1398 	for (i = 0; i < num; ++i) {
1399 		for (j = x - 1; j > 0; --j) {
1400 			if (con->tab_ruler[j])
1401 				break;
1402 		}
1403 
1404 		if (j <= 0) {
1405 			x = 0;
1406 			break;
1407 		}
1408 		x = j;
1409 	}
1410 
1411 	move_cursor(con, x, con->cursor_y);
1412 }
1413 
1414 SHL_EXPORT
tsm_screen_insert_lines(struct tsm_screen * con,unsigned int num)1415 void tsm_screen_insert_lines(struct tsm_screen *con, unsigned int num)
1416 {
1417 	unsigned int i, j, max;
1418 
1419 	if (!con || !num)
1420 		return;
1421 
1422 	if (con->cursor_y < con->margin_top ||
1423 	    con->cursor_y > con->margin_bottom)
1424 		return;
1425 
1426 	inc_age(con);
1427 	/* TODO: more sophisticated ageing */
1428 	con->age = con->age_cnt;
1429 
1430 	max = con->margin_bottom - con->cursor_y + 1;
1431 	if (num > max)
1432 		num = max;
1433 
1434 	struct line *cache[num];
1435 
1436 	for (i = 0; i < num; ++i) {
1437 		cache[i] = con->lines[con->margin_bottom - i];
1438 		for (j = 0; j < con->size_x; ++j)
1439 			cell_init(con, &cache[i]->cells[j]);
1440 	}
1441 
1442 	if (num < max) {
1443 		memmove(&con->lines[con->cursor_y + num],
1444 			&con->lines[con->cursor_y],
1445 			(max - num) * sizeof(struct line*));
1446 
1447 		memcpy(&con->lines[con->cursor_y],
1448 		       cache, num * sizeof(struct line*));
1449 	}
1450 
1451 	con->cursor_x = 0;
1452 }
1453 
1454 SHL_EXPORT
tsm_screen_delete_lines(struct tsm_screen * con,unsigned int num)1455 void tsm_screen_delete_lines(struct tsm_screen *con, unsigned int num)
1456 {
1457 	unsigned int i, j, max;
1458 
1459 	if (!con || !num)
1460 		return;
1461 
1462 	if (con->cursor_y < con->margin_top ||
1463 	    con->cursor_y > con->margin_bottom)
1464 		return;
1465 
1466 	inc_age(con);
1467 	/* TODO: more sophisticated ageing */
1468 	con->age = con->age_cnt;
1469 
1470 	max = con->margin_bottom - con->cursor_y + 1;
1471 	if (num > max)
1472 		num = max;
1473 
1474 	struct line *cache[num];
1475 
1476 	for (i = 0; i < num; ++i) {
1477 		cache[i] = con->lines[con->cursor_y + i];
1478 		for (j = 0; j < con->size_x; ++j)
1479 			cell_init(con, &cache[i]->cells[j]);
1480 	}
1481 
1482 	if (num < max) {
1483 		memmove(&con->lines[con->cursor_y],
1484 			&con->lines[con->cursor_y + num],
1485 			(max - num) * sizeof(struct line*));
1486 
1487 		memcpy(&con->lines[con->cursor_y + (max - num)],
1488 		       cache, num * sizeof(struct line*));
1489 	}
1490 
1491 	con->cursor_x = 0;
1492 }
1493 
1494 SHL_EXPORT
tsm_screen_insert_chars(struct tsm_screen * con,unsigned int num)1495 void tsm_screen_insert_chars(struct tsm_screen *con, unsigned int num)
1496 {
1497 	struct cell *cells;
1498 	unsigned int max, mv, i;
1499 
1500 	if (!con || !num || !con->size_y || !con->size_x)
1501 		return;
1502 
1503 	inc_age(con);
1504 	/* TODO: more sophisticated ageing */
1505 	con->age = con->age_cnt;
1506 
1507 	if (con->cursor_x >= con->size_x)
1508 		con->cursor_x = con->size_x - 1;
1509 	if (con->cursor_y >= con->size_y)
1510 		con->cursor_y = con->size_y - 1;
1511 
1512 	max = con->size_x - con->cursor_x;
1513 	if (num > max)
1514 		num = max;
1515 	mv = max - num;
1516 
1517 	cells = con->lines[con->cursor_y]->cells;
1518 	if (mv)
1519 		memmove(&cells[con->cursor_x + num],
1520 			&cells[con->cursor_x],
1521 			mv * sizeof(*cells));
1522 
1523 	for (i = 0; i < num; ++i)
1524 		cell_init(con, &cells[con->cursor_x + i]);
1525 }
1526 
1527 SHL_EXPORT
tsm_screen_delete_chars(struct tsm_screen * con,unsigned int num)1528 void tsm_screen_delete_chars(struct tsm_screen *con, unsigned int num)
1529 {
1530 	struct cell *cells;
1531 	unsigned int max, mv, i;
1532 
1533 	if (!con || !num || !con->size_y || !con->size_x)
1534 		return;
1535 
1536 	inc_age(con);
1537 	/* TODO: more sophisticated ageing */
1538 	con->age = con->age_cnt;
1539 
1540 	if (con->cursor_x >= con->size_x)
1541 		con->cursor_x = con->size_x - 1;
1542 	if (con->cursor_y >= con->size_y)
1543 		con->cursor_y = con->size_y - 1;
1544 
1545 	max = con->size_x - con->cursor_x;
1546 	if (num > max)
1547 		num = max;
1548 	mv = max - num;
1549 
1550 	cells = con->lines[con->cursor_y]->cells;
1551 	if (mv)
1552 		memmove(&cells[con->cursor_x],
1553 			&cells[con->cursor_x + num],
1554 			mv * sizeof(*cells));
1555 
1556 	for (i = 0; i < num; ++i)
1557 		cell_init(con, &cells[con->cursor_x + mv + i]);
1558 }
1559 
1560 SHL_EXPORT
tsm_screen_erase_cursor(struct tsm_screen * con)1561 void tsm_screen_erase_cursor(struct tsm_screen *con)
1562 {
1563 	unsigned int x;
1564 
1565 	if (!con)
1566 		return;
1567 
1568 	inc_age(con);
1569 
1570 	if (con->cursor_x >= con->size_x)
1571 		x = con->size_x - 1;
1572 	else
1573 		x = con->cursor_x;
1574 
1575 	screen_erase_region(con, x, con->cursor_y, x, con->cursor_y, false);
1576 }
1577 
1578 SHL_EXPORT
tsm_screen_erase_chars(struct tsm_screen * con,unsigned int num)1579 void tsm_screen_erase_chars(struct tsm_screen *con, unsigned int num)
1580 {
1581 	unsigned int x;
1582 
1583 	if (!con || !num)
1584 		return;
1585 
1586 	inc_age(con);
1587 
1588 	if (con->cursor_x >= con->size_x)
1589 		x = con->size_x - 1;
1590 	else
1591 		x = con->cursor_x;
1592 
1593 	screen_erase_region(con, x, con->cursor_y, x + num - 1, con->cursor_y,
1594 			     false);
1595 }
1596 
1597 SHL_EXPORT
tsm_screen_erase_cursor_to_end(struct tsm_screen * con,bool protect)1598 void tsm_screen_erase_cursor_to_end(struct tsm_screen *con,
1599 				        bool protect)
1600 {
1601 	unsigned int x;
1602 
1603 	if (!con)
1604 		return;
1605 
1606 	inc_age(con);
1607 
1608 	if (con->cursor_x >= con->size_x)
1609 		x = con->size_x - 1;
1610 	else
1611 		x = con->cursor_x;
1612 
1613 	screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1614 			     con->cursor_y, protect);
1615 }
1616 
1617 SHL_EXPORT
tsm_screen_erase_home_to_cursor(struct tsm_screen * con,bool protect)1618 void tsm_screen_erase_home_to_cursor(struct tsm_screen *con,
1619 					 bool protect)
1620 {
1621 	if (!con)
1622 		return;
1623 
1624 	inc_age(con);
1625 
1626 	screen_erase_region(con, 0, con->cursor_y, con->cursor_x,
1627 			     con->cursor_y, protect);
1628 }
1629 
1630 SHL_EXPORT
tsm_screen_erase_current_line(struct tsm_screen * con,bool protect)1631 void tsm_screen_erase_current_line(struct tsm_screen *con,
1632 				       bool protect)
1633 {
1634 	if (!con)
1635 		return;
1636 
1637 	inc_age(con);
1638 
1639 	screen_erase_region(con, 0, con->cursor_y, con->size_x - 1,
1640 			     con->cursor_y, protect);
1641 }
1642 
1643 SHL_EXPORT
tsm_screen_erase_screen_to_cursor(struct tsm_screen * con,bool protect)1644 void tsm_screen_erase_screen_to_cursor(struct tsm_screen *con,
1645 					   bool protect)
1646 {
1647 	if (!con)
1648 		return;
1649 
1650 	inc_age(con);
1651 
1652 	screen_erase_region(con, 0, 0, con->cursor_x, con->cursor_y, protect);
1653 }
1654 
1655 SHL_EXPORT
tsm_screen_erase_cursor_to_screen(struct tsm_screen * con,bool protect)1656 void tsm_screen_erase_cursor_to_screen(struct tsm_screen *con,
1657 					   bool protect)
1658 {
1659 	unsigned int x;
1660 
1661 	if (!con)
1662 		return;
1663 
1664 	inc_age(con);
1665 
1666 	if (con->cursor_x >= con->size_x)
1667 		x = con->size_x - 1;
1668 	else
1669 		x = con->cursor_x;
1670 
1671 	screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1672 			     con->size_y - 1, protect);
1673 }
1674 
1675 SHL_EXPORT
tsm_screen_erase_screen(struct tsm_screen * con,bool protect)1676 void tsm_screen_erase_screen(struct tsm_screen *con, bool protect)
1677 {
1678 	if (!con)
1679 		return;
1680 
1681 	inc_age(con);
1682 
1683 	screen_erase_region(con, 0, 0, con->size_x - 1, con->size_y - 1,
1684 			     protect);
1685 }
1686 
1687 /*
1688  * Selection Code
1689  * If a running pty-client does not support mouse-tracking extensions, a
1690  * terminal can manually mark selected areas if it does mouse-tracking itself.
1691  * This tracking is slightly different than the integrated client-tracking:
1692  *
1693  * Initial state is no-selection. At any time selection_reset() can be called to
1694  * clear the selection and go back to initial state.
1695  * If the user presses a mouse-button, the terminal can calculate the selected
1696  * cell and call selection_start() to notify the terminal that the user started
1697  * the selection. While the mouse-button is held down, the terminal should call
1698  * selection_target() whenever a mouse-event occurs. This will tell the screen
1699  * layer to draw the selection from the initial start up to the last given
1700  * target.
1701  * Please note that the selection-start cannot be modified by the terminal
1702  * during a selection. Instead, the screen-layer automatically moves it along
1703  * with any scroll-operations or inserts/deletes. This also means, the terminal
1704  * must _not_ cache the start-position itself as it may change under the hood.
1705  * This selection takes also care of scrollback-buffer selections and correctly
1706  * moves selection state along.
1707  *
1708  * Please note that this is not the kind of selection that some PTY applications
1709  * support. If the client supports the mouse-protocol, then it can also control
1710  * a separate screen-selection which is always inside of the actual screen. This
1711  * is a totally different selection.
1712  */
1713 
selection_set(struct tsm_screen * con,struct selection_pos * sel,unsigned int x,unsigned int y)1714 static void selection_set(struct tsm_screen *con, struct selection_pos *sel,
1715 			  unsigned int x, unsigned int y)
1716 {
1717 	struct line *pos;
1718 
1719 	sel->line = NULL;
1720 	pos = con->sb_pos;
1721 
1722 	while (y && pos) {
1723 		--y;
1724 		pos = pos->next;
1725 	}
1726 
1727 	if (pos)
1728 		sel->line = pos;
1729 
1730 	sel->x = x;
1731 	sel->y = y;
1732 }
1733 
1734 SHL_EXPORT
tsm_screen_selection_reset(struct tsm_screen * con)1735 void tsm_screen_selection_reset(struct tsm_screen *con)
1736 {
1737 	if (!con)
1738 		return;
1739 
1740 	inc_age(con);
1741 	/* TODO: more sophisticated ageing */
1742 	con->age = con->age_cnt;
1743 
1744 	con->sel_active = false;
1745 }
1746 
1747 SHL_EXPORT
tsm_screen_selection_start(struct tsm_screen * con,unsigned int posx,unsigned int posy)1748 void tsm_screen_selection_start(struct tsm_screen *con,
1749 				unsigned int posx,
1750 				unsigned int posy)
1751 {
1752 	if (!con)
1753 		return;
1754 
1755 	inc_age(con);
1756 	/* TODO: more sophisticated ageing */
1757 	con->age = con->age_cnt;
1758 
1759 	con->sel_active = true;
1760 	selection_set(con, &con->sel_start, posx, posy);
1761 	memcpy(&con->sel_end, &con->sel_start, sizeof(con->sel_end));
1762 }
1763 
1764 SHL_EXPORT
tsm_screen_selection_target(struct tsm_screen * con,unsigned int posx,unsigned int posy)1765 void tsm_screen_selection_target(struct tsm_screen *con,
1766 				 unsigned int posx,
1767 				 unsigned int posy)
1768 {
1769 	if (!con || !con->sel_active)
1770 		return;
1771 
1772 	inc_age(con);
1773 	/* TODO: more sophisticated ageing */
1774 	con->age = con->age_cnt;
1775 
1776 	selection_set(con, &con->sel_end, posx, posy);
1777 }
1778 
1779 /* TODO: tsm_ucs4_to_utf8 expects UCS4 characters, but a cell contains a
1780  * tsm-symbol (which can contain multiple UCS4 chars). Fix this when introducing
1781  * support for combining characters. */
copy_line(struct line * line,char * buf,unsigned int start,unsigned int len)1782 static unsigned int copy_line(struct line *line, char *buf,
1783 			      unsigned int start, unsigned int len)
1784 {
1785 	unsigned int i, end;
1786 	char *pos = buf;
1787 
1788 	end = start + len;
1789 	for (i = start; i < line->size && i < end; ++i) {
1790 		if (i < line->size || !line->cells[i].ch)
1791 			pos += tsm_ucs4_to_utf8(line->cells[i].ch, pos);
1792 		else
1793 			pos += tsm_ucs4_to_utf8(' ', pos);
1794 	}
1795 
1796 	return pos - buf;
1797 }
1798 
1799 /* TODO: This beast definitely needs some "beautification", however, it's meant
1800  * as a "proof-of-concept" so its enough for now. */
1801 SHL_EXPORT
tsm_screen_selection_copy(struct tsm_screen * con,char ** out)1802 int tsm_screen_selection_copy(struct tsm_screen *con, char **out)
1803 {
1804 	unsigned int len, i;
1805 	struct selection_pos *start, *end;
1806 	struct line *iter;
1807 	char *str, *pos;
1808 
1809 	if (!con || !out)
1810 		return -EINVAL;
1811 
1812 	if (!con->sel_active)
1813 		return -ENOENT;
1814 
1815 	/* check whether sel_start or sel_end comes first */
1816 	if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP) {
1817 		if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
1818 			str = strdup("");
1819 			if (!str)
1820 				return -ENOMEM;
1821 			*out = str;
1822 			return 0;
1823 		}
1824 		start = &con->sel_start;
1825 		end = &con->sel_end;
1826 	} else if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
1827 		start = &con->sel_end;
1828 		end = &con->sel_start;
1829 	} else if (con->sel_start.line && con->sel_end.line) {
1830 		if (con->sel_start.line->sb_id < con->sel_end.line->sb_id) {
1831 			start = &con->sel_start;
1832 			end = &con->sel_end;
1833 		} else if (con->sel_start.line->sb_id > con->sel_end.line->sb_id) {
1834 			start = &con->sel_end;
1835 			end = &con->sel_start;
1836 		} else if (con->sel_start.x < con->sel_end.x) {
1837 			start = &con->sel_start;
1838 			end = &con->sel_end;
1839 		} else {
1840 			start = &con->sel_end;
1841 			end = &con->sel_start;
1842 		}
1843 	} else if (con->sel_start.line) {
1844 		start = &con->sel_start;
1845 		end = &con->sel_end;
1846 	} else if (con->sel_end.line) {
1847 		start = &con->sel_end;
1848 		end = &con->sel_start;
1849 	} else if (con->sel_start.y < con->sel_end.y) {
1850 		start = &con->sel_start;
1851 		end = &con->sel_end;
1852 	} else if (con->sel_start.y > con->sel_end.y) {
1853 		start = &con->sel_end;
1854 		end = &con->sel_start;
1855 	} else if (con->sel_start.x < con->sel_end.x) {
1856 		start = &con->sel_start;
1857 		end = &con->sel_end;
1858 	} else {
1859 		start = &con->sel_end;
1860 		end = &con->sel_start;
1861 	}
1862 
1863 	/* calculate size of buffer */
1864 	len = 0;
1865 	iter = start->line;
1866 	if (!iter && start->y == SELECTION_TOP)
1867 		iter = con->sb_first;
1868 
1869 	while (iter) {
1870 		if (iter == start->line && iter == end->line) {
1871 			if (iter->size > start->x) {
1872 				if (iter->size > end->x)
1873 					len += end->x - start->x + 1;
1874 				else
1875 					len += iter->size - start->x;
1876 			}
1877 			break;
1878 		} else if (iter == start->line) {
1879 			if (iter->size > start->x)
1880 				len += iter->size - start->x;
1881 		} else if (iter == end->line) {
1882 			if (iter->size > end->x)
1883 				len += end->x + 1;
1884 			else
1885 				len += iter->size;
1886 			break;
1887 		} else {
1888 			len += iter->size;
1889 		}
1890 
1891 		++len;
1892 		iter = iter->next;
1893 	}
1894 
1895 	if (!end->line) {
1896 		if (start->line || start->y == SELECTION_TOP)
1897 			i = 0;
1898 		else
1899 			i = start->y;
1900 		for ( ; i < con->size_y; ++i) {
1901 			if (!start->line && start->y == i && end->y == i) {
1902 				if (con->size_x > start->x) {
1903 					if (con->size_x > end->x)
1904 						len += end->x - start->x + 1;
1905 					else
1906 						len += con->size_x - start->x;
1907 				}
1908 				break;
1909 			} else if (!start->line && start->y == i) {
1910 				if (con->size_x > start->x)
1911 					len += con->size_x - start->x;
1912 			} else if (end->y == i) {
1913 				if (con->size_x > end->x)
1914 					len += end->x + 1;
1915 				else
1916 					len += con->size_x;
1917 				break;
1918 			} else {
1919 				len += con->size_x;
1920 			}
1921 
1922 			++len;
1923 		}
1924 	}
1925 
1926 	/* allocate buffer */
1927 	len *= 4;
1928 	++len;
1929 	str = malloc(len);
1930 	if (!str)
1931 		return -ENOMEM;
1932 	pos = str;
1933 
1934 	/* copy data into buffer */
1935 	iter = start->line;
1936 	if (!iter && start->y == SELECTION_TOP)
1937 		iter = con->sb_first;
1938 
1939 	while (iter) {
1940 		if (iter == start->line && iter == end->line) {
1941 			if (iter->size > start->x) {
1942 				if (iter->size > end->x)
1943 					len = end->x - start->x + 1;
1944 				else
1945 					len = iter->size - start->x;
1946 				pos += copy_line(iter, pos, start->x, len);
1947 			}
1948 			break;
1949 		} else if (iter == start->line) {
1950 			if (iter->size > start->x)
1951 				pos += copy_line(iter, pos, start->x,
1952 						 iter->size - start->x);
1953 		} else if (iter == end->line) {
1954 			if (iter->size > end->x)
1955 				len = end->x + 1;
1956 			else
1957 				len = iter->size;
1958 			pos += copy_line(iter, pos, 0, len);
1959 			break;
1960 		} else {
1961 			pos += copy_line(iter, pos, 0, iter->size);
1962 		}
1963 
1964 		*pos++ = '\n';
1965 		iter = iter->next;
1966 	}
1967 
1968 	if (!end->line) {
1969 		if (start->line || start->y == SELECTION_TOP)
1970 			i = 0;
1971 		else
1972 			i = start->y;
1973 		for ( ; i < con->size_y; ++i) {
1974 			iter = con->lines[i];
1975 			if (!start->line && start->y == i && end->y == i) {
1976 				if (con->size_x > start->x) {
1977 					if (con->size_x > end->x)
1978 						len = end->x - start->x + 1;
1979 					else
1980 						len = con->size_x - start->x;
1981 					pos += copy_line(iter, pos, start->x, len);
1982 				}
1983 				break;
1984 			} else if (!start->line && start->y == i) {
1985 				if (con->size_x > start->x)
1986 					pos += copy_line(iter, pos, start->x,
1987 							 con->size_x - start->x);
1988 			} else if (end->y == i) {
1989 				if (con->size_x > end->x)
1990 					len = end->x + 1;
1991 				else
1992 					len = con->size_x;
1993 				pos += copy_line(iter, pos, 0, len);
1994 				break;
1995 			} else {
1996 				pos += copy_line(iter, pos, 0, con->size_x);
1997 			}
1998 
1999 			*pos++ = '\n';
2000 		}
2001 	}
2002 
2003 	/* return buffer */
2004 	*pos = 0;
2005 	*out = str;
2006 	return pos - str;
2007 }
2008 
2009 SHL_EXPORT
tsm_screen_draw(struct tsm_screen * con,tsm_screen_draw_cb draw_cb,void * data)2010 tsm_age_t tsm_screen_draw(struct tsm_screen *con, tsm_screen_draw_cb draw_cb,
2011 			  void *data)
2012 {
2013 	unsigned int cur_x, cur_y;
2014 	unsigned int i, j, k;
2015 	struct line *iter, *line = NULL;
2016 	struct cell *cell;
2017 	struct tsm_screen_attr attr;
2018 	int ret, warned = 0;
2019 	const uint32_t *ch;
2020 	size_t len;
2021 	bool in_sel = false, sel_start = false, sel_end = false;
2022 	bool was_sel = false;
2023 	tsm_age_t age;
2024 
2025 	if (!con || !draw_cb)
2026 		return 0;
2027 
2028 	cur_x = con->cursor_x;
2029 	if (con->cursor_x >= con->size_x)
2030 		cur_x = con->size_x - 1;
2031 	cur_y = con->cursor_y;
2032 	if (con->cursor_y >= con->size_y)
2033 		cur_y = con->size_y - 1;
2034 
2035 	/* push each character into rendering pipeline */
2036 
2037 	iter = con->sb_pos;
2038 	k = 0;
2039 
2040 	if (con->sel_active) {
2041 		if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP)
2042 			in_sel = !in_sel;
2043 		if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP)
2044 			in_sel = !in_sel;
2045 
2046 		if (con->sel_start.line &&
2047 		    (!iter || con->sel_start.line->sb_id < iter->sb_id))
2048 			in_sel = !in_sel;
2049 		if (con->sel_end.line &&
2050 		    (!iter || con->sel_end.line->sb_id < iter->sb_id))
2051 			in_sel = !in_sel;
2052 	}
2053 
2054 	for (i = 0; i < con->size_y; ++i) {
2055 		if (iter) {
2056 			line = iter;
2057 			iter = iter->next;
2058 		} else {
2059 			line = con->lines[k];
2060 			k++;
2061 		}
2062 
2063 		if (con->sel_active) {
2064 			if (con->sel_start.line == line ||
2065 			    (!con->sel_start.line &&
2066 			     con->sel_start.y == k - 1))
2067 				sel_start = true;
2068 			else
2069 				sel_start = false;
2070 			if (con->sel_end.line == line ||
2071 			    (!con->sel_end.line &&
2072 			     con->sel_end.y == k - 1))
2073 				sel_end = true;
2074 			else
2075 				sel_end = false;
2076 
2077 			was_sel = false;
2078 		}
2079 
2080 		for (j = 0; j < con->size_x; ++j) {
2081 			cell = &line->cells[j];
2082 			memcpy(&attr, &cell->attr, sizeof(attr));
2083 
2084 			if (con->sel_active) {
2085 				if (sel_start &&
2086 				    j == con->sel_start.x) {
2087 					was_sel = in_sel;
2088 					in_sel = !in_sel;
2089 				}
2090 				if (sel_end &&
2091 				    j == con->sel_end.x) {
2092 					was_sel = in_sel;
2093 					in_sel = !in_sel;
2094 				}
2095 			}
2096 
2097 			if (k == cur_y + 1 && j == cur_x &&
2098 			    !(con->flags & TSM_SCREEN_HIDE_CURSOR))
2099 				attr.inverse = !attr.inverse;
2100 
2101 			/* TODO: do some more sophisticated inverse here. When
2102 			 * INVERSE mode is set, we should instead just select
2103 			 * inverse colors instead of switching background and
2104 			 * foreground */
2105 			if (con->flags & TSM_SCREEN_INVERSE)
2106 				attr.inverse = !attr.inverse;
2107 
2108 			if (in_sel || was_sel) {
2109 				was_sel = false;
2110 				attr.inverse = !attr.inverse;
2111 			}
2112 
2113 			if (con->age_reset) {
2114 				age = 0;
2115 			} else {
2116 				age = cell->age;
2117 				if (line->age > age)
2118 					age = line->age;
2119 				if (con->age > age)
2120 					age = con->age;
2121 			}
2122 
2123 			ch = tsm_symbol_get(con->sym_table, &cell->ch, &len);
2124 			if (cell->ch == ' ' || cell->ch == 0)
2125 				len = 0;
2126 			ret = draw_cb(con, cell->ch, ch, len, cell->width,
2127 				      j, i, &attr, age, data);
2128 			if (ret && warned++ < 3) {
2129 				llog_debug(con,
2130 					   "cannot draw glyph at %ux%u via text-renderer",
2131 					   j, i);
2132 				if (warned == 3)
2133 					llog_debug(con,
2134 						   "suppressing further warnings during this rendering round");
2135 			}
2136 		}
2137 	}
2138 
2139 	if (con->age_reset) {
2140 		con->age_reset = 0;
2141 		return 0;
2142 	} else {
2143 		return con->age_cnt;
2144 	}
2145 }
2146