xref: /haiku/src/apps/serialconnect/libvterm/src/screen.c (revision 6a545a8eb1e3dd624c7a473fe9965a4c08e5a54f)
1 #include "vterm_internal.h"
2 
3 #include <stdio.h>
4 #include <string.h>
5 
6 #include "rect.h"
7 #include "utf8.h"
8 
9 #define UNICODE_SPACE 0x20
10 #define UNICODE_LINEFEED 0x0a
11 
12 /* State of the pen at some moment in time, also used in a cell */
13 typedef struct
14 {
15   /* After the bitfield */
16   VTermColor   fg, bg;
17 
18   unsigned int bold      : 1;
19   unsigned int underline : 2;
20   unsigned int italic    : 1;
21   unsigned int blink     : 1;
22   unsigned int reverse   : 1;
23   unsigned int strike    : 1;
24   unsigned int font      : 4; /* 0 to 9 */
25 
26   /* Extra state storage that isn't strictly pen-related */
27   unsigned int protected_cell : 1;
28   unsigned int dwl            : 1; /* on a DECDWL or DECDHL line */
29   unsigned int dhl            : 2; /* on a DECDHL line (1=top 2=bottom) */
30 } ScreenPen;
31 
32 /* Internal representation of a screen cell */
33 typedef struct
34 {
35   uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
36   ScreenPen pen;
37 } ScreenCell;
38 
39 static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell);
40 
41 struct VTermScreen
42 {
43   VTerm *vt;
44   VTermState *state;
45 
46   const VTermScreenCallbacks *callbacks;
47   void *cbdata;
48 
49   VTermDamageSize damage_merge;
50   /* start_row == -1 => no damage */
51   VTermRect damaged;
52   VTermRect pending_scrollrect;
53   int pending_scroll_downward, pending_scroll_rightward;
54 
55   int rows;
56   int cols;
57   int global_reverse;
58 
59   /* Primary and Altscreen. buffers[1] is lazily allocated as needed */
60   ScreenCell *buffers[2];
61 
62   /* buffer will == buffers[0] or buffers[1], depending on altscreen */
63   ScreenCell *buffer;
64 
65   /* buffer for a single screen row used in scrollback storage callbacks */
66   VTermScreenCell *sb_buffer;
67 
68   ScreenPen pen;
69 };
70 
getcell(const VTermScreen * screen,int row,int col)71 static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col)
72 {
73   if(row < 0 || row >= screen->rows)
74     return NULL;
75   if(col < 0 || col >= screen->cols)
76     return NULL;
77   return screen->buffer + (screen->cols * row) + col;
78 }
79 
realloc_buffer(VTermScreen * screen,ScreenCell * buffer,int new_rows,int new_cols)80 static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols)
81 {
82   ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
83   int row, col;
84 
85   for(row = 0; row < new_rows; row++) {
86     for(col = 0; col < new_cols; col++) {
87       ScreenCell *new_cell = new_buffer + row*new_cols + col;
88 
89       if(buffer && row < screen->rows && col < screen->cols)
90         *new_cell = buffer[row * screen->cols + col];
91       else {
92         new_cell->chars[0] = 0;
93         new_cell->pen = screen->pen;
94       }
95     }
96   }
97 
98   if(buffer)
99     vterm_allocator_free(screen->vt, buffer);
100 
101   return new_buffer;
102 }
103 
damagerect(VTermScreen * screen,VTermRect rect)104 static void damagerect(VTermScreen *screen, VTermRect rect)
105 {
106   VTermRect emit;
107 
108   switch(screen->damage_merge) {
109   case VTERM_DAMAGE_CELL:
110     /* Always emit damage event */
111     emit = rect;
112     break;
113 
114   case VTERM_DAMAGE_ROW:
115     /* Emit damage longer than one row. Try to merge with existing damage in
116      * the same row */
117     if(rect.end_row > rect.start_row + 1) {
118       // Bigger than 1 line - flush existing, emit this
119       vterm_screen_flush_damage(screen);
120       emit = rect;
121     }
122     else if(screen->damaged.start_row == -1) {
123       // None stored yet
124       screen->damaged = rect;
125       return;
126     }
127     else if(rect.start_row == screen->damaged.start_row) {
128       // Merge with the stored line
129       if(screen->damaged.start_col > rect.start_col)
130         screen->damaged.start_col = rect.start_col;
131       if(screen->damaged.end_col < rect.end_col)
132         screen->damaged.end_col = rect.end_col;
133       return;
134     }
135     else {
136       // Emit the currently stored line, store a new one
137       emit = screen->damaged;
138       screen->damaged = rect;
139     }
140     break;
141 
142   case VTERM_DAMAGE_SCREEN:
143   case VTERM_DAMAGE_SCROLL:
144     /* Never emit damage event */
145     if(screen->damaged.start_row == -1)
146       screen->damaged = rect;
147     else {
148       rect_expand(&screen->damaged, &rect);
149     }
150     return;
151 
152   default:
153     fprintf(stderr, "TODO: Maybe merge damage for level %d\n", screen->damage_merge);
154     return;
155   }
156 
157   if(screen->callbacks && screen->callbacks->damage)
158     (*screen->callbacks->damage)(emit, screen->cbdata);
159 }
160 
damagescreen(VTermScreen * screen)161 static void damagescreen(VTermScreen *screen)
162 {
163   VTermRect rect = {
164     .start_row = 0,
165     .end_row   = screen->rows,
166     .start_col = 0,
167     .end_col   = screen->cols,
168   };
169 
170   damagerect(screen, rect);
171 }
172 
putglyph(VTermGlyphInfo * info,VTermPos pos,void * user)173 static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
174 {
175   VTermScreen *screen = user;
176   ScreenCell *cell = getcell(screen, pos.row, pos.col);
177   int i, col;
178   VTermRect rect;
179 
180   if(!cell)
181     return 0;
182 
183   for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
184     cell->chars[i] = info->chars[i];
185     cell->pen = screen->pen;
186   }
187   if(i < VTERM_MAX_CHARS_PER_CELL)
188     cell->chars[i] = 0;
189 
190   for(col = 1; col < info->width; col++)
191     getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
192 
193   rect.start_row = pos.row;
194   rect.end_row   = pos.row+1;
195   rect.start_col = pos.col;
196   rect.end_col   = pos.col+info->width;
197 
198   cell->pen.protected_cell = info->protected_cell;
199   cell->pen.dwl            = info->dwl;
200   cell->pen.dhl            = info->dhl;
201 
202   damagerect(screen, rect);
203 
204   return 1;
205 }
206 
moverect_internal(VTermRect dest,VTermRect src,void * user)207 static int moverect_internal(VTermRect dest, VTermRect src, void *user)
208 {
209   VTermScreen *screen = user;
210   int cols, downward, row;
211   int init_row, test_row, inc_row;
212 
213   if(screen->callbacks && screen->callbacks->sb_pushline &&
214      dest.start_row == 0 && dest.start_col == 0 &&  // starts top-left corner
215      dest.end_col == screen->cols &&                // full width
216      screen->buffer == screen->buffers[0]) {        // not altscreen
217     VTermPos pos;
218     for(pos.row = 0; pos.row < src.start_row; pos.row++) {
219       for(pos.col = 0; pos.col < screen->cols; pos.col++)
220         vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
221 
222       (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
223     }
224   }
225 
226   cols = src.end_col - src.start_col;
227   downward = src.start_row - dest.start_row;
228 
229   if(downward < 0) {
230     init_row = dest.end_row - 1;
231     test_row = dest.start_row - 1;
232     inc_row  = -1;
233   }
234   else {
235     init_row = dest.start_row;
236     test_row = dest.end_row;
237     inc_row  = +1;
238   }
239 
240   for(row = init_row; row != test_row; row += inc_row)
241     memmove(getcell(screen, row, dest.start_col),
242             getcell(screen, row + downward, src.start_col),
243             cols * sizeof(ScreenCell));
244 
245   return 1;
246 }
247 
moverect_user(VTermRect dest,VTermRect src,void * user)248 static int moverect_user(VTermRect dest, VTermRect src, void *user)
249 {
250   VTermScreen *screen = user;
251 
252   if(screen->callbacks && screen->callbacks->moverect) {
253     if(screen->damage_merge != VTERM_DAMAGE_SCROLL)
254       // Avoid an infinite loop
255       vterm_screen_flush_damage(screen);
256 
257     if((*screen->callbacks->moverect)(dest, src, screen->cbdata))
258       return 1;
259   }
260 
261   damagerect(screen, dest);
262 
263   return 1;
264 }
265 
erase_internal(VTermRect rect,int selective,void * user)266 static int erase_internal(VTermRect rect, int selective, void *user)
267 {
268   VTermScreen *screen = user;
269   int row, col;
270 
271   for(row = rect.start_row; row < rect.end_row; row++) {
272     const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
273 
274     for(col = rect.start_col; col < rect.end_col; col++) {
275       ScreenCell *cell = getcell(screen, row, col);
276 
277       if(selective && cell->pen.protected_cell)
278         continue;
279 
280       cell->chars[0] = 0;
281       cell->pen = screen->pen;
282       cell->pen.dwl = info->doublewidth;
283       cell->pen.dhl = info->doubleheight;
284     }
285   }
286 
287   return 1;
288 }
289 
erase_user(VTermRect rect,int selective,void * user)290 static int erase_user(VTermRect rect, int selective, void *user)
291 {
292   VTermScreen *screen = user;
293 
294   damagerect(screen, rect);
295 
296   return 1;
297 }
298 
erase(VTermRect rect,int selective,void * user)299 static int erase(VTermRect rect, int selective, void *user)
300 {
301   erase_internal(rect, selective, user);
302   return erase_user(rect, 0, user);
303 }
304 
scrollrect(VTermRect rect,int downward,int rightward,void * user)305 static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
306 {
307   VTermScreen *screen = user;
308 
309   vterm_scroll_rect(rect, downward, rightward,
310       moverect_internal, erase_internal, screen);
311 
312   if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
313     vterm_screen_flush_damage(screen);
314 
315     vterm_scroll_rect(rect, downward, rightward,
316         moverect_user, erase_user, screen);
317 
318     return 1;
319   }
320 
321   if(screen->damaged.start_row != -1 &&
322      !rect_intersects(&rect, &screen->damaged)) {
323     vterm_screen_flush_damage(screen);
324   }
325 
326   if(screen->pending_scrollrect.start_row == -1) {
327     screen->pending_scrollrect = rect;
328     screen->pending_scroll_downward  = downward;
329     screen->pending_scroll_rightward = rightward;
330   }
331   else if(rect_equal(&screen->pending_scrollrect, &rect) &&
332      ((screen->pending_scroll_downward  == 0 && downward  == 0) ||
333       (screen->pending_scroll_rightward == 0 && rightward == 0))) {
334     screen->pending_scroll_downward  += downward;
335     screen->pending_scroll_rightward += rightward;
336   }
337   else {
338     vterm_screen_flush_damage(screen);
339 
340     screen->pending_scrollrect = rect;
341     screen->pending_scroll_downward  = downward;
342     screen->pending_scroll_rightward = rightward;
343   }
344 
345   if(screen->damaged.start_row == -1)
346     return 1;
347 
348   if(rect_contains(&rect, &screen->damaged)) {
349     vterm_rect_move(&screen->damaged, -downward, -rightward);
350     rect_clip(&screen->damaged, &rect);
351   }
352   /* There are a number of possible cases here, but lets restrict this to only
353    * the common case where we might actually gain some performance by
354    * optimising it. Namely, a vertical scroll that neatly cuts the damage
355    * region in half.
356    */
357   else if(rect.start_col <= screen->damaged.start_col &&
358           rect.end_col   >= screen->damaged.end_col &&
359           rightward == 0) {
360     if(screen->damaged.start_row >= rect.start_row &&
361        screen->damaged.start_row  < rect.end_row) {
362       screen->damaged.start_row -= downward;
363       if(screen->damaged.start_row < rect.start_row)
364         screen->damaged.start_row = rect.start_row;
365       if(screen->damaged.start_row > rect.end_row)
366         screen->damaged.start_row = rect.end_row;
367     }
368     if(screen->damaged.end_row >= rect.start_row &&
369        screen->damaged.end_row  < rect.end_row) {
370       screen->damaged.end_row -= downward;
371       if(screen->damaged.end_row < rect.start_row)
372         screen->damaged.end_row = rect.start_row;
373       if(screen->damaged.end_row > rect.end_row)
374         screen->damaged.end_row = rect.end_row;
375     }
376   }
377   else {
378     fprintf(stderr, "TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
379         ARGSrect(screen->damaged), ARGSrect(rect));
380   }
381 
382   return 1;
383 }
384 
movecursor(VTermPos pos,VTermPos oldpos,int visible,void * user)385 static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
386 {
387   VTermScreen *screen = user;
388 
389   if(screen->callbacks && screen->callbacks->movecursor)
390     return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
391 
392   return 0;
393 }
394 
setpenattr(VTermAttr attr,VTermValue * val,void * user)395 static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
396 {
397   VTermScreen *screen = user;
398 
399   switch(attr) {
400   case VTERM_ATTR_BOLD:
401     screen->pen.bold = val->boolean;
402     return 1;
403   case VTERM_ATTR_UNDERLINE:
404     screen->pen.underline = val->number;
405     return 1;
406   case VTERM_ATTR_ITALIC:
407     screen->pen.italic = val->boolean;
408     return 1;
409   case VTERM_ATTR_BLINK:
410     screen->pen.blink = val->boolean;
411     return 1;
412   case VTERM_ATTR_REVERSE:
413     screen->pen.reverse = val->boolean;
414     return 1;
415   case VTERM_ATTR_STRIKE:
416     screen->pen.strike = val->boolean;
417     return 1;
418   case VTERM_ATTR_FONT:
419     screen->pen.font = val->number;
420     return 1;
421   case VTERM_ATTR_FOREGROUND:
422     screen->pen.fg = val->color;
423     return 1;
424   case VTERM_ATTR_BACKGROUND:
425     screen->pen.bg = val->color;
426     return 1;
427   }
428 
429   return 0;
430 }
431 
settermprop(VTermProp prop,VTermValue * val,void * user)432 static int settermprop(VTermProp prop, VTermValue *val, void *user)
433 {
434   VTermScreen *screen = user;
435 
436   switch(prop) {
437   case VTERM_PROP_ALTSCREEN:
438     if(val->boolean && !screen->buffers[1])
439       return 0;
440 
441     screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0];
442     /* only send a damage event on disable; because during enable there's an
443      * erase that sends a damage anyway
444      */
445     if(!val->boolean)
446       damagescreen(screen);
447     break;
448   case VTERM_PROP_REVERSE:
449     screen->global_reverse = val->boolean;
450     damagescreen(screen);
451     break;
452   default:
453     ; /* ignore */
454   }
455 
456   if(screen->callbacks && screen->callbacks->settermprop)
457     return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
458 
459   return 1;
460 }
461 
setmousefunc(VTermMouseFunc func,void * data,void * user)462 static int setmousefunc(VTermMouseFunc func, void *data, void *user)
463 {
464   VTermScreen *screen = user;
465 
466   if(screen->callbacks && screen->callbacks->setmousefunc)
467     return (*screen->callbacks->setmousefunc)(func, data, screen->cbdata);
468 
469   return 0;
470 }
471 
bell(void * user)472 static int bell(void *user)
473 {
474   VTermScreen *screen = user;
475 
476   if(screen->callbacks && screen->callbacks->bell)
477     return (*screen->callbacks->bell)(screen->cbdata);
478 
479   return 0;
480 }
481 
resize(int new_rows,int new_cols,VTermPos * delta,void * user)482 static int resize(int new_rows, int new_cols, VTermPos *delta, void *user)
483 {
484   VTermScreen *screen = user;
485 
486   int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]);
487 
488   int old_rows = screen->rows;
489   int old_cols = screen->cols;
490   int first_blank_row;
491   VTermRect rect;
492 
493   if(!is_altscreen && new_rows < old_rows) {
494     // Fewer rows - determine if we're going to scroll at all, and if so, push
495     // those lines to scrollback
496     VTermPos pos = { 0, 0 };
497     for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--)
498       if(!vterm_screen_is_eol(screen, pos))
499         break;
500 
501     first_blank_row = pos.row + 1;
502     if(first_blank_row > new_rows) {
503       VTermRect rect = {
504         .start_row = 0,
505         .end_row   = old_rows,
506         .start_col = 0,
507         .end_col   = old_cols,
508       };
509       scrollrect(rect, first_blank_row - new_rows, 0, user);
510       vterm_screen_flush_damage(screen);
511 
512       delta->row -= first_blank_row - new_rows;
513     }
514   }
515 
516   screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols);
517   if(screen->buffers[1])
518     screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols);
519 
520   screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0];
521 
522   screen->rows = new_rows;
523   screen->cols = new_cols;
524 
525   if(screen->sb_buffer)
526     vterm_allocator_free(screen->vt, screen->sb_buffer);
527 
528   screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
529 
530   if(new_cols > old_cols) {
531     VTermRect rect = {
532       .start_row = 0,
533       .end_row   = old_rows,
534       .start_col = old_cols,
535       .end_col   = new_cols,
536     };
537     damagerect(screen, rect);
538   }
539 
540   if(new_rows > old_rows) {
541     if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) {
542       int rows = new_rows - old_rows;
543       while(rows) {
544         VTermRect rect = {
545           .start_row = 0,
546           .end_row   = screen->rows,
547           .start_col = 0,
548           .end_col   = screen->cols,
549         };
550         VTermPos pos = { 0, 0 };
551 
552         if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata)))
553           break;
554 
555         scrollrect(rect, -1, 0, user);
556 
557         for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width)
558           vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col);
559 
560         rect.end_row = 1;
561         damagerect(screen, rect);
562 
563         vterm_screen_flush_damage(screen);
564 
565         rows--;
566         delta->row++;
567       }
568     }
569 
570     rect.start_row = old_rows;
571     rect.end_row   = new_rows;
572     rect.start_col = 0;
573     rect.end_col   = new_cols;
574     damagerect(screen, rect);
575   }
576 
577   if(screen->callbacks && screen->callbacks->resize)
578     return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
579 
580   return 1;
581 }
582 
setlineinfo(int row,const VTermLineInfo * newinfo,const VTermLineInfo * oldinfo,void * user)583 static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
584 {
585   VTermScreen *screen = user;
586 
587   if(newinfo->doublewidth != oldinfo->doublewidth ||
588      newinfo->doubleheight != oldinfo->doubleheight) {
589     VTermRect rect;
590 	int col;
591     for(col = 0; col < screen->cols; col++) {
592       ScreenCell *cell = getcell(screen, row, col);
593       cell->pen.dwl = newinfo->doublewidth;
594       cell->pen.dhl = newinfo->doubleheight;
595     }
596 
597     rect.start_row = row;
598     rect.end_row   = row + 1;
599     rect.start_col = 0;
600     rect.end_col   = newinfo->doublewidth ? screen->cols / 2 : screen->cols;
601     damagerect(screen, rect);
602 
603     if(newinfo->doublewidth) {
604       rect.start_col = screen->cols / 2;
605       rect.end_col   = screen->cols;
606 
607       erase_internal(rect, 0, user);
608     }
609   }
610 
611   return 1;
612 }
613 
614 static VTermStateCallbacks state_cbs = {
615   .putglyph     = &putglyph,
616   .movecursor   = &movecursor,
617   .scrollrect   = &scrollrect,
618   .erase        = &erase,
619   .setpenattr   = &setpenattr,
620   .settermprop  = &settermprop,
621   .setmousefunc = &setmousefunc,
622   .bell         = &bell,
623   .resize       = &resize,
624   .setlineinfo  = &setlineinfo,
625 };
626 
screen_new(VTerm * vt)627 static VTermScreen *screen_new(VTerm *vt)
628 {
629   VTermState *state = vterm_obtain_state(vt);
630   int rows, cols;
631   VTermScreen *screen;
632 
633   if(!state)
634     return NULL;
635 
636   screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
637 
638   vterm_get_size(vt, &rows, &cols);
639 
640   screen->vt = vt;
641   screen->state = state;
642 
643   screen->damage_merge = VTERM_DAMAGE_CELL;
644   screen->damaged.start_row = -1;
645   screen->pending_scrollrect.start_row = -1;
646 
647   screen->rows = rows;
648   screen->cols = cols;
649 
650   screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols);
651 
652   screen->buffer = screen->buffers[0];
653 
654   screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
655 
656   vterm_state_set_callbacks(screen->state, &state_cbs, screen);
657 
658   return screen;
659 }
660 
vterm_screen_free(VTermScreen * screen)661 INTERNAL void vterm_screen_free(VTermScreen *screen)
662 {
663   vterm_allocator_free(screen->vt, screen->buffers[0]);
664   if(screen->buffers[1])
665     vterm_allocator_free(screen->vt, screen->buffers[1]);
666 
667   vterm_allocator_free(screen->vt, screen->sb_buffer);
668 
669   vterm_allocator_free(screen->vt, screen);
670 }
671 
vterm_screen_reset(VTermScreen * screen,int hard)672 void vterm_screen_reset(VTermScreen *screen, int hard)
673 {
674   screen->damaged.start_row = -1;
675   screen->pending_scrollrect.start_row = -1;
676   vterm_state_reset(screen->state, hard);
677   vterm_screen_flush_damage(screen);
678 }
679 
_get_chars(const VTermScreen * screen,const int utf8,void * buffer,size_t len,const VTermRect rect)680 static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
681 {
682   size_t outpos = 0;
683   int padding = 0;
684   int row, col;
685 
686 #define PUT(c)                                             \
687   if(utf8) {                                               \
688     size_t thislen = utf8_seqlen(c);                       \
689     if(buffer && outpos + thislen <= len)                  \
690       outpos += fill_utf8((c), (char *)buffer + outpos);   \
691     else                                                   \
692       outpos += thislen;                                   \
693   }                                                        \
694   else {                                                   \
695     if(buffer && outpos + 1 <= len)                        \
696       ((uint32_t*)buffer)[outpos++] = (c);                 \
697     else                                                   \
698       outpos++;                                            \
699   }
700 
701   for(row = rect.start_row; row < rect.end_row; row++) {
702     for(col = rect.start_col; col < rect.end_col; col++) {
703       ScreenCell *cell = getcell(screen, row, col);
704 
705       if(cell->chars[0] == 0)
706         // Erased cell, might need a space
707         padding++;
708       else if(cell->chars[0] == (uint32_t)-1)
709         // Gap behind a double-width char, do nothing
710         ;
711       else {
712 		int i;
713         while(padding) {
714           PUT(UNICODE_SPACE);
715           padding--;
716         }
717         for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
718           PUT(cell->chars[i]);
719         }
720       }
721     }
722 
723     if(row < rect.end_row - 1) {
724       PUT(UNICODE_LINEFEED);
725       padding = 0;
726     }
727   }
728 
729   return outpos;
730 }
731 
vterm_screen_get_chars(const VTermScreen * screen,uint32_t * chars,size_t len,const VTermRect rect)732 size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
733 {
734   return _get_chars(screen, 0, chars, len, rect);
735 }
736 
vterm_screen_get_text(const VTermScreen * screen,char * str,size_t len,const VTermRect rect)737 size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
738 {
739   return _get_chars(screen, 1, str, len, rect);
740 }
741 
742 /* Copy internal to external representation of a screen cell */
vterm_screen_get_cell(const VTermScreen * screen,VTermPos pos,VTermScreenCell * cell)743 int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
744 {
745   ScreenCell *intcell = getcell(screen, pos.row, pos.col);
746   int i;
747   if(!intcell)
748     return 0;
749 
750   for(i = 0; ; i++) {
751     cell->chars[i] = intcell->chars[i];
752     if(!intcell->chars[i])
753       break;
754   }
755 
756   cell->attrs.bold      = intcell->pen.bold;
757   cell->attrs.underline = intcell->pen.underline;
758   cell->attrs.italic    = intcell->pen.italic;
759   cell->attrs.blink     = intcell->pen.blink;
760   cell->attrs.reverse   = intcell->pen.reverse ^ screen->global_reverse;
761   cell->attrs.strike    = intcell->pen.strike;
762   cell->attrs.font      = intcell->pen.font;
763 
764   cell->attrs.dwl = intcell->pen.dwl;
765   cell->attrs.dhl = intcell->pen.dhl;
766 
767   cell->fg = intcell->pen.fg;
768   cell->bg = intcell->pen.bg;
769 
770   if(pos.col < (screen->cols - 1) &&
771      getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
772     cell->width = 2;
773   else
774     cell->width = 1;
775 
776   return 1;
777 }
778 
779 /* Copy external to internal representation of a screen cell */
780 /* static because it's only used internally for sb_popline during resize */
vterm_screen_set_cell(VTermScreen * screen,VTermPos pos,const VTermScreenCell * cell)781 static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell)
782 {
783   ScreenCell *intcell = getcell(screen, pos.row, pos.col);
784   int i;
785 
786   if(!intcell)
787     return 0;
788 
789   for(i = 0; ; i++) {
790     intcell->chars[i] = cell->chars[i];
791     if(!cell->chars[i])
792       break;
793   }
794 
795   intcell->pen.bold      = cell->attrs.bold;
796   intcell->pen.underline = cell->attrs.underline;
797   intcell->pen.italic    = cell->attrs.italic;
798   intcell->pen.blink     = cell->attrs.blink;
799   intcell->pen.reverse   = cell->attrs.reverse ^ screen->global_reverse;
800   intcell->pen.strike    = cell->attrs.strike;
801   intcell->pen.font      = cell->attrs.font;
802 
803   intcell->pen.fg = cell->fg;
804   intcell->pen.bg = cell->bg;
805 
806   if(cell->width == 2)
807     getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1;
808 
809   return 1;
810 }
811 
vterm_screen_is_eol(const VTermScreen * screen,VTermPos pos)812 int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
813 {
814   /* This cell is EOL if this and every cell to the right is black */
815   for(; pos.col < screen->cols; pos.col++) {
816     ScreenCell *cell = getcell(screen, pos.row, pos.col);
817     if(cell->chars[0] != 0)
818       return 0;
819   }
820 
821   return 1;
822 }
823 
vterm_obtain_screen(VTerm * vt)824 VTermScreen *vterm_obtain_screen(VTerm *vt)
825 {
826   VTermScreen *screen;
827   if(vt->screen)
828     return vt->screen;
829 
830   screen = screen_new(vt);
831   vt->screen = screen;
832 
833   return screen;
834 }
835 
vterm_screen_enable_altscreen(VTermScreen * screen,int altscreen)836 void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
837 {
838 
839   if(!screen->buffers[1] && altscreen) {
840     int rows, cols;
841     vterm_get_size(screen->vt, &rows, &cols);
842 
843     screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols);
844   }
845 }
846 
vterm_screen_set_callbacks(VTermScreen * screen,const VTermScreenCallbacks * callbacks,void * user)847 void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)
848 {
849   screen->callbacks = callbacks;
850   screen->cbdata = user;
851 }
852 
vterm_screen_flush_damage(VTermScreen * screen)853 void vterm_screen_flush_damage(VTermScreen *screen)
854 {
855   if(screen->pending_scrollrect.start_row != -1) {
856     vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,
857         moverect_user, erase_user, screen);
858 
859     screen->pending_scrollrect.start_row = -1;
860   }
861 
862   if(screen->damaged.start_row != -1) {
863     if(screen->callbacks && screen->callbacks->damage)
864       (*screen->callbacks->damage)(screen->damaged, screen->cbdata);
865 
866     screen->damaged.start_row = -1;
867   }
868 }
869 
vterm_screen_set_damage_merge(VTermScreen * screen,VTermDamageSize size)870 void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
871 {
872   vterm_screen_flush_damage(screen);
873   screen->damage_merge = size;
874 }
875 
attrs_differ(VTermAttrMask attrs,ScreenCell * a,ScreenCell * b)876 static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
877 {
878   if((attrs & VTERM_ATTR_BOLD_MASK)       && (a->pen.bold != b->pen.bold))
879     return 1;
880   if((attrs & VTERM_ATTR_UNDERLINE_MASK)  && (a->pen.underline != b->pen.underline))
881     return 1;
882   if((attrs & VTERM_ATTR_ITALIC_MASK)     && (a->pen.italic != b->pen.italic))
883     return 1;
884   if((attrs & VTERM_ATTR_BLINK_MASK)      && (a->pen.blink != b->pen.blink))
885     return 1;
886   if((attrs & VTERM_ATTR_REVERSE_MASK)    && (a->pen.reverse != b->pen.reverse))
887     return 1;
888   if((attrs & VTERM_ATTR_STRIKE_MASK)     && (a->pen.strike != b->pen.strike))
889     return 1;
890   if((attrs & VTERM_ATTR_FONT_MASK)       && (a->pen.font != b->pen.font))
891     return 1;
892   if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_equal(a->pen.fg, b->pen.fg))
893     return 1;
894   if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_equal(a->pen.bg, b->pen.bg))
895     return 1;
896 
897   return 0;
898 }
899 
vterm_screen_get_attrs_extent(const VTermScreen * screen,VTermRect * extent,VTermPos pos,VTermAttrMask attrs)900 int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
901 {
902   ScreenCell *target = getcell(screen, pos.row, pos.col);
903 
904   int col;
905 
906   // TODO: bounds check
907   extent->start_row = pos.row;
908   extent->end_row   = pos.row + 1;
909 
910   if(extent->start_col < 0)
911     extent->start_col = 0;
912   if(extent->end_col < 0)
913     extent->end_col = screen->cols;
914 
915   for(col = pos.col - 1; col >= extent->start_col; col--)
916     if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
917       break;
918   extent->start_col = col + 1;
919 
920   for(col = pos.col + 1; col < extent->end_col; col++)
921     if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
922       break;
923   extent->end_col = col - 1;
924 
925   return 1;
926 }
927