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