tsm: screen: fix out-of-bounds access when drawing sb
[platform/upstream/kmscon.git] / src / tsm_screen.c
1 /*
2  * TSM - Screen Management
3  *
4  * Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
5  * Copyright (c) 2011 University of Tuebingen
6  *
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:
14  *
15  * The above copyright notice and this permission notice shall be included
16  * in all copies or substantial portions of the Software.
17  *
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.
25  */
26
27 /*
28  * Screen Management
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.
32  */
33
34 #include <errno.h>
35 #include <inttypes.h>
36 #include <stdbool.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include "shl_llog.h"
40 #include "shl_timer.h"
41 #include "tsm_screen.h"
42 #include "tsm_unicode.h"
43
44 #define LLOG_SUBSYSTEM "tsm_screen"
45
46 struct cell {
47         tsm_symbol_t ch;
48         struct tsm_screen_attr attr;
49 };
50
51 struct line {
52         struct line *next;
53         struct line *prev;
54
55         unsigned int size;
56         struct cell *cells;
57 };
58
59 struct tsm_screen {
60         size_t ref;
61         llog_submit_t llog;
62         unsigned int opts;
63         unsigned int flags;
64         struct shl_timer *timer;
65
66         /* default attributes for new cells */
67         struct tsm_screen_attr def_attr;
68
69         /* current buffer */
70         unsigned int size_x;
71         unsigned int size_y;
72         unsigned int margin_top;
73         unsigned int margin_bottom;
74         unsigned int line_num;
75         struct line **lines;
76
77         /* scroll-back buffer */
78         unsigned int sb_count;          /* number of lines in sb */
79         struct line *sb_first;          /* first line; was moved first */
80         struct line *sb_last;           /* last line; was moved last*/
81         unsigned int sb_max;            /* max-limit of lines in sb */
82         struct line *sb_pos;            /* current position in sb or NULL */
83
84         /* cursor */
85         unsigned int cursor_x;
86         unsigned int cursor_y;
87
88         /* tab ruler */
89         bool *tab_ruler;
90 };
91
92 static void cell_init(struct tsm_screen *con, struct cell *cell)
93 {
94         cell->ch = 0;
95         memcpy(&cell->attr, &con->def_attr, sizeof(cell->attr));
96 }
97
98 static int line_new(struct tsm_screen *con, struct line **out,
99                     unsigned int width)
100 {
101         struct line *line;
102         unsigned int i;
103
104         if (!width)
105                 return -EINVAL;
106
107         line = malloc(sizeof(*line));
108         if (!line)
109                 return -ENOMEM;
110         line->next = NULL;
111         line->prev = NULL;
112         line->size = width;
113
114         line->cells = malloc(sizeof(struct cell) * width);
115         if (!line->cells) {
116                 free(line);
117                 return -ENOMEM;
118         }
119
120         for (i = 0; i < width; ++i)
121                 cell_init(con, &line->cells[i]);
122
123         *out = line;
124         return 0;
125 }
126
127 static void line_free(struct line *line)
128 {
129         free(line->cells);
130         free(line);
131 }
132
133 static int line_resize(struct tsm_screen *con, struct line *line,
134                        unsigned int width)
135 {
136         struct cell *tmp;
137
138         if (!line || !width)
139                 return -EINVAL;
140
141         if (line->size < width) {
142                 tmp = realloc(line->cells, width * sizeof(struct cell));
143                 if (!tmp)
144                         return -ENOMEM;
145
146                 line->cells = tmp;
147                 line->size = width;
148
149                 while (line->size < width) {
150                         cell_init(con, &line->cells[line->size]);
151                         ++line->size;
152                 }
153         }
154
155         return 0;
156 }
157
158 /* This links the given line into the scrollback-buffer */
159 static void link_to_scrollback(struct tsm_screen *con, struct line *line)
160 {
161         struct line *tmp;
162
163         if (con->sb_max == 0) {
164                 line_free(line);
165                 return;
166         }
167
168         /* Remove a line from the scrollback buffer if it reaches its maximum.
169          * We must take care to correctly keep the current position as the new
170          * line is linked in after we remove the top-most line here.
171          * sb_max == 0 is tested earlier so we can assume sb_max > 0 here. In
172          * other words, buf->sb_first is a valid line if sb_count >= sb_max. */
173         if (con->sb_count >= con->sb_max) {
174                 tmp = con->sb_first;
175                 con->sb_first = tmp->next;
176                 if (tmp->next)
177                         tmp->next->prev = NULL;
178                 else
179                         con->sb_last = NULL;
180                 --con->sb_count;
181
182                 /* (position == tmp && !next) means we have sb_max=1 so set
183                  * position to the new line. Otherwise, set to new first line.
184                  * If position!=tmp and we have a fixed-position then nothing
185                  * needs to be done because we can stay at the same line. If we
186                  * have no fixed-position, we need to set the position to the
187                  * next inserted line, which can be "line", too. */
188                 if (con->sb_pos) {
189                         if (con->sb_pos == tmp ||
190                             !(con->flags & TSM_SCREEN_FIXED_POS)) {
191                                 if (con->sb_pos->next)
192                                         con->sb_pos = con->sb_pos->next;
193                                 else
194                                         con->sb_pos = line;
195                         }
196                 }
197                 line_free(tmp);
198         }
199
200         line->next = NULL;
201         line->prev = con->sb_last;
202         if (con->sb_last)
203                 con->sb_last->next = line;
204         else
205                 con->sb_first = line;
206         con->sb_last = line;
207         ++con->sb_count;
208 }
209
210 /* Unlinks last line from the scrollback buffer, Returns NULL if it is empty */
211 static struct line *get_from_scrollback(struct tsm_screen *con)
212 {
213         struct line *line;
214
215         if (!con->sb_last)
216                 return NULL;
217
218         line = con->sb_last;
219         con->sb_last = line->prev;
220         if (line->prev)
221                 line->prev->next = NULL;
222         else
223                 con->sb_first = NULL;
224         con->sb_count--;
225
226         /* correctly move the current position if it is set in the sb */
227         if (con->sb_pos) {
228                 if (con->flags & TSM_SCREEN_FIXED_POS ||
229                     !con->sb_pos->prev) {
230                         if (con->sb_pos == line)
231                                 con->sb_pos = NULL;
232                 } else {
233                         con->sb_pos = con->sb_pos->prev;
234                 }
235         }
236
237         line->next = NULL;
238         line->prev = NULL;
239         return line;
240 }
241
242 static void screen_scroll_up(struct tsm_screen *con, unsigned int num)
243 {
244         unsigned int i, j, max;
245         int ret;
246
247         if (!num)
248                 return;
249
250         max = con->margin_bottom + 1 - con->margin_top;
251         if (num > max)
252                 num = max;
253
254         /* We cache lines on the stack to speed up the scrolling. However, if
255          * num is too big we might get overflows here so use recursion if num
256          * exceeds a hard-coded limit.
257          * 128 seems to be a sane limit that should never be reached but should
258          * also be small enough so we do not get stack overflows. */
259         if (num > 128) {
260                 screen_scroll_up(con, 128);
261                 return screen_scroll_up(con, num - 128);
262         }
263         struct line *cache[num];
264
265         for (i = 0; i < num; ++i) {
266                 ret = line_new(con, &cache[i], con->size_x);
267                 if (!ret) {
268                         link_to_scrollback(con,
269                                            con->lines[con->margin_top + i]);
270                 } else {
271                         cache[i] = con->lines[con->margin_top + i];
272                         for (j = 0; j < con->size_x; ++j)
273                                 cell_init(con, &cache[i]->cells[j]);
274                 }
275         }
276
277         if (num < max) {
278                 memmove(&con->lines[con->margin_top],
279                         &con->lines[con->margin_top + num],
280                         (max - num) * sizeof(struct line*));
281         }
282
283         memcpy(&con->lines[con->margin_top + (max - num)],
284                cache, num * sizeof(struct line*));
285 }
286
287 static void screen_scroll_down(struct tsm_screen *con, unsigned int num)
288 {
289         unsigned int i, j, max;
290
291         if (!num)
292                 return;
293
294         max = con->margin_bottom + 1 - con->margin_top;
295         if (num > max)
296                 num = max;
297
298         /* see screen_scroll_up() for an explanation */
299         if (num > 128) {
300                 screen_scroll_down(con, 128);
301                 return screen_scroll_down(con, num - 128);
302         }
303         struct line *cache[num];
304
305         for (i = 0; i < num; ++i) {
306                 cache[i] = con->lines[con->margin_bottom - i];
307                 for (j = 0; j < con->size_x; ++j)
308                         cell_init(con, &cache[i]->cells[j]);
309         }
310
311         if (num < max) {
312                 memmove(&con->lines[con->margin_top + num],
313                         &con->lines[con->margin_top],
314                         (max - num) * sizeof(struct line*));
315         }
316
317         memcpy(&con->lines[con->margin_top],
318                cache, num * sizeof(struct line*));
319 }
320
321 static void screen_write(struct tsm_screen *con, unsigned int x,
322                           unsigned int y, tsm_symbol_t ch,
323                           const struct tsm_screen_attr *attr)
324 {
325         struct line *line;
326
327         if (x >= con->size_x || y >= con->size_y) {
328                 llog_warn(con, "writing beyond buffer boundary");
329                 return;
330         }
331
332         line = con->lines[y];
333
334         if ((con->flags & TSM_SCREEN_INSERT_MODE) && x < (con->size_x - 1))
335                 memmove(&line->cells[x + 1], &line->cells[x],
336                         sizeof(struct cell) * (con->size_x - 1 - x));
337         line->cells[x].ch = ch;
338         memcpy(&line->cells[x].attr, attr, sizeof(*attr));
339 }
340
341 static void screen_erase_region(struct tsm_screen *con,
342                                  unsigned int x_from,
343                                  unsigned int y_from,
344                                  unsigned int x_to,
345                                  unsigned int y_to,
346                                  bool protect)
347 {
348         unsigned int to;
349         struct line *line;
350
351         if (y_to >= con->size_y)
352                 y_to = con->size_y - 1;
353         if (x_to >= con->size_x)
354                 x_to = con->size_x - 1;
355
356         for ( ; y_from <= y_to; ++y_from) {
357                 line = con->lines[y_from];
358                 if (!line) {
359                         x_from = 0;
360                         continue;
361                 }
362
363                 if (y_from == y_to)
364                         to = x_to;
365                 else
366                         to = con->size_x - 1;
367                 for ( ; x_from <= to; ++x_from) {
368                         if (protect && line->cells[x_from].attr.protect)
369                                 continue;
370
371                         cell_init(con, &line->cells[x_from]);
372                 }
373                 x_from = 0;
374         }
375 }
376
377 static inline unsigned int to_abs_x(struct tsm_screen *con, unsigned int x)
378 {
379         return x;
380 }
381
382 static inline unsigned int to_abs_y(struct tsm_screen *con, unsigned int y)
383 {
384         if (!(con->flags & TSM_SCREEN_REL_ORIGIN))
385                 return y;
386
387         return con->margin_top + y;
388 }
389
390 int tsm_screen_new(struct tsm_screen **out, tsm_log_t log)
391 {
392         struct tsm_screen *con;
393         int ret;
394         unsigned int i;
395
396         if (!out)
397                 return -EINVAL;
398
399         con = malloc(sizeof(*con));
400         if (!con)
401                 return -ENOMEM;
402
403         memset(con, 0, sizeof(*con));
404         con->ref = 1;
405         con->llog = log;
406         con->def_attr.fr = 255;
407         con->def_attr.fg = 255;
408         con->def_attr.fb = 255;
409
410         ret = shl_timer_new(&con->timer);
411         if (ret)
412                 goto err_free;
413
414         ret = tsm_screen_resize(con, 80, 24);
415         if (ret)
416                 goto err_timer;
417
418         llog_debug(con, "new screen");
419         *out = con;
420
421         return 0;
422
423 err_timer:
424         shl_timer_free(con->timer);
425         for (i = 0; i < con->line_num; ++i)
426                 line_free(con->lines[i]);
427         free(con->lines);
428         free(con->tab_ruler);
429 err_free:
430         free(con);
431         return ret;
432 }
433
434 void tsm_screen_ref(struct tsm_screen *con)
435 {
436         if (!con)
437                 return;
438
439         ++con->ref;
440 }
441
442 void tsm_screen_unref(struct tsm_screen *con)
443 {
444         unsigned int i;
445
446         if (!con || !con->ref || --con->ref)
447                 return;
448
449         llog_debug(con, "destroying screen");
450
451         for (i = 0; i < con->line_num; ++i)
452                 line_free(con->lines[i]);
453         free(con->lines);
454         free(con->tab_ruler);
455         shl_timer_free(con->timer);
456         free(con);
457 }
458
459 void tsm_screen_set_opts(struct tsm_screen *scr, unsigned int opts)
460 {
461         if (!scr || !opts)
462                 return;
463
464         scr->opts |= opts;
465 }
466
467 void tsm_screen_reset_opts(struct tsm_screen *scr, unsigned int opts)
468 {
469         if (!scr || !opts)
470                 return;
471
472         scr->opts &= ~opts;
473 }
474
475 unsigned int tsm_screen_get_opts(struct tsm_screen *scr)
476 {
477         if (!scr)
478                 return 0;
479
480         return scr->opts;
481 }
482
483 unsigned int tsm_screen_get_width(struct tsm_screen *con)
484 {
485         if (!con)
486                 return 0;
487
488         return con->size_x;
489 }
490
491 unsigned int tsm_screen_get_height(struct tsm_screen *con)
492 {
493         if (!con)
494                 return 0;
495
496         return con->size_y;
497 }
498
499 int tsm_screen_resize(struct tsm_screen *con, unsigned int x,
500                           unsigned int y)
501 {
502         struct line **cache;
503         unsigned int i, j, width, diff;
504         int ret;
505         bool *tab_ruler;
506
507         if (!con || !x || !y)
508                 return -EINVAL;
509
510         if (con->size_x == x && con->size_y == y)
511                 return 0;
512
513         /* First make sure the line buffer is big enough for our new screen.
514          * That is, allocate all new lines and make sure each line has enough
515          * cells to hold the new screen or the current screen. If we fail, we
516          * can safely return -ENOMEM and the buffer is still valid. We must
517          * allocate the new lines to at least the same size as the current
518          * lines. Otherwise, if this function fails in later turns, we will have
519          * invalid lines in the buffer. */
520         if (y > con->line_num) {
521                 cache = realloc(con->lines, sizeof(struct line*) * y);
522                 if (!cache)
523                         return -ENOMEM;
524
525                 con->lines = cache;
526                 if (x > con->size_x)
527                         width = x;
528                 else
529                         width = con->size_x;
530
531                 while (con->line_num < y) {
532                         ret = line_new(con, &cache[con->line_num], width);
533                         if (ret)
534                                 return ret;
535                         ++con->line_num;
536                 }
537         }
538
539         /* Resize all lines in the buffer if we increase screen width. This
540          * will guarantee that all lines are big enough so we can resize the
541          * buffer without reallocating them later. */
542         if (x > con->size_x) {
543                 tab_ruler = realloc(con->tab_ruler, sizeof(bool) * x);
544                 if (!tab_ruler)
545                         return -ENOMEM;
546                 con->tab_ruler = tab_ruler;
547
548                 for (i = 0; i < con->line_num; ++i) {
549                         ret = line_resize(con, con->lines[i], x);
550                         if (ret)
551                                 return ret;
552                 }
553         }
554
555         /* When resizing, we need to reset all the new cells, otherwise, the old
556          * data that was written there will reoccur on the screen.
557          * TODO: we overwrite way to much here; most of it should already be
558          * cleaned. Maybe it does more sense cleaning when _allocating_ or when
559          * _shrinking_, then we never clean twice (for performance reasons). */
560         for (j = 0; j < con->line_num; ++j) {
561                 if (j >= con->size_y)
562                         i = 0;
563                 else
564                         i = con->size_x;
565                 if (x < con->lines[j]->size)
566                         width = x;
567                 else
568                         width = con->lines[j]->size;
569                 for (; i < width; ++i)
570                         cell_init(con, &con->lines[j]->cells[i]);
571         }
572
573         /* xterm destroys margins on resize, so do we */
574         con->margin_top = 0;
575         con->margin_bottom = con->size_y - 1;
576
577         /* reset tabs */
578         for (i = 0; i < x; ++i) {
579                 if (i % 8 == 0)
580                         con->tab_ruler[i] = true;
581                 else
582                         con->tab_ruler[i] = false;
583         }
584
585         /* We need to adjust x-size first as screen_scroll_up() and friends may
586          * have to reallocate lines. The y-size is adjusted after them to avoid
587          * missing lines when shrinking y-size.
588          * We need to carefully look for the functions that we call here as they
589          * have stronger invariants as when called normally. */
590
591         con->size_x = x;
592         if (con->cursor_x >= con->size_x)
593                 con->cursor_x = con->size_x - 1;
594
595         /* scroll buffer if screen height shrinks */
596         if (con->size_y != 0 && y < con->size_y) {
597                 diff = con->size_y - y;
598                 screen_scroll_up(con, diff);
599                 if (con->cursor_y > diff)
600                         con->cursor_y -= diff;
601                 else
602                         con->cursor_y = 0;
603         }
604
605         con->size_y = y;
606         con->margin_bottom = con->size_y - 1;
607         if (con->cursor_y >= con->size_y)
608                 con->cursor_y = con->size_y - 1;
609
610         return 0;
611 }
612
613 int tsm_screen_set_margins(struct tsm_screen *con,
614                                unsigned int top, unsigned int bottom)
615 {
616         unsigned int upper, lower;
617
618         if (!con)
619                 return -EINVAL;
620
621         if (!top)
622                 top = 1;
623
624         if (bottom <= top) {
625                 upper = 0;
626                 lower = con->size_y - 1;
627         } else if (bottom > con->size_y) {
628                 upper = 0;
629                 lower = con->size_y - 1;
630         } else {
631                 upper = top - 1;
632                 lower = bottom - 1;
633         }
634
635         con->margin_top = upper;
636         con->margin_bottom = lower;
637         return 0;
638 }
639
640 /* set maximum scrollback buffer size */
641 void tsm_screen_set_max_sb(struct tsm_screen *con,
642                                unsigned int max)
643 {
644         struct line *line;
645
646         if (!con)
647                 return;
648
649         while (con->sb_count > max) {
650                 line = con->sb_first;
651                 con->sb_first = line->next;
652                 if (line->next)
653                         line->next->prev = NULL;
654                 else
655                         con->sb_last = NULL;
656                 con->sb_count--;
657
658                 /* We treat fixed/unfixed position the same here because we
659                  * remove lines from the TOP of the scrollback buffer. */
660                 if (con->sb_pos == line)
661                         con->sb_pos = con->sb_first;
662
663                 line_free(line);
664         }
665
666         con->sb_max = max;
667 }
668
669 /* clear scrollback buffer */
670 void tsm_screen_clear_sb(struct tsm_screen *con)
671 {
672         struct line *iter, *tmp;
673
674         if (!con)
675                 return;
676
677         for (iter = con->sb_first; iter; ) {
678                 tmp = iter;
679                 iter = iter->next;
680                 line_free(tmp);
681         }
682
683         con->sb_first = NULL;
684         con->sb_last = NULL;
685         con->sb_count = 0;
686         con->sb_pos = NULL;
687 }
688
689 void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num)
690 {
691         if (!con || !num)
692                 return;
693
694         while (num--) {
695                 if (con->sb_pos) {
696                         if (!con->sb_pos->prev)
697                                 return;
698
699                         con->sb_pos = con->sb_pos->prev;
700                 } else if (!con->sb_last) {
701                         return;
702                 } else {
703                         con->sb_pos = con->sb_last;
704                 }
705         }
706 }
707
708 void tsm_screen_sb_down(struct tsm_screen *con, unsigned int num)
709 {
710         if (!con || !num)
711                 return;
712
713         while (num--) {
714                 if (con->sb_pos) {
715                         con->sb_pos = con->sb_pos->next;
716                         if (!con->sb_pos)
717                                 return;
718                 } else {
719                         return;
720                 }
721         }
722 }
723
724 void tsm_screen_sb_page_up(struct tsm_screen *con, unsigned int num)
725 {
726         if (!con || !num)
727                 return;
728
729         tsm_screen_sb_up(con, num * con->size_y);
730 }
731
732 void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num)
733 {
734         if (!con || !num)
735                 return;
736
737         tsm_screen_sb_down(con, num * con->size_y);
738 }
739
740 void tsm_screen_sb_reset(struct tsm_screen *con)
741 {
742         if (!con)
743                 return;
744
745         con->sb_pos = NULL;
746 }
747
748 void tsm_screen_set_def_attr(struct tsm_screen *con,
749                                  const struct tsm_screen_attr *attr)
750 {
751         if (!con || !attr)
752                 return;
753
754         memcpy(&con->def_attr, attr, sizeof(*attr));
755 }
756
757 void tsm_screen_reset(struct tsm_screen *con)
758 {
759         unsigned int i;
760
761         if (!con)
762                 return;
763
764         con->flags = 0;
765         con->margin_top = 0;
766         con->margin_bottom = con->size_y - 1;
767
768         for (i = 0; i < con->size_x; ++i) {
769                 if (i % 8 == 0)
770                         con->tab_ruler[i] = true;
771                 else
772                         con->tab_ruler[i] = false;
773         }
774 }
775
776 void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags)
777 {
778         if (!con || !flags)
779                 return;
780
781         con->flags |= flags;
782 }
783
784 void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags)
785 {
786         if (!con || !flags)
787                 return;
788
789         con->flags &= ~flags;
790 }
791
792 unsigned int tsm_screen_get_flags(struct tsm_screen *con)
793 {
794         if (!con)
795                 return 0;
796
797         return con->flags;
798 }
799
800 unsigned int tsm_screen_get_cursor_x(struct tsm_screen *con)
801 {
802         if (!con)
803                 return 0;
804
805         return con->cursor_x;
806 }
807
808 unsigned int tsm_screen_get_cursor_y(struct tsm_screen *con)
809 {
810         if (!con)
811                 return 0;
812
813         return con->cursor_y;
814 }
815
816 void tsm_screen_set_tabstop(struct tsm_screen *con)
817 {
818         if (!con || con->cursor_x >= con->size_x)
819                 return;
820
821         con->tab_ruler[con->cursor_x] = true;
822 }
823
824 void tsm_screen_reset_tabstop(struct tsm_screen *con)
825 {
826         if (!con || con->cursor_x >= con->size_x)
827                 return;
828
829         con->tab_ruler[con->cursor_x] = false;
830 }
831
832 void tsm_screen_reset_all_tabstops(struct tsm_screen *con)
833 {
834         unsigned int i;
835
836         if (!con)
837                 return;
838
839         for (i = 0; i < con->size_x; ++i)
840                 con->tab_ruler[i] = false;
841 }
842
843 void tsm_screen_write(struct tsm_screen *con, tsm_symbol_t ch,
844                           const struct tsm_screen_attr *attr)
845 {
846         unsigned int last;
847
848         if (!con)
849                 return;
850
851         if (con->cursor_y <= con->margin_bottom ||
852             con->cursor_y >= con->size_y)
853                 last = con->margin_bottom;
854         else
855                 last = con->size_y - 1;
856
857         if (con->cursor_x >= con->size_x) {
858                 if (con->flags & TSM_SCREEN_AUTO_WRAP) {
859                         con->cursor_x = 0;
860                         ++con->cursor_y;
861                 } else {
862                         con->cursor_x = con->size_x - 1;
863                 }
864         }
865
866         if (con->cursor_y > last) {
867                 con->cursor_y = last;
868                 screen_scroll_up(con, 1);
869         }
870
871         screen_write(con, con->cursor_x, con->cursor_y, ch, attr);
872         ++con->cursor_x;
873 }
874
875 void tsm_screen_newline(struct tsm_screen *con)
876 {
877         if (!con)
878                 return;
879
880         tsm_screen_move_down(con, 1, true);
881         tsm_screen_move_line_home(con);
882 }
883
884 void tsm_screen_scroll_up(struct tsm_screen *con, unsigned int num)
885 {
886         if (!con || !num)
887                 return;
888
889         screen_scroll_up(con, num);
890 }
891
892 void tsm_screen_scroll_down(struct tsm_screen *con, unsigned int num)
893 {
894         if (!con || !num)
895                 return;
896
897         screen_scroll_down(con, num);
898 }
899
900 void tsm_screen_move_to(struct tsm_screen *con, unsigned int x,
901                             unsigned int y)
902 {
903         unsigned int last;
904
905         if (!con)
906                 return;
907
908         if (con->flags & TSM_SCREEN_REL_ORIGIN)
909                 last = con->margin_bottom;
910         else
911                 last = con->size_y - 1;
912
913         con->cursor_x = to_abs_x(con, x);
914         if (con->cursor_x >= con->size_x)
915                 con->cursor_x = con->size_x - 1;
916
917         con->cursor_y = to_abs_y(con, y);
918         if (con->cursor_y > last)
919                 con->cursor_y = last;
920 }
921
922 void tsm_screen_move_up(struct tsm_screen *con, unsigned int num,
923                             bool scroll)
924 {
925         unsigned int diff, size;
926
927         if (!con || !num)
928                 return;
929
930         if (con->cursor_y >= con->margin_top)
931                 size = con->margin_top;
932         else
933                 size = 0;
934
935         diff = con->cursor_y - size;
936         if (num > diff) {
937                 num -= diff;
938                 if (scroll)
939                         screen_scroll_down(con, num);
940                 con->cursor_y = size;
941         } else {
942                 con->cursor_y -= num;
943         }
944 }
945
946 void tsm_screen_move_down(struct tsm_screen *con, unsigned int num,
947                               bool scroll)
948 {
949         unsigned int diff, size;
950
951         if (!con || !num)
952                 return;
953
954         if (con->cursor_y <= con->margin_bottom)
955                 size = con->margin_bottom + 1;
956         else
957                 size = con->size_y;
958
959         diff = size - con->cursor_y - 1;
960         if (num > diff) {
961                 num -= diff;
962                 if (scroll)
963                         screen_scroll_up(con, num);
964                 con->cursor_y = size - 1;
965         } else {
966                 con->cursor_y += num;
967         }
968 }
969
970 void tsm_screen_move_left(struct tsm_screen *con, unsigned int num)
971 {
972         if (!con || !num)
973                 return;
974
975         if (num > con->size_x)
976                 num = con->size_x;
977
978         if (con->cursor_x >= con->size_x)
979                 con->cursor_x = con->size_x - 1;
980
981         if (num > con->cursor_x)
982                 con->cursor_x = 0;
983         else
984                 con->cursor_x -= num;
985 }
986
987 void tsm_screen_move_right(struct tsm_screen *con, unsigned int num)
988 {
989         if (!con || !num)
990                 return;
991
992         if (num > con->size_x)
993                 num = con->size_x;
994
995         if (num + con->cursor_x >= con->size_x)
996                 con->cursor_x = con->size_x - 1;
997         else
998                 con->cursor_x += num;
999 }
1000
1001 void tsm_screen_move_line_end(struct tsm_screen *con)
1002 {
1003         if (!con)
1004                 return;
1005
1006         con->cursor_x = con->size_x - 1;
1007 }
1008
1009 void tsm_screen_move_line_home(struct tsm_screen *con)
1010 {
1011         if (!con)
1012                 return;
1013
1014         con->cursor_x = 0;
1015 }
1016
1017 void tsm_screen_tab_right(struct tsm_screen *con, unsigned int num)
1018 {
1019         unsigned int i, j;
1020
1021         if (!con || !num)
1022                 return;
1023
1024         for (i = 0; i < num; ++i) {
1025                 for (j = con->cursor_x + 1; j < con->size_x; ++j) {
1026                         if (con->tab_ruler[j])
1027                                 break;
1028                 }
1029
1030                 con->cursor_x = j;
1031                 if (con->cursor_x + 1 >= con->size_x)
1032                         break;
1033         }
1034
1035         /* tabs never cause pending new-lines */
1036         if (con->cursor_x >= con->size_x)
1037                 con->cursor_x = con->size_x - 1;
1038 }
1039
1040 void tsm_screen_tab_left(struct tsm_screen *con, unsigned int num)
1041 {
1042         unsigned int i;
1043         int j;
1044
1045         if (!con || !num)
1046                 return;
1047
1048         for (i = 0; i < num; ++i) {
1049                 for (j = con->cursor_x - 1; j > 0; --j) {
1050                         if (con->tab_ruler[j])
1051                                 break;
1052                 }
1053
1054                 if (j <= 0) {
1055                         con->cursor_x = 0;
1056                         break;
1057                 }
1058                 con->cursor_x = j;
1059         }
1060 }
1061
1062 void tsm_screen_insert_lines(struct tsm_screen *con, unsigned int num)
1063 {
1064         unsigned int i, j, max;
1065
1066         if (!con || !num)
1067                 return;
1068
1069         if (con->cursor_y < con->margin_top ||
1070             con->cursor_y > con->margin_bottom)
1071                 return;
1072
1073         max = con->margin_bottom - con->cursor_y + 1;
1074         if (num > max)
1075                 num = max;
1076
1077         struct line *cache[num];
1078
1079         for (i = 0; i < num; ++i) {
1080                 cache[i] = con->lines[con->margin_bottom - i];
1081                 for (j = 0; j < con->size_x; ++j)
1082                         cell_init(con, &cache[i]->cells[j]);
1083         }
1084
1085         if (num < max) {
1086                 memmove(&con->lines[con->cursor_y + num],
1087                         &con->lines[con->cursor_y],
1088                         (max - num) * sizeof(struct line*));
1089
1090                 memcpy(&con->lines[con->cursor_y],
1091                        cache, num * sizeof(struct line*));
1092         }
1093
1094         con->cursor_x = 0;
1095 }
1096
1097 void tsm_screen_delete_lines(struct tsm_screen *con, unsigned int num)
1098 {
1099         unsigned int i, j, max;
1100
1101         if (!con || !num)
1102                 return;
1103
1104         if (con->cursor_y < con->margin_top ||
1105             con->cursor_y > con->margin_bottom)
1106                 return;
1107
1108         max = con->margin_bottom - con->cursor_y + 1;
1109         if (num > max)
1110                 num = max;
1111
1112         struct line *cache[num];
1113
1114         for (i = 0; i < num; ++i) {
1115                 cache[i] = con->lines[con->cursor_y + i];
1116                 for (j = 0; j < con->size_x; ++j)
1117                         cell_init(con, &cache[i]->cells[j]);
1118         }
1119
1120         if (num < max) {
1121                 memmove(&con->lines[con->cursor_y],
1122                         &con->lines[con->cursor_y + num],
1123                         (max - num) * sizeof(struct line*));
1124
1125                 memcpy(&con->lines[con->cursor_y + (max - num)],
1126                        cache, num * sizeof(struct line*));
1127         }
1128
1129         con->cursor_x = 0;
1130 }
1131
1132 void tsm_screen_insert_chars(struct tsm_screen *con, unsigned int num)
1133 {
1134         struct cell *cells;
1135         unsigned int max, mv, i;
1136
1137         if (!con || !num || !con->size_y || !con->size_x)
1138                 return;
1139
1140         if (con->cursor_x >= con->size_x)
1141                 con->cursor_x = con->size_x - 1;
1142         if (con->cursor_y >= con->size_y)
1143                 con->cursor_y = con->size_y - 1;
1144
1145         max = con->size_x - con->cursor_x;
1146         if (num > max)
1147                 num = max;
1148         mv = max - num;
1149
1150         cells = con->lines[con->cursor_y]->cells;
1151         if (mv)
1152                 memmove(&cells[con->cursor_x + num],
1153                         &cells[con->cursor_x],
1154                         mv * sizeof(*cells));
1155
1156         for (i = 0; i < num; ++i) {
1157                 cell_init(con, &cells[con->cursor_x + i]);
1158         }
1159 }
1160
1161 void tsm_screen_delete_chars(struct tsm_screen *con, unsigned int num)
1162 {
1163         struct cell *cells;
1164         unsigned int max, mv, i;
1165
1166         if (!con || !num || !con->size_y || !con->size_x)
1167                 return;
1168
1169         if (con->cursor_x >= con->size_x)
1170                 con->cursor_x = con->size_x - 1;
1171         if (con->cursor_y >= con->size_y)
1172                 con->cursor_y = con->size_y - 1;
1173
1174         max = con->size_x - con->cursor_x;
1175         if (num > max)
1176                 num = max;
1177         mv = max - num;
1178
1179         cells = con->lines[con->cursor_y]->cells;
1180         if (mv)
1181                 memmove(&cells[con->cursor_x],
1182                         &cells[con->cursor_x + num],
1183                         mv * sizeof(*cells));
1184
1185         for (i = 0; i < num; ++i) {
1186                 cell_init(con, &cells[con->cursor_x + mv + i]);
1187         }
1188 }
1189
1190 void tsm_screen_erase_cursor(struct tsm_screen *con)
1191 {
1192         unsigned int x;
1193
1194         if (!con)
1195                 return;
1196
1197         if (con->cursor_x >= con->size_x)
1198                 x = con->size_x - 1;
1199         else
1200                 x = con->cursor_x;
1201
1202         screen_erase_region(con, x, con->cursor_y, x, con->cursor_y, false);
1203 }
1204
1205 void tsm_screen_erase_chars(struct tsm_screen *con, unsigned int num)
1206 {
1207         unsigned int x;
1208
1209         if (!con || !num)
1210                 return;
1211
1212         if (con->cursor_x >= con->size_x)
1213                 x = con->size_x - 1;
1214         else
1215                 x = con->cursor_x;
1216
1217         screen_erase_region(con, x, con->cursor_y, x + num - 1, con->cursor_y,
1218                              false);
1219 }
1220
1221 void tsm_screen_erase_cursor_to_end(struct tsm_screen *con,
1222                                         bool protect)
1223 {
1224         unsigned int x;
1225
1226         if (!con)
1227                 return;
1228
1229         if (con->cursor_x >= con->size_x)
1230                 x = con->size_x - 1;
1231         else
1232                 x = con->cursor_x;
1233
1234         screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1235                              con->cursor_y, protect);
1236 }
1237
1238 void tsm_screen_erase_home_to_cursor(struct tsm_screen *con,
1239                                          bool protect)
1240 {
1241         if (!con)
1242                 return;
1243
1244         screen_erase_region(con, 0, con->cursor_y, con->cursor_x,
1245                              con->cursor_y, protect);
1246 }
1247
1248 void tsm_screen_erase_current_line(struct tsm_screen *con,
1249                                        bool protect)
1250 {
1251         if (!con)
1252                 return;
1253
1254         screen_erase_region(con, 0, con->cursor_y, con->size_x - 1,
1255                              con->cursor_y, protect);
1256 }
1257
1258 void tsm_screen_erase_screen_to_cursor(struct tsm_screen *con,
1259                                            bool protect)
1260 {
1261         if (!con)
1262                 return;
1263
1264         screen_erase_region(con, 0, 0, con->cursor_x, con->cursor_y, protect);
1265 }
1266
1267 void tsm_screen_erase_cursor_to_screen(struct tsm_screen *con,
1268                                            bool protect)
1269 {
1270         unsigned int x;
1271
1272         if (!con)
1273                 return;
1274
1275         if (con->cursor_x >= con->size_x)
1276                 x = con->size_x - 1;
1277         else
1278                 x = con->cursor_x;
1279
1280         screen_erase_region(con, x, con->cursor_y, con->size_x - 1,
1281                              con->size_y - 1, protect);
1282 }
1283
1284 void tsm_screen_erase_screen(struct tsm_screen *con, bool protect)
1285 {
1286         if (!con)
1287                 return;
1288
1289         screen_erase_region(con, 0, 0, con->size_x - 1, con->size_y - 1,
1290                              protect);
1291 }
1292
1293 void tsm_screen_draw(struct tsm_screen *con,
1294                          tsm_screen_prepare_cb prepare_cb,
1295                          tsm_screen_draw_cb draw_cb,
1296                          tsm_screen_render_cb render_cb,
1297                          void *data)
1298 {
1299         unsigned int cur_x, cur_y;
1300         unsigned int i, j, k;
1301         struct line *iter, *line = NULL;
1302         struct cell *cell;
1303         struct tsm_screen_attr attr;
1304         bool cursor_done = false;
1305         int ret, warned = 0;
1306         uint64_t time_prep = 0, time_draw = 0, time_rend = 0;
1307         const uint32_t *ch;
1308         size_t len;
1309         struct cell empty;
1310
1311         if (!con || !draw_cb)
1312                 return;
1313
1314         cell_init(con, &empty);
1315
1316         cur_x = con->cursor_x;
1317         if (con->cursor_x >= con->size_x)
1318                 cur_x = con->size_x - 1;
1319         cur_y = con->cursor_y;
1320         if (con->cursor_y >= con->size_y)
1321                 cur_y = con->size_y - 1;
1322
1323         /* render preparation */
1324
1325         if (prepare_cb) {
1326                 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1327                         shl_timer_reset(con->timer);
1328
1329                 ret = prepare_cb(con, data);
1330                 if (ret) {
1331                         llog_warning(con,
1332                                      "cannot prepare text-renderer for rendering");
1333                         return;
1334                 }
1335
1336                 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1337                         time_prep = shl_timer_elapsed(con->timer);
1338         } else {
1339                 time_prep = 0;
1340         }
1341
1342         /* push each character into rendering pipeline */
1343
1344         if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1345                 shl_timer_reset(con->timer);
1346
1347         iter = con->sb_pos;
1348         k = 0;
1349         for (i = 0; i < con->size_y; ++i) {
1350                 if (iter) {
1351                         line = iter;
1352                         iter = iter->next;
1353                 } else {
1354                         line = con->lines[k];
1355                         k++;
1356                 }
1357
1358                 for (j = 0; j < con->size_x; ++j) {
1359                         if (j < line->size)
1360                                 cell = &line->cells[j];
1361                         else
1362                                 cell = &empty;
1363                         memcpy(&attr, &cell->attr, sizeof(attr));
1364
1365                         if (k == cur_y + 1 &&
1366                             j == cur_x) {
1367                                 cursor_done = true;
1368                                 if (!(con->flags & TSM_SCREEN_HIDE_CURSOR))
1369                                         attr.inverse = !attr.inverse;
1370                         }
1371
1372                         /* TODO: do some more sophisticated inverse here. When
1373                          * INVERSE mode is set, we should instead just select
1374                          * inverse colors instead of switching background and
1375                          * foreground */
1376                         if (con->flags & TSM_SCREEN_INVERSE)
1377                                 attr.inverse = !attr.inverse;
1378
1379                         ch = tsm_symbol_get(NULL, &cell->ch, &len);
1380                         if (cell->ch == ' ' || cell->ch == 0)
1381                                 len = 0;
1382                         ret = draw_cb(con, cell->ch, ch, len, j, i, &attr,
1383                                       data);
1384                         if (ret && warned++ < 3) {
1385                                 llog_debug(con,
1386                                            "cannot draw glyph at %ux%u via text-renderer",
1387                                            j, i);
1388                                 if (warned == 3)
1389                                         llog_debug(con,
1390                                                    "suppressing further warnings during this rendering round");
1391                         }
1392                 }
1393
1394                 if (k == cur_y + 1 && !cursor_done) {
1395                         cursor_done = true;
1396                         if (!(con->flags & TSM_SCREEN_HIDE_CURSOR)) {
1397                                 if (!(con->flags & TSM_SCREEN_INVERSE))
1398                                         attr.inverse = !attr.inverse;
1399                                 draw_cb(con, 0, NULL, 0, cur_x, i, &attr, data);
1400                         }
1401                 }
1402         }
1403
1404         if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1405                 time_draw = shl_timer_elapsed(con->timer);
1406
1407         /* perform final rendering steps */
1408
1409         if (render_cb) {
1410                 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1411                         shl_timer_reset(con->timer);
1412
1413                 ret = render_cb(con, data);
1414                 if (ret)
1415                         llog_warning(con,
1416                                      "cannot render via text-renderer");
1417
1418                 if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1419                         time_rend = shl_timer_elapsed(con->timer);
1420         } else {
1421                 time_rend = 0;
1422         }
1423
1424         if (con->opts & TSM_SCREEN_OPT_RENDER_TIMING)
1425                 llog_debug(con,
1426                            "timing: sum: %" PRIu64 " prepare: %" PRIu64 " draw: %" PRIu64 " render: %" PRIu64,
1427                            time_prep + time_draw + time_rend,
1428                            time_prep, time_draw, time_rend);
1429 }