]>
Commit | Line | Data |
---|---|---|
6f110819 MAL |
1 | /* |
2 | * SPDX-License-Identifier: MIT | |
3 | * QEMU VC | |
4 | */ | |
5 | #include "qemu/osdep.h" | |
6 | ||
7 | #include "chardev/char.h" | |
8 | #include "qapi/error.h" | |
9 | #include "qemu/fifo8.h" | |
10 | #include "qemu/option.h" | |
11 | #include "ui/console.h" | |
12 | ||
13 | #include "trace.h" | |
14 | #include "console-priv.h" | |
15 | ||
16 | #define DEFAULT_BACKSCROLL 512 | |
17 | #define CONSOLE_CURSOR_PERIOD 500 | |
18 | ||
19 | typedef struct TextAttributes { | |
20 | uint8_t fgcol:4; | |
21 | uint8_t bgcol:4; | |
22 | uint8_t bold:1; | |
23 | uint8_t uline:1; | |
24 | uint8_t blink:1; | |
25 | uint8_t invers:1; | |
26 | uint8_t unvisible:1; | |
27 | } TextAttributes; | |
28 | ||
29 | #define TEXT_ATTRIBUTES_DEFAULT ((TextAttributes) { \ | |
30 | .fgcol = QEMU_COLOR_WHITE, \ | |
31 | .bgcol = QEMU_COLOR_BLACK \ | |
32 | }) | |
33 | ||
34 | typedef struct TextCell { | |
35 | uint8_t ch; | |
36 | TextAttributes t_attrib; | |
37 | } TextCell; | |
38 | ||
39 | #define MAX_ESC_PARAMS 3 | |
40 | ||
41 | enum TTYState { | |
42 | TTY_STATE_NORM, | |
43 | TTY_STATE_ESC, | |
44 | TTY_STATE_CSI, | |
45 | }; | |
46 | ||
47 | typedef struct QemuTextConsole { | |
48 | QemuConsole parent; | |
49 | ||
50 | int width; | |
51 | int height; | |
52 | int total_height; | |
53 | int backscroll_height; | |
54 | int x, y; | |
55 | int y_displayed; | |
56 | int y_base; | |
57 | TextCell *cells; | |
58 | int text_x[2], text_y[2], cursor_invalidate; | |
59 | int echo; | |
60 | ||
61 | int update_x0; | |
62 | int update_y0; | |
63 | int update_x1; | |
64 | int update_y1; | |
65 | ||
66 | Chardev *chr; | |
67 | /* fifo for key pressed */ | |
68 | Fifo8 out_fifo; | |
69 | } QemuTextConsole; | |
70 | ||
71 | typedef QemuConsoleClass QemuTextConsoleClass; | |
72 | ||
73 | OBJECT_DEFINE_TYPE(QemuTextConsole, qemu_text_console, QEMU_TEXT_CONSOLE, QEMU_CONSOLE) | |
74 | ||
75 | typedef struct QemuFixedTextConsole { | |
76 | QemuTextConsole parent; | |
77 | } QemuFixedTextConsole; | |
78 | ||
79 | typedef QemuTextConsoleClass QemuFixedTextConsoleClass; | |
80 | ||
81 | OBJECT_DEFINE_TYPE(QemuFixedTextConsole, qemu_fixed_text_console, QEMU_FIXED_TEXT_CONSOLE, QEMU_TEXT_CONSOLE) | |
82 | ||
83 | struct VCChardev { | |
84 | Chardev parent; | |
85 | QemuTextConsole *console; | |
86 | ||
87 | enum TTYState state; | |
88 | int esc_params[MAX_ESC_PARAMS]; | |
89 | int nb_esc_params; | |
90 | TextAttributes t_attrib; /* currently active text attributes */ | |
91 | int x_saved, y_saved; | |
92 | }; | |
93 | typedef struct VCChardev VCChardev; | |
94 | ||
95 | static const pixman_color_t color_table_rgb[2][8] = { | |
96 | { /* dark */ | |
97 | [QEMU_COLOR_BLACK] = QEMU_PIXMAN_COLOR_BLACK, | |
98 | [QEMU_COLOR_BLUE] = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xaa), /* blue */ | |
99 | [QEMU_COLOR_GREEN] = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0x00), /* green */ | |
100 | [QEMU_COLOR_CYAN] = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0xaa), /* cyan */ | |
101 | [QEMU_COLOR_RED] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0x00), /* red */ | |
102 | [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0xaa), /* magenta */ | |
103 | [QEMU_COLOR_YELLOW] = QEMU_PIXMAN_COLOR(0xaa, 0xaa, 0x00), /* yellow */ | |
104 | [QEMU_COLOR_WHITE] = QEMU_PIXMAN_COLOR_GRAY, | |
105 | }, | |
106 | { /* bright */ | |
107 | [QEMU_COLOR_BLACK] = QEMU_PIXMAN_COLOR_BLACK, | |
108 | [QEMU_COLOR_BLUE] = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xff), /* blue */ | |
109 | [QEMU_COLOR_GREEN] = QEMU_PIXMAN_COLOR(0x00, 0xff, 0x00), /* green */ | |
110 | [QEMU_COLOR_CYAN] = QEMU_PIXMAN_COLOR(0x00, 0xff, 0xff), /* cyan */ | |
111 | [QEMU_COLOR_RED] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0x00), /* red */ | |
112 | [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0xff), /* magenta */ | |
113 | [QEMU_COLOR_YELLOW] = QEMU_PIXMAN_COLOR(0xff, 0xff, 0x00), /* yellow */ | |
114 | [QEMU_COLOR_WHITE] = QEMU_PIXMAN_COLOR(0xff, 0xff, 0xff), /* white */ | |
115 | } | |
116 | }; | |
117 | ||
118 | static bool cursor_visible_phase; | |
119 | static QEMUTimer *cursor_timer; | |
120 | ||
121 | const char * | |
122 | qemu_text_console_get_label(QemuTextConsole *c) | |
123 | { | |
124 | return c->chr ? c->chr->label : NULL; | |
125 | } | |
126 | ||
127 | static void qemu_console_fill_rect(QemuConsole *con, int posx, int posy, | |
128 | int width, int height, pixman_color_t color) | |
129 | { | |
130 | DisplaySurface *surface = qemu_console_surface(con); | |
131 | pixman_rectangle16_t rect = { | |
132 | .x = posx, .y = posy, .width = width, .height = height | |
133 | }; | |
134 | ||
135 | assert(surface); | |
136 | pixman_image_fill_rectangles(PIXMAN_OP_SRC, surface->image, | |
137 | &color, 1, &rect); | |
138 | } | |
139 | ||
140 | /* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */ | |
141 | static void qemu_console_bitblt(QemuConsole *con, | |
142 | int xs, int ys, int xd, int yd, int w, int h) | |
143 | { | |
144 | DisplaySurface *surface = qemu_console_surface(con); | |
145 | ||
146 | assert(surface); | |
147 | pixman_image_composite(PIXMAN_OP_SRC, | |
148 | surface->image, NULL, surface->image, | |
149 | xs, ys, 0, 0, xd, yd, w, h); | |
150 | } | |
151 | ||
152 | static void vga_putcharxy(QemuConsole *s, int x, int y, int ch, | |
153 | TextAttributes *t_attrib) | |
154 | { | |
155 | static pixman_image_t *glyphs[256]; | |
156 | DisplaySurface *surface = qemu_console_surface(s); | |
157 | pixman_color_t fgcol, bgcol; | |
158 | ||
159 | assert(surface); | |
160 | if (t_attrib->invers) { | |
161 | bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol]; | |
162 | fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol]; | |
163 | } else { | |
164 | fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol]; | |
165 | bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol]; | |
166 | } | |
167 | ||
168 | if (!glyphs[ch]) { | |
169 | glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch); | |
170 | } | |
171 | qemu_pixman_glyph_render(glyphs[ch], surface->image, | |
172 | &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT); | |
173 | } | |
174 | ||
175 | static void invalidate_xy(QemuTextConsole *s, int x, int y) | |
176 | { | |
177 | if (!qemu_console_is_visible(QEMU_CONSOLE(s))) { | |
178 | return; | |
179 | } | |
180 | if (s->update_x0 > x * FONT_WIDTH) | |
181 | s->update_x0 = x * FONT_WIDTH; | |
182 | if (s->update_y0 > y * FONT_HEIGHT) | |
183 | s->update_y0 = y * FONT_HEIGHT; | |
184 | if (s->update_x1 < (x + 1) * FONT_WIDTH) | |
185 | s->update_x1 = (x + 1) * FONT_WIDTH; | |
186 | if (s->update_y1 < (y + 1) * FONT_HEIGHT) | |
187 | s->update_y1 = (y + 1) * FONT_HEIGHT; | |
188 | } | |
189 | ||
190 | static void console_show_cursor(QemuTextConsole *s, int show) | |
191 | { | |
192 | TextCell *c; | |
193 | int y, y1; | |
194 | int x = s->x; | |
195 | ||
196 | s->cursor_invalidate = 1; | |
197 | ||
198 | if (x >= s->width) { | |
199 | x = s->width - 1; | |
200 | } | |
201 | y1 = (s->y_base + s->y) % s->total_height; | |
202 | y = y1 - s->y_displayed; | |
203 | if (y < 0) { | |
204 | y += s->total_height; | |
205 | } | |
206 | if (y < s->height) { | |
207 | c = &s->cells[y1 * s->width + x]; | |
208 | if (show && cursor_visible_phase) { | |
209 | TextAttributes t_attrib = TEXT_ATTRIBUTES_DEFAULT; | |
210 | t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */ | |
211 | vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &t_attrib); | |
212 | } else { | |
213 | vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &(c->t_attrib)); | |
214 | } | |
215 | invalidate_xy(s, x, y); | |
216 | } | |
217 | } | |
218 | ||
219 | static void console_refresh(QemuTextConsole *s) | |
220 | { | |
221 | DisplaySurface *surface = qemu_console_surface(QEMU_CONSOLE(s)); | |
222 | TextCell *c; | |
223 | int x, y, y1; | |
224 | ||
225 | assert(surface); | |
226 | s->text_x[0] = 0; | |
227 | s->text_y[0] = 0; | |
228 | s->text_x[1] = s->width - 1; | |
229 | s->text_y[1] = s->height - 1; | |
230 | s->cursor_invalidate = 1; | |
231 | ||
232 | qemu_console_fill_rect(QEMU_CONSOLE(s), 0, 0, surface_width(surface), surface_height(surface), | |
233 | color_table_rgb[0][QEMU_COLOR_BLACK]); | |
234 | y1 = s->y_displayed; | |
235 | for (y = 0; y < s->height; y++) { | |
236 | c = s->cells + y1 * s->width; | |
237 | for (x = 0; x < s->width; x++) { | |
238 | vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, | |
239 | &(c->t_attrib)); | |
240 | c++; | |
241 | } | |
242 | if (++y1 == s->total_height) { | |
243 | y1 = 0; | |
244 | } | |
245 | } | |
246 | console_show_cursor(s, 1); | |
247 | dpy_gfx_update(QEMU_CONSOLE(s), 0, 0, | |
248 | surface_width(surface), surface_height(surface)); | |
249 | } | |
250 | ||
251 | static void console_scroll(QemuTextConsole *s, int ydelta) | |
252 | { | |
253 | int i, y1; | |
254 | ||
255 | if (ydelta > 0) { | |
256 | for(i = 0; i < ydelta; i++) { | |
257 | if (s->y_displayed == s->y_base) | |
258 | break; | |
259 | if (++s->y_displayed == s->total_height) | |
260 | s->y_displayed = 0; | |
261 | } | |
262 | } else { | |
263 | ydelta = -ydelta; | |
264 | i = s->backscroll_height; | |
265 | if (i > s->total_height - s->height) | |
266 | i = s->total_height - s->height; | |
267 | y1 = s->y_base - i; | |
268 | if (y1 < 0) | |
269 | y1 += s->total_height; | |
270 | for(i = 0; i < ydelta; i++) { | |
271 | if (s->y_displayed == y1) | |
272 | break; | |
273 | if (--s->y_displayed < 0) | |
274 | s->y_displayed = s->total_height - 1; | |
275 | } | |
276 | } | |
277 | console_refresh(s); | |
278 | } | |
279 | ||
280 | static void kbd_send_chars(QemuTextConsole *s) | |
281 | { | |
282 | uint32_t len, avail; | |
283 | ||
284 | len = qemu_chr_be_can_write(s->chr); | |
285 | avail = fifo8_num_used(&s->out_fifo); | |
286 | while (len > 0 && avail > 0) { | |
287 | const uint8_t *buf; | |
288 | uint32_t size; | |
289 | ||
290 | buf = fifo8_pop_buf(&s->out_fifo, MIN(len, avail), &size); | |
291 | qemu_chr_be_write(s->chr, buf, size); | |
292 | len = qemu_chr_be_can_write(s->chr); | |
293 | avail -= size; | |
294 | } | |
295 | } | |
296 | ||
297 | /* called when an ascii key is pressed */ | |
298 | void qemu_text_console_handle_keysym(QemuTextConsole *s, int keysym) | |
299 | { | |
300 | uint8_t buf[16], *q; | |
301 | int c; | |
302 | uint32_t num_free; | |
303 | ||
304 | switch(keysym) { | |
305 | case QEMU_KEY_CTRL_UP: | |
306 | console_scroll(s, -1); | |
307 | break; | |
308 | case QEMU_KEY_CTRL_DOWN: | |
309 | console_scroll(s, 1); | |
310 | break; | |
311 | case QEMU_KEY_CTRL_PAGEUP: | |
312 | console_scroll(s, -10); | |
313 | break; | |
314 | case QEMU_KEY_CTRL_PAGEDOWN: | |
315 | console_scroll(s, 10); | |
316 | break; | |
317 | default: | |
318 | /* convert the QEMU keysym to VT100 key string */ | |
319 | q = buf; | |
320 | if (keysym >= 0xe100 && keysym <= 0xe11f) { | |
321 | *q++ = '\033'; | |
322 | *q++ = '['; | |
323 | c = keysym - 0xe100; | |
324 | if (c >= 10) | |
325 | *q++ = '0' + (c / 10); | |
326 | *q++ = '0' + (c % 10); | |
327 | *q++ = '~'; | |
328 | } else if (keysym >= 0xe120 && keysym <= 0xe17f) { | |
329 | *q++ = '\033'; | |
330 | *q++ = '['; | |
331 | *q++ = keysym & 0xff; | |
332 | } else if (s->echo && (keysym == '\r' || keysym == '\n')) { | |
333 | qemu_chr_write(s->chr, (uint8_t *)"\r", 1, true); | |
334 | *q++ = '\n'; | |
335 | } else { | |
336 | *q++ = keysym; | |
337 | } | |
338 | if (s->echo) { | |
339 | qemu_chr_write(s->chr, buf, q - buf, true); | |
340 | } | |
341 | num_free = fifo8_num_free(&s->out_fifo); | |
342 | fifo8_push_all(&s->out_fifo, buf, MIN(num_free, q - buf)); | |
343 | kbd_send_chars(s); | |
344 | break; | |
345 | } | |
346 | } | |
347 | ||
348 | static void text_console_update(void *opaque, console_ch_t *chardata) | |
349 | { | |
350 | QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque); | |
351 | int i, j, src; | |
352 | ||
353 | if (s->text_x[0] <= s->text_x[1]) { | |
354 | src = (s->y_base + s->text_y[0]) * s->width; | |
355 | chardata += s->text_y[0] * s->width; | |
356 | for (i = s->text_y[0]; i <= s->text_y[1]; i ++) | |
357 | for (j = 0; j < s->width; j++, src++) { | |
358 | console_write_ch(chardata ++, | |
359 | ATTR2CHTYPE(s->cells[src].ch, | |
360 | s->cells[src].t_attrib.fgcol, | |
361 | s->cells[src].t_attrib.bgcol, | |
362 | s->cells[src].t_attrib.bold)); | |
363 | } | |
364 | dpy_text_update(QEMU_CONSOLE(s), s->text_x[0], s->text_y[0], | |
365 | s->text_x[1] - s->text_x[0], i - s->text_y[0]); | |
366 | s->text_x[0] = s->width; | |
367 | s->text_y[0] = s->height; | |
368 | s->text_x[1] = 0; | |
369 | s->text_y[1] = 0; | |
370 | } | |
371 | if (s->cursor_invalidate) { | |
372 | dpy_text_cursor(QEMU_CONSOLE(s), s->x, s->y); | |
373 | s->cursor_invalidate = 0; | |
374 | } | |
375 | } | |
376 | ||
377 | static void text_console_resize(QemuTextConsole *t) | |
378 | { | |
379 | QemuConsole *s = QEMU_CONSOLE(t); | |
380 | TextCell *cells, *c, *c1; | |
381 | int w1, x, y, last_width, w, h; | |
382 | ||
383 | assert(s->scanout.kind == SCANOUT_SURFACE); | |
384 | ||
385 | w = surface_width(s->surface) / FONT_WIDTH; | |
386 | h = surface_height(s->surface) / FONT_HEIGHT; | |
387 | if (w == t->width && h == t->height) { | |
388 | return; | |
389 | } | |
390 | ||
391 | last_width = t->width; | |
392 | t->width = w; | |
393 | t->height = h; | |
394 | ||
395 | w1 = MIN(t->width, last_width); | |
396 | ||
397 | cells = g_new(TextCell, t->width * t->total_height + 1); | |
398 | for (y = 0; y < t->total_height; y++) { | |
399 | c = &cells[y * t->width]; | |
400 | if (w1 > 0) { | |
401 | c1 = &t->cells[y * last_width]; | |
402 | for (x = 0; x < w1; x++) { | |
403 | *c++ = *c1++; | |
404 | } | |
405 | } | |
406 | for (x = w1; x < t->width; x++) { | |
407 | c->ch = ' '; | |
408 | c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | |
409 | c++; | |
410 | } | |
411 | } | |
412 | g_free(t->cells); | |
413 | t->cells = cells; | |
414 | } | |
415 | ||
416 | static void vc_put_lf(VCChardev *vc) | |
417 | { | |
418 | QemuTextConsole *s = vc->console; | |
419 | TextCell *c; | |
420 | int x, y1; | |
421 | ||
422 | s->y++; | |
423 | if (s->y >= s->height) { | |
424 | s->y = s->height - 1; | |
425 | ||
426 | if (s->y_displayed == s->y_base) { | |
427 | if (++s->y_displayed == s->total_height) | |
428 | s->y_displayed = 0; | |
429 | } | |
430 | if (++s->y_base == s->total_height) | |
431 | s->y_base = 0; | |
432 | if (s->backscroll_height < s->total_height) | |
433 | s->backscroll_height++; | |
434 | y1 = (s->y_base + s->height - 1) % s->total_height; | |
435 | c = &s->cells[y1 * s->width]; | |
436 | for(x = 0; x < s->width; x++) { | |
437 | c->ch = ' '; | |
438 | c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | |
439 | c++; | |
440 | } | |
441 | if (s->y_displayed == s->y_base) { | |
442 | s->text_x[0] = 0; | |
443 | s->text_y[0] = 0; | |
444 | s->text_x[1] = s->width - 1; | |
445 | s->text_y[1] = s->height - 1; | |
446 | ||
447 | qemu_console_bitblt(QEMU_CONSOLE(s), 0, FONT_HEIGHT, 0, 0, | |
448 | s->width * FONT_WIDTH, | |
449 | (s->height - 1) * FONT_HEIGHT); | |
450 | qemu_console_fill_rect(QEMU_CONSOLE(s), 0, (s->height - 1) * FONT_HEIGHT, | |
451 | s->width * FONT_WIDTH, FONT_HEIGHT, | |
452 | color_table_rgb[0][TEXT_ATTRIBUTES_DEFAULT.bgcol]); | |
453 | s->update_x0 = 0; | |
454 | s->update_y0 = 0; | |
455 | s->update_x1 = s->width * FONT_WIDTH; | |
456 | s->update_y1 = s->height * FONT_HEIGHT; | |
457 | } | |
458 | } | |
459 | } | |
460 | ||
461 | /* Set console attributes depending on the current escape codes. | |
462 | * NOTE: I know this code is not very efficient (checking every color for it | |
463 | * self) but it is more readable and better maintainable. | |
464 | */ | |
465 | static void vc_handle_escape(VCChardev *vc) | |
466 | { | |
467 | int i; | |
468 | ||
469 | for (i = 0; i < vc->nb_esc_params; i++) { | |
470 | switch (vc->esc_params[i]) { | |
471 | case 0: /* reset all console attributes to default */ | |
472 | vc->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | |
473 | break; | |
474 | case 1: | |
475 | vc->t_attrib.bold = 1; | |
476 | break; | |
477 | case 4: | |
478 | vc->t_attrib.uline = 1; | |
479 | break; | |
480 | case 5: | |
481 | vc->t_attrib.blink = 1; | |
482 | break; | |
483 | case 7: | |
484 | vc->t_attrib.invers = 1; | |
485 | break; | |
486 | case 8: | |
487 | vc->t_attrib.unvisible = 1; | |
488 | break; | |
489 | case 22: | |
490 | vc->t_attrib.bold = 0; | |
491 | break; | |
492 | case 24: | |
493 | vc->t_attrib.uline = 0; | |
494 | break; | |
495 | case 25: | |
496 | vc->t_attrib.blink = 0; | |
497 | break; | |
498 | case 27: | |
499 | vc->t_attrib.invers = 0; | |
500 | break; | |
501 | case 28: | |
502 | vc->t_attrib.unvisible = 0; | |
503 | break; | |
504 | /* set foreground color */ | |
505 | case 30: | |
506 | vc->t_attrib.fgcol = QEMU_COLOR_BLACK; | |
507 | break; | |
508 | case 31: | |
509 | vc->t_attrib.fgcol = QEMU_COLOR_RED; | |
510 | break; | |
511 | case 32: | |
512 | vc->t_attrib.fgcol = QEMU_COLOR_GREEN; | |
513 | break; | |
514 | case 33: | |
515 | vc->t_attrib.fgcol = QEMU_COLOR_YELLOW; | |
516 | break; | |
517 | case 34: | |
518 | vc->t_attrib.fgcol = QEMU_COLOR_BLUE; | |
519 | break; | |
520 | case 35: | |
521 | vc->t_attrib.fgcol = QEMU_COLOR_MAGENTA; | |
522 | break; | |
523 | case 36: | |
524 | vc->t_attrib.fgcol = QEMU_COLOR_CYAN; | |
525 | break; | |
526 | case 37: | |
527 | vc->t_attrib.fgcol = QEMU_COLOR_WHITE; | |
528 | break; | |
529 | /* set background color */ | |
530 | case 40: | |
531 | vc->t_attrib.bgcol = QEMU_COLOR_BLACK; | |
532 | break; | |
533 | case 41: | |
534 | vc->t_attrib.bgcol = QEMU_COLOR_RED; | |
535 | break; | |
536 | case 42: | |
537 | vc->t_attrib.bgcol = QEMU_COLOR_GREEN; | |
538 | break; | |
539 | case 43: | |
540 | vc->t_attrib.bgcol = QEMU_COLOR_YELLOW; | |
541 | break; | |
542 | case 44: | |
543 | vc->t_attrib.bgcol = QEMU_COLOR_BLUE; | |
544 | break; | |
545 | case 45: | |
546 | vc->t_attrib.bgcol = QEMU_COLOR_MAGENTA; | |
547 | break; | |
548 | case 46: | |
549 | vc->t_attrib.bgcol = QEMU_COLOR_CYAN; | |
550 | break; | |
551 | case 47: | |
552 | vc->t_attrib.bgcol = QEMU_COLOR_WHITE; | |
553 | break; | |
554 | } | |
555 | } | |
556 | } | |
557 | ||
558 | static void vc_update_xy(VCChardev *vc, int x, int y) | |
559 | { | |
560 | QemuTextConsole *s = vc->console; | |
561 | TextCell *c; | |
562 | int y1, y2; | |
563 | ||
564 | s->text_x[0] = MIN(s->text_x[0], x); | |
565 | s->text_x[1] = MAX(s->text_x[1], x); | |
566 | s->text_y[0] = MIN(s->text_y[0], y); | |
567 | s->text_y[1] = MAX(s->text_y[1], y); | |
568 | ||
569 | y1 = (s->y_base + y) % s->total_height; | |
570 | y2 = y1 - s->y_displayed; | |
571 | if (y2 < 0) { | |
572 | y2 += s->total_height; | |
573 | } | |
574 | if (y2 < s->height) { | |
575 | if (x >= s->width) { | |
576 | x = s->width - 1; | |
577 | } | |
578 | c = &s->cells[y1 * s->width + x]; | |
579 | vga_putcharxy(QEMU_CONSOLE(s), x, y2, c->ch, | |
580 | &(c->t_attrib)); | |
581 | invalidate_xy(s, x, y2); | |
582 | } | |
583 | } | |
584 | ||
585 | static void vc_clear_xy(VCChardev *vc, int x, int y) | |
586 | { | |
587 | QemuTextConsole *s = vc->console; | |
588 | int y1 = (s->y_base + y) % s->total_height; | |
589 | if (x >= s->width) { | |
590 | x = s->width - 1; | |
591 | } | |
592 | TextCell *c = &s->cells[y1 * s->width + x]; | |
593 | c->ch = ' '; | |
594 | c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | |
595 | vc_update_xy(vc, x, y); | |
596 | } | |
597 | ||
598 | static void vc_put_one(VCChardev *vc, int ch) | |
599 | { | |
600 | QemuTextConsole *s = vc->console; | |
601 | TextCell *c; | |
602 | int y1; | |
603 | if (s->x >= s->width) { | |
604 | /* line wrap */ | |
605 | s->x = 0; | |
606 | vc_put_lf(vc); | |
607 | } | |
608 | y1 = (s->y_base + s->y) % s->total_height; | |
609 | c = &s->cells[y1 * s->width + s->x]; | |
610 | c->ch = ch; | |
611 | c->t_attrib = vc->t_attrib; | |
612 | vc_update_xy(vc, s->x, s->y); | |
613 | s->x++; | |
614 | } | |
615 | ||
616 | static void vc_respond_str(VCChardev *vc, const char *buf) | |
617 | { | |
618 | while (*buf) { | |
619 | vc_put_one(vc, *buf); | |
620 | buf++; | |
621 | } | |
622 | } | |
623 | ||
624 | /* set cursor, checking bounds */ | |
625 | static void vc_set_cursor(VCChardev *vc, int x, int y) | |
626 | { | |
627 | QemuTextConsole *s = vc->console; | |
628 | ||
629 | if (x < 0) { | |
630 | x = 0; | |
631 | } | |
632 | if (y < 0) { | |
633 | y = 0; | |
634 | } | |
635 | if (y >= s->height) { | |
636 | y = s->height - 1; | |
637 | } | |
638 | if (x >= s->width) { | |
639 | x = s->width - 1; | |
640 | } | |
641 | ||
642 | s->x = x; | |
643 | s->y = y; | |
644 | } | |
645 | ||
646 | static void vc_putchar(VCChardev *vc, int ch) | |
647 | { | |
648 | QemuTextConsole *s = vc->console; | |
649 | int i; | |
650 | int x, y; | |
651 | char response[40]; | |
652 | ||
653 | switch(vc->state) { | |
654 | case TTY_STATE_NORM: | |
655 | switch(ch) { | |
656 | case '\r': /* carriage return */ | |
657 | s->x = 0; | |
658 | break; | |
659 | case '\n': /* newline */ | |
660 | vc_put_lf(vc); | |
661 | break; | |
662 | case '\b': /* backspace */ | |
663 | if (s->x > 0) | |
664 | s->x--; | |
665 | break; | |
666 | case '\t': /* tabspace */ | |
667 | if (s->x + (8 - (s->x % 8)) > s->width) { | |
668 | s->x = 0; | |
669 | vc_put_lf(vc); | |
670 | } else { | |
671 | s->x = s->x + (8 - (s->x % 8)); | |
672 | } | |
673 | break; | |
674 | case '\a': /* alert aka. bell */ | |
675 | /* TODO: has to be implemented */ | |
676 | break; | |
677 | case 14: | |
678 | /* SI (shift in), character set 0 (ignored) */ | |
679 | break; | |
680 | case 15: | |
681 | /* SO (shift out), character set 1 (ignored) */ | |
682 | break; | |
683 | case 27: /* esc (introducing an escape sequence) */ | |
684 | vc->state = TTY_STATE_ESC; | |
685 | break; | |
686 | default: | |
687 | vc_put_one(vc, ch); | |
688 | break; | |
689 | } | |
690 | break; | |
691 | case TTY_STATE_ESC: /* check if it is a terminal escape sequence */ | |
692 | if (ch == '[') { | |
693 | for(i=0;i<MAX_ESC_PARAMS;i++) | |
694 | vc->esc_params[i] = 0; | |
695 | vc->nb_esc_params = 0; | |
696 | vc->state = TTY_STATE_CSI; | |
697 | } else { | |
698 | vc->state = TTY_STATE_NORM; | |
699 | } | |
700 | break; | |
701 | case TTY_STATE_CSI: /* handle escape sequence parameters */ | |
702 | if (ch >= '0' && ch <= '9') { | |
703 | if (vc->nb_esc_params < MAX_ESC_PARAMS) { | |
704 | int *param = &vc->esc_params[vc->nb_esc_params]; | |
705 | int digit = (ch - '0'); | |
706 | ||
707 | *param = (*param <= (INT_MAX - digit) / 10) ? | |
708 | *param * 10 + digit : INT_MAX; | |
709 | } | |
710 | } else { | |
711 | if (vc->nb_esc_params < MAX_ESC_PARAMS) | |
712 | vc->nb_esc_params++; | |
713 | if (ch == ';' || ch == '?') { | |
714 | break; | |
715 | } | |
716 | trace_console_putchar_csi(vc->esc_params[0], vc->esc_params[1], | |
717 | ch, vc->nb_esc_params); | |
718 | vc->state = TTY_STATE_NORM; | |
719 | switch(ch) { | |
720 | case 'A': | |
721 | /* move cursor up */ | |
722 | if (vc->esc_params[0] == 0) { | |
723 | vc->esc_params[0] = 1; | |
724 | } | |
725 | vc_set_cursor(vc, s->x, s->y - vc->esc_params[0]); | |
726 | break; | |
727 | case 'B': | |
728 | /* move cursor down */ | |
729 | if (vc->esc_params[0] == 0) { | |
730 | vc->esc_params[0] = 1; | |
731 | } | |
732 | vc_set_cursor(vc, s->x, s->y + vc->esc_params[0]); | |
733 | break; | |
734 | case 'C': | |
735 | /* move cursor right */ | |
736 | if (vc->esc_params[0] == 0) { | |
737 | vc->esc_params[0] = 1; | |
738 | } | |
739 | vc_set_cursor(vc, s->x + vc->esc_params[0], s->y); | |
740 | break; | |
741 | case 'D': | |
742 | /* move cursor left */ | |
743 | if (vc->esc_params[0] == 0) { | |
744 | vc->esc_params[0] = 1; | |
745 | } | |
746 | vc_set_cursor(vc, s->x - vc->esc_params[0], s->y); | |
747 | break; | |
748 | case 'G': | |
749 | /* move cursor to column */ | |
750 | vc_set_cursor(vc, vc->esc_params[0] - 1, s->y); | |
751 | break; | |
752 | case 'f': | |
753 | case 'H': | |
754 | /* move cursor to row, column */ | |
755 | vc_set_cursor(vc, vc->esc_params[1] - 1, vc->esc_params[0] - 1); | |
756 | break; | |
757 | case 'J': | |
758 | switch (vc->esc_params[0]) { | |
759 | case 0: | |
760 | /* clear to end of screen */ | |
761 | for (y = s->y; y < s->height; y++) { | |
762 | for (x = 0; x < s->width; x++) { | |
763 | if (y == s->y && x < s->x) { | |
764 | continue; | |
765 | } | |
766 | vc_clear_xy(vc, x, y); | |
767 | } | |
768 | } | |
769 | break; | |
770 | case 1: | |
771 | /* clear from beginning of screen */ | |
772 | for (y = 0; y <= s->y; y++) { | |
773 | for (x = 0; x < s->width; x++) { | |
774 | if (y == s->y && x > s->x) { | |
775 | break; | |
776 | } | |
777 | vc_clear_xy(vc, x, y); | |
778 | } | |
779 | } | |
780 | break; | |
781 | case 2: | |
782 | /* clear entire screen */ | |
783 | for (y = 0; y <= s->height; y++) { | |
784 | for (x = 0; x < s->width; x++) { | |
785 | vc_clear_xy(vc, x, y); | |
786 | } | |
787 | } | |
788 | break; | |
789 | } | |
790 | break; | |
791 | case 'K': | |
792 | switch (vc->esc_params[0]) { | |
793 | case 0: | |
794 | /* clear to eol */ | |
795 | for(x = s->x; x < s->width; x++) { | |
796 | vc_clear_xy(vc, x, s->y); | |
797 | } | |
798 | break; | |
799 | case 1: | |
800 | /* clear from beginning of line */ | |
801 | for (x = 0; x <= s->x && x < s->width; x++) { | |
802 | vc_clear_xy(vc, x, s->y); | |
803 | } | |
804 | break; | |
805 | case 2: | |
806 | /* clear entire line */ | |
807 | for(x = 0; x < s->width; x++) { | |
808 | vc_clear_xy(vc, x, s->y); | |
809 | } | |
810 | break; | |
811 | } | |
812 | break; | |
813 | case 'm': | |
814 | vc_handle_escape(vc); | |
815 | break; | |
816 | case 'n': | |
817 | switch (vc->esc_params[0]) { | |
818 | case 5: | |
819 | /* report console status (always succeed)*/ | |
820 | vc_respond_str(vc, "\033[0n"); | |
821 | break; | |
822 | case 6: | |
823 | /* report cursor position */ | |
824 | sprintf(response, "\033[%d;%dR", | |
825 | (s->y_base + s->y) % s->total_height + 1, | |
826 | s->x + 1); | |
827 | vc_respond_str(vc, response); | |
828 | break; | |
829 | } | |
830 | break; | |
831 | case 's': | |
832 | /* save cursor position */ | |
833 | vc->x_saved = s->x; | |
834 | vc->y_saved = s->y; | |
835 | break; | |
836 | case 'u': | |
837 | /* restore cursor position */ | |
838 | s->x = vc->x_saved; | |
839 | s->y = vc->y_saved; | |
840 | break; | |
841 | default: | |
842 | trace_console_putchar_unhandled(ch); | |
843 | break; | |
844 | } | |
845 | break; | |
846 | } | |
847 | } | |
848 | } | |
849 | ||
850 | #define TYPE_CHARDEV_VC "chardev-vc" | |
851 | DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV, | |
852 | TYPE_CHARDEV_VC) | |
853 | ||
854 | static int vc_chr_write(Chardev *chr, const uint8_t *buf, int len) | |
855 | { | |
856 | VCChardev *drv = VC_CHARDEV(chr); | |
857 | QemuTextConsole *s = drv->console; | |
858 | int i; | |
859 | ||
860 | s->update_x0 = s->width * FONT_WIDTH; | |
861 | s->update_y0 = s->height * FONT_HEIGHT; | |
862 | s->update_x1 = 0; | |
863 | s->update_y1 = 0; | |
864 | console_show_cursor(s, 0); | |
865 | for(i = 0; i < len; i++) { | |
866 | vc_putchar(drv, buf[i]); | |
867 | } | |
868 | console_show_cursor(s, 1); | |
869 | if (s->update_x0 < s->update_x1) { | |
870 | dpy_gfx_update(QEMU_CONSOLE(s), s->update_x0, s->update_y0, | |
871 | s->update_x1 - s->update_x0, | |
872 | s->update_y1 - s->update_y0); | |
873 | } | |
874 | return len; | |
875 | } | |
876 | ||
877 | void qemu_text_console_update_cursor(void) | |
878 | { | |
879 | cursor_visible_phase = !cursor_visible_phase; | |
880 | ||
881 | if (qemu_invalidate_text_consoles()) { | |
882 | timer_mod(cursor_timer, | |
883 | qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2); | |
884 | } | |
885 | } | |
886 | ||
887 | static void | |
888 | cursor_timer_cb(void *opaque) | |
889 | { | |
890 | qemu_text_console_update_cursor(); | |
891 | } | |
892 | ||
893 | static void text_console_invalidate(void *opaque) | |
894 | { | |
895 | QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque); | |
896 | ||
897 | if (!QEMU_IS_FIXED_TEXT_CONSOLE(s)) { | |
898 | text_console_resize(QEMU_TEXT_CONSOLE(s)); | |
899 | } | |
900 | console_refresh(s); | |
901 | } | |
902 | ||
903 | static void | |
904 | qemu_text_console_finalize(Object *obj) | |
905 | { | |
906 | } | |
907 | ||
908 | static void | |
909 | qemu_text_console_class_init(ObjectClass *oc, void *data) | |
910 | { | |
911 | if (!cursor_timer) { | |
912 | cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL); | |
913 | } | |
914 | } | |
915 | ||
916 | static const GraphicHwOps text_console_ops = { | |
917 | .invalidate = text_console_invalidate, | |
918 | .text_update = text_console_update, | |
919 | }; | |
920 | ||
921 | static void | |
922 | qemu_text_console_init(Object *obj) | |
923 | { | |
924 | QemuTextConsole *c = QEMU_TEXT_CONSOLE(obj); | |
925 | ||
926 | fifo8_create(&c->out_fifo, 16); | |
927 | c->total_height = DEFAULT_BACKSCROLL; | |
928 | QEMU_CONSOLE(c)->hw_ops = &text_console_ops; | |
929 | QEMU_CONSOLE(c)->hw = c; | |
930 | } | |
931 | ||
932 | static void | |
933 | qemu_fixed_text_console_finalize(Object *obj) | |
934 | { | |
935 | } | |
936 | ||
937 | static void | |
938 | qemu_fixed_text_console_class_init(ObjectClass *oc, void *data) | |
939 | { | |
940 | } | |
941 | ||
942 | static void | |
943 | qemu_fixed_text_console_init(Object *obj) | |
944 | { | |
945 | } | |
946 | ||
947 | static void vc_chr_accept_input(Chardev *chr) | |
948 | { | |
949 | VCChardev *drv = VC_CHARDEV(chr); | |
950 | ||
951 | kbd_send_chars(drv->console); | |
952 | } | |
953 | ||
954 | static void vc_chr_set_echo(Chardev *chr, bool echo) | |
955 | { | |
956 | VCChardev *drv = VC_CHARDEV(chr); | |
957 | ||
958 | drv->console->echo = echo; | |
959 | } | |
960 | ||
961 | void qemu_text_console_select(QemuTextConsole *c) | |
962 | { | |
963 | dpy_text_resize(QEMU_CONSOLE(c), c->width, c->height); | |
964 | qemu_text_console_update_cursor(); | |
965 | } | |
966 | ||
967 | static void vc_chr_open(Chardev *chr, | |
968 | ChardevBackend *backend, | |
969 | bool *be_opened, | |
970 | Error **errp) | |
971 | { | |
972 | ChardevVC *vc = backend->u.vc.data; | |
973 | VCChardev *drv = VC_CHARDEV(chr); | |
974 | QemuTextConsole *s; | |
975 | unsigned width = 0; | |
976 | unsigned height = 0; | |
977 | ||
978 | if (vc->has_width) { | |
979 | width = vc->width; | |
980 | } else if (vc->has_cols) { | |
981 | width = vc->cols * FONT_WIDTH; | |
982 | } | |
983 | ||
984 | if (vc->has_height) { | |
985 | height = vc->height; | |
986 | } else if (vc->has_rows) { | |
987 | height = vc->rows * FONT_HEIGHT; | |
988 | } | |
989 | ||
990 | trace_console_txt_new(width, height); | |
991 | if (width == 0 || height == 0) { | |
992 | s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_TEXT_CONSOLE)); | |
993 | width = qemu_console_get_width(NULL, 80 * FONT_WIDTH); | |
994 | height = qemu_console_get_height(NULL, 24 * FONT_HEIGHT); | |
995 | } else { | |
996 | s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_FIXED_TEXT_CONSOLE)); | |
997 | } | |
998 | ||
999 | dpy_gfx_replace_surface(QEMU_CONSOLE(s), qemu_create_displaysurface(width, height)); | |
1000 | ||
1001 | s->chr = chr; | |
1002 | drv->console = s; | |
1003 | ||
1004 | /* set current text attributes to default */ | |
1005 | drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | |
1006 | text_console_resize(s); | |
1007 | ||
1008 | if (chr->label) { | |
1009 | char *msg; | |
1010 | ||
1011 | drv->t_attrib.bgcol = QEMU_COLOR_BLUE; | |
1012 | msg = g_strdup_printf("%s console\r\n", chr->label); | |
1013 | qemu_chr_write(chr, (uint8_t *)msg, strlen(msg), true); | |
1014 | g_free(msg); | |
1015 | drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | |
1016 | } | |
1017 | ||
1018 | *be_opened = true; | |
1019 | } | |
1020 | ||
1021 | static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp) | |
1022 | { | |
1023 | int val; | |
1024 | ChardevVC *vc; | |
1025 | ||
1026 | backend->type = CHARDEV_BACKEND_KIND_VC; | |
1027 | vc = backend->u.vc.data = g_new0(ChardevVC, 1); | |
1028 | qemu_chr_parse_common(opts, qapi_ChardevVC_base(vc)); | |
1029 | ||
1030 | val = qemu_opt_get_number(opts, "width", 0); | |
1031 | if (val != 0) { | |
1032 | vc->has_width = true; | |
1033 | vc->width = val; | |
1034 | } | |
1035 | ||
1036 | val = qemu_opt_get_number(opts, "height", 0); | |
1037 | if (val != 0) { | |
1038 | vc->has_height = true; | |
1039 | vc->height = val; | |
1040 | } | |
1041 | ||
1042 | val = qemu_opt_get_number(opts, "cols", 0); | |
1043 | if (val != 0) { | |
1044 | vc->has_cols = true; | |
1045 | vc->cols = val; | |
1046 | } | |
1047 | ||
1048 | val = qemu_opt_get_number(opts, "rows", 0); | |
1049 | if (val != 0) { | |
1050 | vc->has_rows = true; | |
1051 | vc->rows = val; | |
1052 | } | |
1053 | } | |
1054 | ||
1055 | static void char_vc_class_init(ObjectClass *oc, void *data) | |
1056 | { | |
1057 | ChardevClass *cc = CHARDEV_CLASS(oc); | |
1058 | ||
1059 | cc->parse = vc_chr_parse; | |
1060 | cc->open = vc_chr_open; | |
1061 | cc->chr_write = vc_chr_write; | |
1062 | cc->chr_accept_input = vc_chr_accept_input; | |
1063 | cc->chr_set_echo = vc_chr_set_echo; | |
1064 | } | |
1065 | ||
1066 | static const TypeInfo char_vc_type_info = { | |
1067 | .name = TYPE_CHARDEV_VC, | |
1068 | .parent = TYPE_CHARDEV, | |
1069 | .instance_size = sizeof(VCChardev), | |
1070 | .class_init = char_vc_class_init, | |
1071 | }; | |
1072 | ||
1073 | void qemu_console_early_init(void) | |
1074 | { | |
1075 | /* set the default vc driver */ | |
1076 | if (!object_class_by_name(TYPE_CHARDEV_VC)) { | |
1077 | type_register(&char_vc_type_info); | |
1078 | } | |
1079 | } |