1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <assert.h>
6 #include <fcntl.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/param.h>
10 
11 #include <zircon/process.h>
12 #include <zircon/syscalls.h>
13 
14 #include <fbl/auto_lock.h>
15 
16 #include "vc.h"
17 
18 static uint32_t default_palette[] = {
19     // 0-7 Normal/dark versions of colors
20     0xff000000, // black
21     0xffaa0000, // red
22     0xff00aa00, // green
23     0xffaa5500, // brown
24     0xff0000aa, // blue
25     0xffaa00aa, // zircon
26     0xff00aaaa, // cyan
27     0xffaaaaaa, // grey
28     // 8-15 Bright/light versions of colors
29     0xff555555, // dark grey
30     0xffff5555, // bright red
31     0xff55ff55, // bright green
32     0xffffff55, // yellow
33     0xff5555ff, // bright blue
34     0xffff55ff, // bright zircon
35     0xff55ffff, // bright cyan
36     0xffffffff, // white
37 };
38 
39 #define DEFAULT_FRONT_COLOR 0x0 // black
40 #define DEFAULT_BACK_COLOR 0xf  // white
41 
42 #define SPECIAL_FRONT_COLOR 0xf // white
43 #define SPECIAL_BACK_COLOR 0x4  // blue
44 
45 // Default height/width (in px) of console before any displays are
46 // attached, since we need somewhere to put any data that is received.
47 #define DEFAULT_WIDTH 1024
48 #define DEFAULT_HEIGHT 768
49 #define SCROLLBACK_ROWS 1024 // TODO make configurable
50 
51 #define ABS(val) (((val) >= 0) ? (val) : -(val))
52 
53 // shared with vc-gfx.cpp
54 extern gfx_surface* vc_gfx;
55 extern gfx_surface* vc_tb_gfx;
56 extern const gfx_font* vc_font;
57 
vc_setup(vc_t * vc,bool special)58 static zx_status_t vc_setup(vc_t* vc, bool special) {
59     // calculate how many rows/columns we have
60     vc->rows = DEFAULT_HEIGHT / vc->charh;
61     vc->columns = DEFAULT_WIDTH / vc->charw;
62     vc->scrollback_rows_max = SCROLLBACK_ROWS;
63     vc->scrollback_rows_count = 0;
64     vc->scrollback_offset = 0;
65 
66     // allocate the text buffer
67     vc->text_buf = reinterpret_cast<vc_char_t*>(
68         calloc(1, vc->rows * vc->columns * sizeof(vc_char_t)));
69     if (!vc->text_buf)
70         return ZX_ERR_NO_MEMORY;
71 
72     // allocate the scrollback buffer
73     vc->scrollback_buf = reinterpret_cast<vc_char_t*>(
74         calloc(1, vc->scrollback_rows_max * vc->columns * sizeof(vc_char_t)));
75     if (!vc->scrollback_buf) {
76         free(vc->text_buf);
77         return ZX_ERR_NO_MEMORY;
78     }
79 
80     // set up the default palette
81     memcpy(&vc->palette, default_palette, sizeof(default_palette));
82     if (special) {
83         vc->front_color = SPECIAL_FRONT_COLOR;
84         vc->back_color = SPECIAL_BACK_COLOR;
85     } else {
86         vc->front_color = DEFAULT_FRONT_COLOR;
87         vc->back_color = DEFAULT_BACK_COLOR;
88     }
89 
90     return ZX_OK;
91 }
92 
vc_invalidate(void * cookie,int x0,int y0,int w,int h)93 static void vc_invalidate(void* cookie, int x0, int y0, int w, int h) {
94     vc_t* vc = reinterpret_cast<vc_t*>(cookie);
95 
96     if (!g_vc_owns_display || !vc->active || !vc_gfx) {
97         return;
98     }
99 
100     assert(h >= 0);
101     int y1 = y0 + h;
102     assert(y0 <= static_cast<int>(vc->rows));
103     assert(y1 <= static_cast<int>(vc->rows));
104 
105     // Clip the y range so that we don't unnecessarily draw characters
106     // outside the visible range, and so that we don't draw characters into
107     // the bottom margin.
108     int visible_y0 = vc->viewport_y;
109     int visible_y1 = vc->viewport_y + vc_rows(vc);
110     y0 = MAX(y0, visible_y0);
111     y1 = MIN(y1, visible_y1);
112 
113     for (int y = y0; y < y1; y++) {
114         if (y < 0) {
115             // Scrollback row.
116             vc_char_t* row = vc_get_scrollback_line_ptr(
117                 vc, y + vc->scrollback_rows_count);
118             for (int x = x0; x < x0 + w; x++) {
119                 vc_gfx_draw_char(vc, row[x], x, y - vc->viewport_y,
120                                  /* invert= */ false);
121             }
122         } else {
123             // Row in the main console region (non-scrollback).
124             vc_char_t* row = &vc->text_buf[y * vc->columns];
125             for (int x = x0; x < x0 + w; x++) {
126                 // Check whether we should display the cursor at this
127                 // position.  Note that it's possible that the cursor is
128                 // outside the display area (vc->cursor_x ==
129                 // vc->columns).  In that case, we won't display the
130                 // cursor, even if there's a margin.  This matches
131                 // gnome-terminal.
132                 bool invert = (!vc->hide_cursor &&
133                                static_cast<unsigned>(x) == vc->cursor_x &&
134                                static_cast<unsigned>(y) == vc->cursor_y);
135                 vc_gfx_draw_char(vc, row[x], x, y - vc->viewport_y, invert);
136             }
137         }
138     }
139 }
140 
141 // implement tc callbacks:
142 
vc_invalidate_lines(vc_t * vc,int y,int h)143 static inline void vc_invalidate_lines(vc_t* vc, int y, int h) {
144     if (y < vc->invy0) {
145         vc->invy0 = y;
146     }
147     y += h;
148     if (y > vc->invy1) {
149         vc->invy1 = y;
150     }
151 }
152 
vc_tc_invalidate(void * cookie,int x0,int y0,int w,int h)153 static void vc_tc_invalidate(void* cookie, int x0, int y0, int w, int h){
154     vc_t* vc = reinterpret_cast<vc_t*>(cookie);
155     vc_invalidate(cookie, x0, y0, w, h);
156     vc_invalidate_lines(vc, y0, h);
157 }
158 
vc_tc_movecursor(void * cookie,int x,int y)159 static void vc_tc_movecursor(void* cookie, int x, int y) {
160     vc_t* vc = reinterpret_cast<vc_t*>(cookie);
161     unsigned old_x = vc->cursor_x;
162     unsigned old_y = vc->cursor_y;
163     vc->cursor_x = x;
164     vc->cursor_y = y;
165     if (g_vc_owns_display && vc->active && !vc->hide_cursor) {
166         // Clear the cursor from its old position.
167         vc_invalidate(cookie, old_x, old_y, 1, 1);
168         vc_invalidate_lines(vc, old_y, 1);
169 
170         // Display the cursor in its new position.
171         vc_invalidate(cookie, vc->cursor_x, vc->cursor_y, 1, 1);
172         vc_invalidate_lines(vc, vc->cursor_y, 1);
173     }
174 }
175 
vc_tc_scrollback_buffer_push(vc_t * vc,vc_char_t * src)176 static void vc_tc_scrollback_buffer_push(vc_t* vc, vc_char_t* src) {
177     unsigned dest_row;
178     assert(vc->scrollback_rows_count <= vc->scrollback_rows_max);
179     if (vc->scrollback_rows_count < vc->scrollback_rows_max) {
180         // Add a row without dropping any existing rows.
181         assert(vc->scrollback_offset == 0);
182         dest_row = vc->scrollback_rows_count++;
183     } else {
184         // Add a row and drop an existing row.
185         assert(vc->scrollback_offset < vc->scrollback_rows_max);
186         dest_row = vc->scrollback_offset++;
187         if (vc->scrollback_offset == vc->scrollback_rows_max)
188             vc->scrollback_offset = 0;
189     }
190     vc_char_t* dst = &vc->scrollback_buf[dest_row * vc->columns];
191     memcpy(dst, src, vc->columns * sizeof(vc_char_t));
192 }
193 
vc_tc_push_scrollback_line(void * cookie,int y)194 static void vc_tc_push_scrollback_line(void* cookie, int y) {
195     vc_t* vc = reinterpret_cast<vc_t*>(cookie);
196     vc_char_t* src = &vc->text_buf[y * vc->columns];
197     vc_tc_scrollback_buffer_push(vc, src);
198 
199     // If we're displaying only the main console region (and no
200     // scrollback), then keep displaying that (i.e. don't modify
201     // viewport_y).
202     if (vc->viewport_y < 0) {
203         // We are displaying some of the scrollback buffer.
204         if (vc->viewport_y > -static_cast<int>(vc->scrollback_rows_max)) {
205             // Scroll the viewport to continue displaying the same point in
206             // the scrollback buffer.
207             --vc->viewport_y;
208         } else {
209             // We were displaying the line at the top of the scrollback
210             // buffer, but we dropped that line from the buffer.  We could
211             // leave the display as it was (which is what gnome-terminal
212             // does) and not scroll the display.  However, that causes
213             // problems.  If the user later scrolls down, we won't
214             // necessarily be able to display the lines below -- we might
215             // have dropped those too.  So, instead, let's scroll the
216             // display and remove the scrollback line that was lost.
217             //
218             // For simplicity, fall back to redrawing everything.
219             vc_invalidate(vc, 0, -vc->scrollback_rows_max,
220                                  vc->columns, vc_rows(vc));
221             vc_render(vc);
222         }
223     }
224 }
225 
vc_set_cursor_hidden(vc_t * vc,bool hide)226 static void vc_set_cursor_hidden(vc_t* vc, bool hide) {
227     if (vc->hide_cursor == hide)
228         return;
229     vc->hide_cursor = hide;
230     if (g_vc_owns_display && vc->active) {
231         vc_invalidate(vc, vc->cursor_x, vc->cursor_y, 1, 1);
232         vc_invalidate_lines(vc, vc->cursor_y, 1);
233     }
234 }
235 
vc_tc_copy_lines(void * cookie,int y_dest,int y_src,int line_count)236 static void vc_tc_copy_lines(void* cookie, int y_dest, int y_src, int line_count) {
237     vc_t* vc = reinterpret_cast<vc_t*>(cookie);
238 
239     if (vc->viewport_y < 0) {
240         tc_copy_lines(&vc->textcon, y_dest, y_src, line_count);
241 
242         // The viewport is scrolled.  For simplicity, fall back to
243         // redrawing all of the non-scrollback lines in this case.
244         int rows = vc_rows(vc);
245         vc_invalidate(vc, 0, 0, vc->columns, rows);
246         vc_invalidate_lines(vc, 0, rows);
247         return;
248     }
249 
250     // Remove the cursor from the display before copying the lines on
251     // screen, otherwise we might be copying a rendering of the cursor to a
252     // position where the cursor isn't.  This must be done before the
253     // tc_copy_lines() call, otherwise we might render the wrong character.
254     bool old_hide_cursor = vc->hide_cursor;
255     if (g_vc_owns_display && vc->active) {
256         vc_set_cursor_hidden(vc, true);
257     }
258 
259     // The next two calls can be done in any order.
260     tc_copy_lines(&vc->textcon, y_dest, y_src, line_count);
261 
262     if (g_vc_owns_display && vc->active && vc_gfx) {
263         gfx_copyrect(vc_gfx, 0, y_src * vc->charh,
264                      vc_gfx->width, line_count * vc->charh,
265                      0, y_dest * vc->charh);
266 
267         // Restore the cursor.
268         vc_set_cursor_hidden(vc, old_hide_cursor);
269 
270         vc_status_update();
271         vc_gfx_invalidate_status();
272         vc_invalidate_lines(vc, 0, vc_rows(vc));
273     }
274 }
275 
vc_tc_setparam(void * cookie,int param,uint8_t * arg,size_t arglen)276 static void vc_tc_setparam(void* cookie, int param, uint8_t* arg, size_t arglen) {
277     vc_t* vc = reinterpret_cast<vc_t*>(cookie);
278     switch (param) {
279     case TC_SET_TITLE:
280         strncpy(vc->title, (char*)arg, sizeof(vc->title));
281         vc->title[sizeof(vc->title) - 1] = '\0';
282         vc_status_update();
283         if (g_vc_owns_display && vc_gfx) {
284             vc_gfx_invalidate_status();
285         }
286         break;
287     case TC_SHOW_CURSOR:
288         vc_set_cursor_hidden(vc, false);
289         break;
290     case TC_HIDE_CURSOR:
291         vc_set_cursor_hidden(vc, true);
292         break;
293     default:; // nothing
294     }
295 }
296 
vc_clear_gfx(vc_t * vc)297 static void vc_clear_gfx(vc_t* vc) {
298     // Fill display with background color
299     if (g_vc_owns_display && vc->active && vc_gfx) {
300         gfx_fillrect(vc_gfx, 0, 0, vc_gfx->width, vc_gfx->height,
301                      palette_to_color(vc, vc->back_color));
302     }
303 }
304 
vc_reset(vc_t * vc)305 static void vc_reset(vc_t* vc) {
306     // reset the cursor
307     vc->cursor_x = 0;
308     vc->cursor_y = 0;
309     // reset the viewport position
310     vc->viewport_y = 0;
311 
312     tc_init(&vc->textcon, vc->columns, vc_rows(vc), vc->text_buf,
313             vc->front_color, vc->back_color, vc->cursor_x, vc->cursor_y);
314     vc->textcon.cookie = vc;
315     vc->textcon.invalidate = vc_tc_invalidate;
316     vc->textcon.movecursor = vc_tc_movecursor;
317     vc->textcon.push_scrollback_line = vc_tc_push_scrollback_line;
318     vc->textcon.copy_lines = vc_tc_copy_lines;
319     vc->textcon.setparam = vc_tc_setparam;
320 
321     // fill textbuffer with blank characters
322     size_t count = vc->rows * vc->columns;
323     vc_char_t* ptr = vc->text_buf;
324     while (count--) {
325         *ptr++ = vc_char_make(' ', vc->front_color, vc->back_color);
326     }
327 
328     vc_clear_gfx(vc);
329     if (vc_gfx) {
330         vc_gfx_invalidate_all(vc);
331     }
332 }
333 
vc_status_clear()334 void vc_status_clear() {
335     if (g_vc_owns_display && vc_gfx) {
336         gfx_fillrect(vc_tb_gfx, 0, 0,
337                      vc_tb_gfx->width, vc_tb_gfx->height,
338                      default_palette[STATUS_COLOR_BG]);
339     }
340 }
341 
vc_status_commit()342 void vc_status_commit() {
343     if (g_vc_owns_display && vc_gfx) {
344         vc_gfx_invalidate_status();
345     }
346 }
347 
vc_status_write(int x,unsigned color,const char * text)348 void vc_status_write(int x, unsigned color, const char* text) {
349     char c;
350     unsigned fg = default_palette[color];
351     unsigned bg = default_palette[STATUS_COLOR_BG];
352 
353     if (g_vc_owns_display && vc_gfx) {
354         x *= vc_font->width;
355         while ((c = *text++) != 0) {
356             gfx_putchar(vc_tb_gfx, vc_font, c, x, 0, fg, bg);
357             x += vc_font->width;
358         }
359     }
360 }
361 
vc_render(vc_t * vc)362 void vc_render(vc_t* vc) {
363     if (g_vc_owns_display && vc->active && vc_gfx) {
364         vc_status_update();
365         vc_gfx_invalidate_all(vc);
366     }
367 }
368 
vc_full_repaint(vc_t * vc)369 void vc_full_repaint(vc_t* vc) {
370     if (g_vc_owns_display && vc_gfx) {
371         vc_clear_gfx(vc);
372         int scrollback_lines = vc_get_scrollback_lines(vc);
373         vc_invalidate(vc, 0, -scrollback_lines,
374                              vc->columns, scrollback_lines + vc->rows);
375     }
376 }
377 
vc_get_scrollback_lines(vc_t * vc)378 int vc_get_scrollback_lines(vc_t* vc) {
379     return vc->scrollback_rows_count;
380 }
381 
vc_get_scrollback_line_ptr(vc_t * vc,unsigned row)382 vc_char_t* vc_get_scrollback_line_ptr(vc_t* vc, unsigned row) {
383     assert(row < vc->scrollback_rows_count);
384     row += vc->scrollback_offset;
385     if (row >= vc->scrollback_rows_max)
386         row -= vc->scrollback_rows_max;
387     return &vc->scrollback_buf[row * vc->columns];
388 }
389 
vc_scroll_viewport_abs(vc_t * vc,int vpy)390 static void vc_scroll_viewport_abs(vc_t* vc, int vpy) {
391     vpy = MIN(vpy, 0);
392     vpy = MAX(vpy, -vc_get_scrollback_lines(vc));
393     int diff = vpy - vc->viewport_y;
394     if (diff == 0)
395         return;
396     int diff_abs = ABS(diff);
397     vc->viewport_y = vpy;
398     int rows = vc_rows(vc);
399     if (!g_vc_owns_display || !vc->active || !vc_gfx) {
400         return;
401     }
402     if (diff_abs >= rows) {
403         // We are scrolling the viewport by a large delta.  Invalidate all
404         // of the visible area of the console.
405         vc_invalidate(vc, 0, vpy, vc->columns, rows);
406     } else {
407         if (diff > 0) {
408             gfx_copyrect(vc_gfx, 0, diff_abs * vc->charh,
409                          vc_gfx->width, (rows - diff_abs) * vc->charh, 0, 0);
410             vc_invalidate(vc, 0, vpy + rows - diff_abs, vc->columns,
411                                  diff_abs);
412         } else {
413             gfx_copyrect(vc_gfx, 0, 0, vc_gfx->width,
414                          (rows - diff_abs) * vc->charh, 0,
415                          diff_abs * vc->charh);
416             vc_invalidate(vc, 0, vpy, vc->columns, diff_abs);
417         }
418     }
419     vc_render(vc);
420 }
421 
vc_scroll_viewport(vc_t * vc,int dir)422 void vc_scroll_viewport(vc_t* vc, int dir) {
423     vc_scroll_viewport_abs(vc, vc->viewport_y + dir);
424 }
425 
vc_scroll_viewport_top(vc_t * vc)426 void vc_scroll_viewport_top(vc_t* vc) {
427     vc_scroll_viewport_abs(vc, INT_MIN);
428 }
429 
vc_scroll_viewport_bottom(vc_t * vc)430 void vc_scroll_viewport_bottom(vc_t* vc) {
431     vc_scroll_viewport_abs(vc, 0);
432 }
433 
vc_set_fullscreen(vc_t * vc,bool fullscreen)434 void vc_set_fullscreen(vc_t* vc, bool fullscreen) {
435     unsigned flags;
436     if (fullscreen) {
437         flags = vc->flags | VC_FLAG_FULLSCREEN;
438     } else {
439         flags = vc->flags & ~VC_FLAG_FULLSCREEN;
440     }
441     if (flags != vc->flags) {
442         vc->flags = flags;
443         tc_seth(&vc->textcon, vc_rows(vc));
444     }
445     vc_render(vc);
446 }
447 
vc_get_font()448 const gfx_font* vc_get_font() {
449     char* fname = getenv("virtcon.font");
450     if (fname) {
451         if (!strcmp(fname, "9x16")) {
452             return &font9x16;
453         } else if (!strcmp(fname, "18x32")) {
454             return &font18x32;
455         } else {
456             printf("gfxconsole: no such font '%s'\n", fname);
457         }
458     }
459     return &font9x16;
460 }
461 
vc_attach_gfx(vc_t * vc)462 void vc_attach_gfx(vc_t* vc) {
463     // If the size of the new gfx console doesn't match what we had been
464     // attached to, we need to allocate new memory and copy the existing
465     // data over.
466     unsigned rows = vc_gfx->height / vc->charh;
467     unsigned columns = vc_gfx->width / vc->charw;
468     if (rows == vc->rows && columns == vc->columns) {
469         return;
470     }
471 
472     // allocate the new buffers
473     vc_char_t* text_buf = reinterpret_cast<vc_char_t*>(
474         calloc(1, rows * columns * sizeof(vc_char_t)));
475     vc_char_t* scrollback_buf = reinterpret_cast<vc_char_t*>(
476         calloc(1, vc->scrollback_rows_max * columns * sizeof(vc_char_t)));
477     if (text_buf && scrollback_buf) {
478         // fill new text buffer with blank characters
479         size_t count = rows * columns;
480         vc_char_t* ptr = text_buf;
481         while (count--) {
482             *ptr++ = vc_char_make(' ', vc->front_color, vc->back_color);
483         }
484 
485         // Copy the most recent data from the old console to the new one. There are
486         // (vc->cursor_y + 1) rows available, and we want (rows - (vc->rows - vc_rows(vc))
487         // rows. Subtract to get the first row index to copy.
488         unsigned old_i = MAX(
489                 static_cast<int>((vc->cursor_y + 1) - (rows - (vc->rows - vc_rows(vc)))), 0);
490         unsigned old_data_start = old_i;
491         unsigned new_i = 0;
492         size_t len = (vc->columns < columns ? vc->columns : columns) * sizeof(vc_char_t);
493         while (new_i < rows && old_i <= vc->cursor_y) {
494             memcpy(text_buf + columns * (new_i++), vc->text_buf + vc->columns * (old_i++), len);
495         }
496 
497         // copy the old scrollback buffer
498         for (int i = 0; i < SCROLLBACK_ROWS; i++) {
499             memcpy(scrollback_buf + columns * i, vc->scrollback_buf + vc->columns * i, len);
500         }
501 
502         vc_char_t* old_text_buf = vc->text_buf;
503         unsigned old_columns = vc->columns;
504         free(vc->scrollback_buf);
505 
506         vc->text_buf = text_buf;
507         vc->scrollback_buf = scrollback_buf;
508         vc->rows = rows;
509         vc->columns = columns;
510 
511         // Push any data that fell off of text_buf. Use a temporary buffer of the
512         // right length to handle going to a wider console. Set it to ' 's before
513         // pushing, so we don't merge data from old rows.
514         if (old_data_start) {
515             vc_char_t buf[columns];
516             for (unsigned i = 0; i < old_data_start; i++) {
517                 vc_char_t* ptr = buf;
518                 while (ptr < buf + columns) {
519                     *ptr++ = vc_char_make(' ', vc->front_color, vc->back_color);
520                 }
521                 ptr = old_text_buf + i * old_columns;
522                 memcpy(buf, ptr, len);
523 
524                 vc_tc_scrollback_buffer_push(vc, buf);
525             }
526         }
527 
528         free(old_text_buf);
529     } else {
530         // If we failed to allocate new buffers, use the old ones as best we can
531         free(text_buf);
532         free(scrollback_buf);
533 
534         vc->rows = MIN(vc->rows, rows);
535         vc->columns = MIN(vc->columns, columns);
536 
537         printf("vc: buffer resize failed, reusing old buffers (%dx%d)\n", vc->rows, vc->columns);
538     }
539 
540     vc->viewport_y = 0;
541     if (vc->cursor_x >= vc->columns) {
542         vc->cursor_x = vc->columns - 1;
543     }
544     if (static_cast<int>(vc->cursor_y) >= vc_rows(vc)) {
545         vc->cursor_y = vc_rows(vc) - 1;
546     }
547 
548     tc_init(&vc->textcon, vc->columns, vc_rows(vc), vc->text_buf,
549             vc->front_color, vc->back_color, vc->cursor_x, vc->cursor_y);
550 }
551 
vc_alloc(vc_t ** out,bool special)552 zx_status_t vc_alloc(vc_t** out, bool special) {
553     vc_t* vc =
554         reinterpret_cast<vc_t*>(calloc(1, sizeof(vc_t)));
555     if (!vc) {
556         return ZX_ERR_NO_MEMORY;
557     }
558     vc->fd = -1;
559 
560     vc->keymap = qwerty_map;
561     char* keys = getenv("virtcon.keymap");
562     if (keys) {
563         if (!strcmp(keys, "qwerty")) {
564             vc->keymap = qwerty_map;
565         } else if (!strcmp(keys, "dvorak")) {
566             vc->keymap = dvorak_map;
567         } else {
568             printf("gfxconsole: no such keymap '%s'\n", keys);
569         }
570     }
571 
572     vc->font = vc_get_font();
573     vc->charw = vc->font->width;
574     vc->charh = vc->font->height;
575 
576     zx_status_t status = vc_setup(vc, special);
577     if (status != ZX_OK) {
578         free(vc);
579         return status;
580     }
581 
582     if (vc_gfx) {
583         vc_attach_gfx(vc);
584     }
585     vc_reset(vc);
586 
587     *out = vc;
588     return ZX_OK;
589 }
590 
vc_free(vc_t * vc)591 void vc_free(vc_t* vc) {
592     if (vc->fd >= 0) {
593         close(vc->fd);
594     }
595     free(vc->text_buf);
596     free(vc->scrollback_buf);
597     free(vc);
598 }
599 
vc_flush(vc_t * vc)600 void vc_flush(vc_t* vc) {
601     if (g_vc_owns_display && vc_gfx && vc->invy1 >= 0) {
602         int rows = vc_rows(vc);
603         // Adjust for the current viewport position.  Convert
604         // console-relative row numbers to screen-relative row numbers.
605         int invalidate_y0 = MIN(vc->invy0 - vc->viewport_y, rows);
606         int invalidate_y1 = MIN(vc->invy1 - vc->viewport_y, rows);
607         vc_gfx_invalidate(vc, 0, invalidate_y0,
608                           vc->columns, invalidate_y1 - invalidate_y0);
609     }
610 }
611 
vc_flush_all(vc_t * vc)612 void vc_flush_all(vc_t* vc) {
613     if (g_vc_owns_display && vc_gfx) {
614         vc_gfx_invalidate_all(vc);
615     }
616 }
617