qemu/ui/console-vc.c
Roman Penyaev a97ef36244 ui/console-vc: implement DCH (delete) and ICH (insert) commands
This patch implements DCH (delete character) and ICH (insert
character) commands.

DCH - Delete Character:
   "As characters are deleted, the remaining characters between the
    cursor and right margin move to the left. Character attributes move
    with the characters. The terminal adds blank spaces with no visual
    character attributes at the right margin. DCH has no effect outside
    the scrolling margins" [1].

ICH - Insert Character:
   "The ICH sequence inserts Pn blank characters with the normal
    character attribute. The cursor remains at the beginning of the
    blank characters. Text between the cursor and right margin moves to
    the right. Characters scrolled past the right margin are lost. ICH
    has no effect outside the scrolling margins" [2].

Without these commands console is barely usable.

[1] https://vt100.net/docs/vt510-rm/DCH.html
[1] https://vt100.net/docs/vt510-rm/ICH.html

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20250226075913.353676-6-r.peniaev@gmail.com>
2025-03-05 09:34:14 +04:00

1208 lines
34 KiB
C

/*
* SPDX-License-Identifier: MIT
* QEMU VC
*/
#include "qemu/osdep.h"
#include "chardev/char.h"
#include "qapi/error.h"
#include "qemu/fifo8.h"
#include "qemu/option.h"
#include "ui/console.h"
#include "trace.h"
#include "console-priv.h"
#define DEFAULT_BACKSCROLL 512
#define CONSOLE_CURSOR_PERIOD 500
typedef struct TextAttributes {
uint8_t fgcol:4;
uint8_t bgcol:4;
uint8_t bold:1;
uint8_t uline:1;
uint8_t blink:1;
uint8_t invers:1;
uint8_t unvisible:1;
} TextAttributes;
#define TEXT_ATTRIBUTES_DEFAULT ((TextAttributes) { \
.fgcol = QEMU_COLOR_WHITE, \
.bgcol = QEMU_COLOR_BLACK \
})
typedef struct TextCell {
uint8_t ch;
TextAttributes t_attrib;
} TextCell;
#define MAX_ESC_PARAMS 3
enum TTYState {
TTY_STATE_NORM,
TTY_STATE_ESC,
TTY_STATE_CSI,
TTY_STATE_G0,
TTY_STATE_G1,
};
typedef struct QemuTextConsole {
QemuConsole parent;
int width;
int height;
int total_height;
int backscroll_height;
int x, y;
int y_displayed;
int y_base;
TextCell *cells;
int text_x[2], text_y[2], cursor_invalidate;
int echo;
int update_x0;
int update_y0;
int update_x1;
int update_y1;
Chardev *chr;
/* fifo for key pressed */
Fifo8 out_fifo;
} QemuTextConsole;
typedef QemuConsoleClass QemuTextConsoleClass;
OBJECT_DEFINE_TYPE(QemuTextConsole, qemu_text_console, QEMU_TEXT_CONSOLE, QEMU_CONSOLE)
typedef struct QemuFixedTextConsole {
QemuTextConsole parent;
} QemuFixedTextConsole;
typedef QemuTextConsoleClass QemuFixedTextConsoleClass;
OBJECT_DEFINE_TYPE(QemuFixedTextConsole, qemu_fixed_text_console, QEMU_FIXED_TEXT_CONSOLE, QEMU_TEXT_CONSOLE)
struct VCChardev {
Chardev parent;
QemuTextConsole *console;
enum TTYState state;
int esc_params[MAX_ESC_PARAMS];
int nb_esc_params;
TextAttributes t_attrib; /* currently active text attributes */
TextAttributes t_attrib_saved;
int x_saved, y_saved;
};
typedef struct VCChardev VCChardev;
static const pixman_color_t color_table_rgb[2][8] = {
{ /* dark */
[QEMU_COLOR_BLACK] = QEMU_PIXMAN_COLOR_BLACK,
[QEMU_COLOR_BLUE] = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xaa), /* blue */
[QEMU_COLOR_GREEN] = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0x00), /* green */
[QEMU_COLOR_CYAN] = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0xaa), /* cyan */
[QEMU_COLOR_RED] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0x00), /* red */
[QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0xaa), /* magenta */
[QEMU_COLOR_YELLOW] = QEMU_PIXMAN_COLOR(0xaa, 0xaa, 0x00), /* yellow */
[QEMU_COLOR_WHITE] = QEMU_PIXMAN_COLOR_GRAY,
},
{ /* bright */
[QEMU_COLOR_BLACK] = QEMU_PIXMAN_COLOR_BLACK,
[QEMU_COLOR_BLUE] = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xff), /* blue */
[QEMU_COLOR_GREEN] = QEMU_PIXMAN_COLOR(0x00, 0xff, 0x00), /* green */
[QEMU_COLOR_CYAN] = QEMU_PIXMAN_COLOR(0x00, 0xff, 0xff), /* cyan */
[QEMU_COLOR_RED] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0x00), /* red */
[QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0xff), /* magenta */
[QEMU_COLOR_YELLOW] = QEMU_PIXMAN_COLOR(0xff, 0xff, 0x00), /* yellow */
[QEMU_COLOR_WHITE] = QEMU_PIXMAN_COLOR(0xff, 0xff, 0xff), /* white */
}
};
static bool cursor_visible_phase;
static QEMUTimer *cursor_timer;
const char *
qemu_text_console_get_label(QemuTextConsole *c)
{
return c->chr ? c->chr->label : NULL;
}
static void qemu_console_fill_rect(QemuConsole *con, int posx, int posy,
int width, int height, pixman_color_t color)
{
DisplaySurface *surface = qemu_console_surface(con);
pixman_rectangle16_t rect = {
.x = posx, .y = posy, .width = width, .height = height
};
assert(surface);
pixman_image_fill_rectangles(PIXMAN_OP_SRC, surface->image,
&color, 1, &rect);
}
/* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */
static void qemu_console_bitblt(QemuConsole *con,
int xs, int ys, int xd, int yd, int w, int h)
{
DisplaySurface *surface = qemu_console_surface(con);
assert(surface);
pixman_image_composite(PIXMAN_OP_SRC,
surface->image, NULL, surface->image,
xs, ys, 0, 0, xd, yd, w, h);
}
static void vga_putcharxy(QemuConsole *s, int x, int y, int ch,
TextAttributes *t_attrib)
{
static pixman_image_t *glyphs[256];
DisplaySurface *surface = qemu_console_surface(s);
pixman_color_t fgcol, bgcol;
assert(surface);
if (t_attrib->invers) {
bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
} else {
fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
}
if (!glyphs[ch]) {
glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch);
}
qemu_pixman_glyph_render(glyphs[ch], surface->image,
&fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT);
}
static void invalidate_xy(QemuTextConsole *s, int x, int y)
{
if (!qemu_console_is_visible(QEMU_CONSOLE(s))) {
return;
}
if (s->update_x0 > x * FONT_WIDTH)
s->update_x0 = x * FONT_WIDTH;
if (s->update_y0 > y * FONT_HEIGHT)
s->update_y0 = y * FONT_HEIGHT;
if (s->update_x1 < (x + 1) * FONT_WIDTH)
s->update_x1 = (x + 1) * FONT_WIDTH;
if (s->update_y1 < (y + 1) * FONT_HEIGHT)
s->update_y1 = (y + 1) * FONT_HEIGHT;
}
static void console_show_cursor(QemuTextConsole *s, int show)
{
TextCell *c;
int y, y1;
int x = s->x;
s->cursor_invalidate = 1;
if (x >= s->width) {
x = s->width - 1;
}
y1 = (s->y_base + s->y) % s->total_height;
y = y1 - s->y_displayed;
if (y < 0) {
y += s->total_height;
}
if (y < s->height) {
c = &s->cells[y1 * s->width + x];
if (show && cursor_visible_phase) {
TextAttributes t_attrib = TEXT_ATTRIBUTES_DEFAULT;
t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */
vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &t_attrib);
} else {
vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &(c->t_attrib));
}
invalidate_xy(s, x, y);
}
}
static void console_refresh(QemuTextConsole *s)
{
DisplaySurface *surface = qemu_console_surface(QEMU_CONSOLE(s));
TextCell *c;
int x, y, y1;
assert(surface);
s->text_x[0] = 0;
s->text_y[0] = 0;
s->text_x[1] = s->width - 1;
s->text_y[1] = s->height - 1;
s->cursor_invalidate = 1;
qemu_console_fill_rect(QEMU_CONSOLE(s), 0, 0, surface_width(surface), surface_height(surface),
color_table_rgb[0][QEMU_COLOR_BLACK]);
y1 = s->y_displayed;
for (y = 0; y < s->height; y++) {
c = s->cells + y1 * s->width;
for (x = 0; x < s->width; x++) {
vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch,
&(c->t_attrib));
c++;
}
if (++y1 == s->total_height) {
y1 = 0;
}
}
console_show_cursor(s, 1);
dpy_gfx_update(QEMU_CONSOLE(s), 0, 0,
surface_width(surface), surface_height(surface));
}
static void console_scroll(QemuTextConsole *s, int ydelta)
{
int i, y1;
if (ydelta > 0) {
for(i = 0; i < ydelta; i++) {
if (s->y_displayed == s->y_base)
break;
if (++s->y_displayed == s->total_height)
s->y_displayed = 0;
}
} else {
ydelta = -ydelta;
i = s->backscroll_height;
if (i > s->total_height - s->height)
i = s->total_height - s->height;
y1 = s->y_base - i;
if (y1 < 0)
y1 += s->total_height;
for(i = 0; i < ydelta; i++) {
if (s->y_displayed == y1)
break;
if (--s->y_displayed < 0)
s->y_displayed = s->total_height - 1;
}
}
console_refresh(s);
}
static void kbd_send_chars(QemuTextConsole *s)
{
uint32_t len, avail;
len = qemu_chr_be_can_write(s->chr);
avail = fifo8_num_used(&s->out_fifo);
while (len > 0 && avail > 0) {
const uint8_t *buf;
uint32_t size;
buf = fifo8_pop_bufptr(&s->out_fifo, MIN(len, avail), &size);
qemu_chr_be_write(s->chr, buf, size);
len = qemu_chr_be_can_write(s->chr);
avail -= size;
}
}
/* called when an ascii key is pressed */
void qemu_text_console_handle_keysym(QemuTextConsole *s, int keysym)
{
uint8_t buf[16], *q;
int c;
uint32_t num_free;
switch(keysym) {
case QEMU_KEY_CTRL_UP:
console_scroll(s, -1);
break;
case QEMU_KEY_CTRL_DOWN:
console_scroll(s, 1);
break;
case QEMU_KEY_CTRL_PAGEUP:
console_scroll(s, -10);
break;
case QEMU_KEY_CTRL_PAGEDOWN:
console_scroll(s, 10);
break;
default:
/* convert the QEMU keysym to VT100 key string */
q = buf;
if (keysym >= 0xe100 && keysym <= 0xe11f) {
*q++ = '\033';
*q++ = '[';
c = keysym - 0xe100;
if (c >= 10)
*q++ = '0' + (c / 10);
*q++ = '0' + (c % 10);
*q++ = '~';
} else if (keysym >= 0xe120 && keysym <= 0xe17f) {
*q++ = '\033';
*q++ = '[';
*q++ = keysym & 0xff;
} else if (s->echo && (keysym == '\r' || keysym == '\n')) {
qemu_chr_write(s->chr, (uint8_t *)"\r", 1, true);
*q++ = '\n';
} else {
*q++ = keysym;
}
if (s->echo) {
qemu_chr_write(s->chr, buf, q - buf, true);
}
num_free = fifo8_num_free(&s->out_fifo);
fifo8_push_all(&s->out_fifo, buf, MIN(num_free, q - buf));
kbd_send_chars(s);
break;
}
}
static void text_console_update(void *opaque, console_ch_t *chardata)
{
QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
int i, j, src;
if (s->text_x[0] <= s->text_x[1]) {
src = (s->y_base + s->text_y[0]) * s->width;
chardata += s->text_y[0] * s->width;
for (i = s->text_y[0]; i <= s->text_y[1]; i ++)
for (j = 0; j < s->width; j++, src++) {
console_write_ch(chardata ++,
ATTR2CHTYPE(s->cells[src].ch,
s->cells[src].t_attrib.fgcol,
s->cells[src].t_attrib.bgcol,
s->cells[src].t_attrib.bold));
}
dpy_text_update(QEMU_CONSOLE(s), s->text_x[0], s->text_y[0],
s->text_x[1] - s->text_x[0], i - s->text_y[0]);
s->text_x[0] = s->width;
s->text_y[0] = s->height;
s->text_x[1] = 0;
s->text_y[1] = 0;
}
if (s->cursor_invalidate) {
dpy_text_cursor(QEMU_CONSOLE(s), s->x, s->y);
s->cursor_invalidate = 0;
}
}
static void text_console_resize(QemuTextConsole *t)
{
QemuConsole *s = QEMU_CONSOLE(t);
TextCell *cells, *c, *c1;
int w1, x, y, last_width, w, h;
assert(s->scanout.kind == SCANOUT_SURFACE);
w = surface_width(s->surface) / FONT_WIDTH;
h = surface_height(s->surface) / FONT_HEIGHT;
if (w == t->width && h == t->height) {
return;
}
last_width = t->width;
t->width = w;
t->height = h;
w1 = MIN(t->width, last_width);
cells = g_new(TextCell, t->width * t->total_height + 1);
for (y = 0; y < t->total_height; y++) {
c = &cells[y * t->width];
if (w1 > 0) {
c1 = &t->cells[y * last_width];
for (x = 0; x < w1; x++) {
*c++ = *c1++;
}
}
for (x = w1; x < t->width; x++) {
c->ch = ' ';
c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
c++;
}
}
g_free(t->cells);
t->cells = cells;
}
static void vc_put_lf(VCChardev *vc)
{
QemuTextConsole *s = vc->console;
TextCell *c;
int x, y1;
s->y++;
if (s->y >= s->height) {
s->y = s->height - 1;
if (s->y_displayed == s->y_base) {
if (++s->y_displayed == s->total_height)
s->y_displayed = 0;
}
if (++s->y_base == s->total_height)
s->y_base = 0;
if (s->backscroll_height < s->total_height)
s->backscroll_height++;
y1 = (s->y_base + s->height - 1) % s->total_height;
c = &s->cells[y1 * s->width];
for(x = 0; x < s->width; x++) {
c->ch = ' ';
c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
c++;
}
if (s->y_displayed == s->y_base) {
s->text_x[0] = 0;
s->text_y[0] = 0;
s->text_x[1] = s->width - 1;
s->text_y[1] = s->height - 1;
qemu_console_bitblt(QEMU_CONSOLE(s), 0, FONT_HEIGHT, 0, 0,
s->width * FONT_WIDTH,
(s->height - 1) * FONT_HEIGHT);
qemu_console_fill_rect(QEMU_CONSOLE(s), 0, (s->height - 1) * FONT_HEIGHT,
s->width * FONT_WIDTH, FONT_HEIGHT,
color_table_rgb[0][TEXT_ATTRIBUTES_DEFAULT.bgcol]);
s->update_x0 = 0;
s->update_y0 = 0;
s->update_x1 = s->width * FONT_WIDTH;
s->update_y1 = s->height * FONT_HEIGHT;
}
}
}
/* Set console attributes depending on the current escape codes.
* NOTE: I know this code is not very efficient (checking every color for it
* self) but it is more readable and better maintainable.
*/
static void vc_handle_escape(VCChardev *vc)
{
int i;
for (i = 0; i < vc->nb_esc_params; i++) {
switch (vc->esc_params[i]) {
case 0: /* reset all console attributes to default */
vc->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
break;
case 1:
vc->t_attrib.bold = 1;
break;
case 4:
vc->t_attrib.uline = 1;
break;
case 5:
vc->t_attrib.blink = 1;
break;
case 7:
vc->t_attrib.invers = 1;
break;
case 8:
vc->t_attrib.unvisible = 1;
break;
case 22:
vc->t_attrib.bold = 0;
break;
case 24:
vc->t_attrib.uline = 0;
break;
case 25:
vc->t_attrib.blink = 0;
break;
case 27:
vc->t_attrib.invers = 0;
break;
case 28:
vc->t_attrib.unvisible = 0;
break;
/* set foreground color */
case 30:
vc->t_attrib.fgcol = QEMU_COLOR_BLACK;
break;
case 31:
vc->t_attrib.fgcol = QEMU_COLOR_RED;
break;
case 32:
vc->t_attrib.fgcol = QEMU_COLOR_GREEN;
break;
case 33:
vc->t_attrib.fgcol = QEMU_COLOR_YELLOW;
break;
case 34:
vc->t_attrib.fgcol = QEMU_COLOR_BLUE;
break;
case 35:
vc->t_attrib.fgcol = QEMU_COLOR_MAGENTA;
break;
case 36:
vc->t_attrib.fgcol = QEMU_COLOR_CYAN;
break;
case 37:
vc->t_attrib.fgcol = QEMU_COLOR_WHITE;
break;
/* set background color */
case 40:
vc->t_attrib.bgcol = QEMU_COLOR_BLACK;
break;
case 41:
vc->t_attrib.bgcol = QEMU_COLOR_RED;
break;
case 42:
vc->t_attrib.bgcol = QEMU_COLOR_GREEN;
break;
case 43:
vc->t_attrib.bgcol = QEMU_COLOR_YELLOW;
break;
case 44:
vc->t_attrib.bgcol = QEMU_COLOR_BLUE;
break;
case 45:
vc->t_attrib.bgcol = QEMU_COLOR_MAGENTA;
break;
case 46:
vc->t_attrib.bgcol = QEMU_COLOR_CYAN;
break;
case 47:
vc->t_attrib.bgcol = QEMU_COLOR_WHITE;
break;
}
}
}
static void vc_update_xy(VCChardev *vc, int x, int y)
{
QemuTextConsole *s = vc->console;
TextCell *c;
int y1, y2;
s->text_x[0] = MIN(s->text_x[0], x);
s->text_x[1] = MAX(s->text_x[1], x);
s->text_y[0] = MIN(s->text_y[0], y);
s->text_y[1] = MAX(s->text_y[1], y);
y1 = (s->y_base + y) % s->total_height;
y2 = y1 - s->y_displayed;
if (y2 < 0) {
y2 += s->total_height;
}
if (y2 < s->height) {
if (x >= s->width) {
x = s->width - 1;
}
c = &s->cells[y1 * s->width + x];
vga_putcharxy(QEMU_CONSOLE(s), x, y2, c->ch,
&(c->t_attrib));
invalidate_xy(s, x, y2);
}
}
static void vc_clear_xy(VCChardev *vc, int x, int y)
{
QemuTextConsole *s = vc->console;
int y1 = (s->y_base + y) % s->total_height;
if (x >= s->width) {
x = s->width - 1;
}
TextCell *c = &s->cells[y1 * s->width + x];
c->ch = ' ';
c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
vc_update_xy(vc, x, y);
}
static void vc_put_one(VCChardev *vc, int ch)
{
QemuTextConsole *s = vc->console;
TextCell *c;
int y1;
if (s->x >= s->width) {
/* line wrap */
s->x = 0;
vc_put_lf(vc);
}
y1 = (s->y_base + s->y) % s->total_height;
c = &s->cells[y1 * s->width + s->x];
c->ch = ch;
c->t_attrib = vc->t_attrib;
vc_update_xy(vc, s->x, s->y);
s->x++;
}
static void vc_respond_str(VCChardev *vc, const char *buf)
{
QemuTextConsole *s = vc->console;
qemu_chr_be_write(s->chr, (const uint8_t *)buf, strlen(buf));
}
/* set cursor, checking bounds */
static void vc_set_cursor(VCChardev *vc, int x, int y)
{
QemuTextConsole *s = vc->console;
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (y >= s->height) {
y = s->height - 1;
}
if (x >= s->width) {
x = s->width - 1;
}
s->x = x;
s->y = y;
}
/**
* vc_csi_P() - (DCH) deletes one or more characters from the cursor
* position to the right. As characters are deleted, the remaining
* characters between the cursor and right margin move to the
* left. Character attributes move with the characters.
*/
static void vc_csi_P(struct VCChardev *vc, unsigned int nr)
{
QemuTextConsole *s = vc->console;
TextCell *c1, *c2;
unsigned int x1, x2, y;
unsigned int end, len;
if (!nr) {
nr = 1;
}
if (nr > s->width - s->x) {
nr = s->width - s->x;
if (!nr) {
return;
}
}
x1 = s->x;
x2 = s->x + nr;
len = s->width - x2;
if (len) {
y = (s->y_base + s->y) % s->total_height;
c1 = &s->cells[y * s->width + x1];
c2 = &s->cells[y * s->width + x2];
memmove(c1, c2, len * sizeof(*c1));
for (end = x1 + len; x1 < end; x1++) {
vc_update_xy(vc, x1, s->y);
}
}
/* Clear the rest */
for (; x1 < s->width; x1++) {
vc_clear_xy(vc, x1, s->y);
}
}
/**
* vc_csi_at() - (ICH) inserts `nr` blank characters with the default
* character attribute. The cursor remains at the beginning of the
* blank characters. Text between the cursor and right margin moves to
* the right. Characters scrolled past the right margin are lost.
*/
static void vc_csi_at(struct VCChardev *vc, unsigned int nr)
{
QemuTextConsole *s = vc->console;
TextCell *c1, *c2;
unsigned int x1, x2, y;
unsigned int end, len;
if (!nr) {
nr = 1;
}
if (nr > s->width - s->x) {
nr = s->width - s->x;
if (!nr) {
return;
}
}
x1 = s->x + nr;
x2 = s->x;
len = s->width - x1;
if (len) {
y = (s->y_base + s->y) % s->total_height;
c1 = &s->cells[y * s->width + x1];
c2 = &s->cells[y * s->width + x2];
memmove(c1, c2, len * sizeof(*c1));
for (end = x1 + len; x1 < end; x1++) {
vc_update_xy(vc, x1, s->y);
}
}
/* Insert blanks */
for (x1 = s->x; x1 < s->x + nr; x1++) {
vc_clear_xy(vc, x1, s->y);
}
}
/**
* vc_save_cursor() - saves cursor position and character attributes.
*/
static void vc_save_cursor(VCChardev *vc)
{
QemuTextConsole *s = vc->console;
vc->x_saved = s->x;
vc->y_saved = s->y;
vc->t_attrib_saved = vc->t_attrib;
}
/**
* vc_restore_cursor() - restores cursor position and character
* attributes from saved state.
*/
static void vc_restore_cursor(VCChardev *vc)
{
QemuTextConsole *s = vc->console;
s->x = vc->x_saved;
s->y = vc->y_saved;
vc->t_attrib = vc->t_attrib_saved;
}
static void vc_putchar(VCChardev *vc, int ch)
{
QemuTextConsole *s = vc->console;
int i;
int x, y;
g_autofree char *response = NULL;
switch(vc->state) {
case TTY_STATE_NORM:
switch(ch) {
case '\r': /* carriage return */
s->x = 0;
break;
case '\n': /* newline */
vc_put_lf(vc);
break;
case '\b': /* backspace */
if (s->x > 0)
s->x--;
break;
case '\t': /* tabspace */
if (s->x + (8 - (s->x % 8)) > s->width) {
s->x = 0;
vc_put_lf(vc);
} else {
s->x = s->x + (8 - (s->x % 8));
}
break;
case '\a': /* alert aka. bell */
/* TODO: has to be implemented */
break;
case 14:
/* SI (shift in), character set 0 (ignored) */
break;
case 15:
/* SO (shift out), character set 1 (ignored) */
break;
case 27: /* esc (introducing an escape sequence) */
vc->state = TTY_STATE_ESC;
break;
default:
vc_put_one(vc, ch);
break;
}
break;
case TTY_STATE_ESC: /* check if it is a terminal escape sequence */
if (ch == '[') {
for(i=0;i<MAX_ESC_PARAMS;i++)
vc->esc_params[i] = 0;
vc->nb_esc_params = 0;
vc->state = TTY_STATE_CSI;
} else if (ch == '(') {
vc->state = TTY_STATE_G0;
} else if (ch == ')') {
vc->state = TTY_STATE_G1;
} else if (ch == '7') {
vc_save_cursor(vc);
vc->state = TTY_STATE_NORM;
} else if (ch == '8') {
vc_restore_cursor(vc);
vc->state = TTY_STATE_NORM;
} else {
vc->state = TTY_STATE_NORM;
}
break;
case TTY_STATE_CSI: /* handle escape sequence parameters */
if (ch >= '0' && ch <= '9') {
if (vc->nb_esc_params < MAX_ESC_PARAMS) {
int *param = &vc->esc_params[vc->nb_esc_params];
int digit = (ch - '0');
*param = (*param <= (INT_MAX - digit) / 10) ?
*param * 10 + digit : INT_MAX;
}
} else {
if (vc->nb_esc_params < MAX_ESC_PARAMS)
vc->nb_esc_params++;
if (ch == ';' || ch == '?') {
break;
}
trace_console_putchar_csi(vc->esc_params[0], vc->esc_params[1],
ch, vc->nb_esc_params);
vc->state = TTY_STATE_NORM;
switch(ch) {
case 'A':
/* move cursor up */
if (vc->esc_params[0] == 0) {
vc->esc_params[0] = 1;
}
vc_set_cursor(vc, s->x, s->y - vc->esc_params[0]);
break;
case 'B':
/* move cursor down */
if (vc->esc_params[0] == 0) {
vc->esc_params[0] = 1;
}
vc_set_cursor(vc, s->x, s->y + vc->esc_params[0]);
break;
case 'C':
/* move cursor right */
if (vc->esc_params[0] == 0) {
vc->esc_params[0] = 1;
}
vc_set_cursor(vc, s->x + vc->esc_params[0], s->y);
break;
case 'D':
/* move cursor left */
if (vc->esc_params[0] == 0) {
vc->esc_params[0] = 1;
}
vc_set_cursor(vc, s->x - vc->esc_params[0], s->y);
break;
case 'G':
/* move cursor to column */
vc_set_cursor(vc, vc->esc_params[0] - 1, s->y);
break;
case 'f':
case 'H':
/* move cursor to row, column */
vc_set_cursor(vc, vc->esc_params[1] - 1, vc->esc_params[0] - 1);
break;
case 'J':
switch (vc->esc_params[0]) {
case 0:
/* clear to end of screen */
for (y = s->y; y < s->height; y++) {
for (x = 0; x < s->width; x++) {
if (y == s->y && x < s->x) {
continue;
}
vc_clear_xy(vc, x, y);
}
}
break;
case 1:
/* clear from beginning of screen */
for (y = 0; y <= s->y; y++) {
for (x = 0; x < s->width; x++) {
if (y == s->y && x > s->x) {
break;
}
vc_clear_xy(vc, x, y);
}
}
break;
case 2:
/* clear entire screen */
for (y = 0; y <= s->height; y++) {
for (x = 0; x < s->width; x++) {
vc_clear_xy(vc, x, y);
}
}
break;
}
break;
case 'K':
switch (vc->esc_params[0]) {
case 0:
/* clear to eol */
for(x = s->x; x < s->width; x++) {
vc_clear_xy(vc, x, s->y);
}
break;
case 1:
/* clear from beginning of line */
for (x = 0; x <= s->x && x < s->width; x++) {
vc_clear_xy(vc, x, s->y);
}
break;
case 2:
/* clear entire line */
for(x = 0; x < s->width; x++) {
vc_clear_xy(vc, x, s->y);
}
break;
}
break;
case 'P':
vc_csi_P(vc, vc->esc_params[0]);
break;
case 'm':
vc_handle_escape(vc);
break;
case 'n':
switch (vc->esc_params[0]) {
case 5:
/* report console status (always succeed)*/
vc_respond_str(vc, "\033[0n");
break;
case 6:
/* report cursor position */
response = g_strdup_printf("\033[%d;%dR",
s->y + 1, s->x + 1);
vc_respond_str(vc, response);
break;
}
break;
case 's':
vc_save_cursor(vc);
break;
case 'u':
vc_restore_cursor(vc);
break;
case '@':
vc_csi_at(vc, vc->esc_params[0]);
break;
default:
trace_console_putchar_unhandled(ch);
break;
}
break;
}
break;
case TTY_STATE_G0: /* set character sets */
case TTY_STATE_G1: /* set character sets */
switch (ch) {
case 'B':
/* Latin-1 map */
break;
}
vc->state = TTY_STATE_NORM;
break;
}
}
#define TYPE_CHARDEV_VC "chardev-vc"
DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV,
TYPE_CHARDEV_VC)
static int vc_chr_write(Chardev *chr, const uint8_t *buf, int len)
{
VCChardev *drv = VC_CHARDEV(chr);
QemuTextConsole *s = drv->console;
int i;
s->update_x0 = s->width * FONT_WIDTH;
s->update_y0 = s->height * FONT_HEIGHT;
s->update_x1 = 0;
s->update_y1 = 0;
console_show_cursor(s, 0);
for(i = 0; i < len; i++) {
vc_putchar(drv, buf[i]);
}
console_show_cursor(s, 1);
if (s->update_x0 < s->update_x1) {
dpy_gfx_update(QEMU_CONSOLE(s), s->update_x0, s->update_y0,
s->update_x1 - s->update_x0,
s->update_y1 - s->update_y0);
}
return len;
}
void qemu_text_console_update_cursor(void)
{
cursor_visible_phase = !cursor_visible_phase;
if (qemu_invalidate_text_consoles()) {
timer_mod(cursor_timer,
qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2);
}
}
static void
cursor_timer_cb(void *opaque)
{
qemu_text_console_update_cursor();
}
static void text_console_invalidate(void *opaque)
{
QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
if (!QEMU_IS_FIXED_TEXT_CONSOLE(s)) {
text_console_resize(QEMU_TEXT_CONSOLE(s));
}
console_refresh(s);
}
static void
qemu_text_console_finalize(Object *obj)
{
}
static void
qemu_text_console_class_init(ObjectClass *oc, void *data)
{
if (!cursor_timer) {
cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL);
}
}
static const GraphicHwOps text_console_ops = {
.invalidate = text_console_invalidate,
.text_update = text_console_update,
};
static void
qemu_text_console_init(Object *obj)
{
QemuTextConsole *c = QEMU_TEXT_CONSOLE(obj);
fifo8_create(&c->out_fifo, 16);
c->total_height = DEFAULT_BACKSCROLL;
QEMU_CONSOLE(c)->hw_ops = &text_console_ops;
QEMU_CONSOLE(c)->hw = c;
}
static void
qemu_fixed_text_console_finalize(Object *obj)
{
}
static void
qemu_fixed_text_console_class_init(ObjectClass *oc, void *data)
{
}
static void
qemu_fixed_text_console_init(Object *obj)
{
}
static void vc_chr_accept_input(Chardev *chr)
{
VCChardev *drv = VC_CHARDEV(chr);
kbd_send_chars(drv->console);
}
static void vc_chr_set_echo(Chardev *chr, bool echo)
{
VCChardev *drv = VC_CHARDEV(chr);
drv->console->echo = echo;
}
void qemu_text_console_update_size(QemuTextConsole *c)
{
dpy_text_resize(QEMU_CONSOLE(c), c->width, c->height);
}
static void vc_chr_open(Chardev *chr,
ChardevBackend *backend,
bool *be_opened,
Error **errp)
{
ChardevVC *vc = backend->u.vc.data;
VCChardev *drv = VC_CHARDEV(chr);
QemuTextConsole *s;
unsigned width = 0;
unsigned height = 0;
if (vc->has_width) {
width = vc->width;
} else if (vc->has_cols) {
width = vc->cols * FONT_WIDTH;
}
if (vc->has_height) {
height = vc->height;
} else if (vc->has_rows) {
height = vc->rows * FONT_HEIGHT;
}
trace_console_txt_new(width, height);
if (width == 0 || height == 0) {
s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_TEXT_CONSOLE));
width = 80 * FONT_WIDTH;
height = 24 * FONT_HEIGHT;
} else {
s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_FIXED_TEXT_CONSOLE));
}
dpy_gfx_replace_surface(QEMU_CONSOLE(s), qemu_create_displaysurface(width, height));
s->chr = chr;
drv->console = s;
/* set current text attributes to default */
drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
text_console_resize(s);
if (chr->label) {
char *msg;
drv->t_attrib.bgcol = QEMU_COLOR_BLUE;
msg = g_strdup_printf("%s console\r\n", chr->label);
qemu_chr_write(chr, (uint8_t *)msg, strlen(msg), true);
g_free(msg);
drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
}
*be_opened = true;
}
static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp)
{
int val;
ChardevVC *vc;
backend->type = CHARDEV_BACKEND_KIND_VC;
vc = backend->u.vc.data = g_new0(ChardevVC, 1);
qemu_chr_parse_common(opts, qapi_ChardevVC_base(vc));
val = qemu_opt_get_number(opts, "width", 0);
if (val != 0) {
vc->has_width = true;
vc->width = val;
}
val = qemu_opt_get_number(opts, "height", 0);
if (val != 0) {
vc->has_height = true;
vc->height = val;
}
val = qemu_opt_get_number(opts, "cols", 0);
if (val != 0) {
vc->has_cols = true;
vc->cols = val;
}
val = qemu_opt_get_number(opts, "rows", 0);
if (val != 0) {
vc->has_rows = true;
vc->rows = val;
}
}
static void char_vc_class_init(ObjectClass *oc, void *data)
{
ChardevClass *cc = CHARDEV_CLASS(oc);
cc->parse = vc_chr_parse;
cc->open = vc_chr_open;
cc->chr_write = vc_chr_write;
cc->chr_accept_input = vc_chr_accept_input;
cc->chr_set_echo = vc_chr_set_echo;
}
static const TypeInfo char_vc_type_info = {
.name = TYPE_CHARDEV_VC,
.parent = TYPE_CHARDEV,
.instance_size = sizeof(VCChardev),
.class_init = char_vc_class_init,
};
void qemu_console_early_init(void)
{
/* set the default vc driver */
if (!object_class_by_name(TYPE_CHARDEV_VC)) {
type_register_static(&char_vc_type_info);
}
}