From e4d6c94e674a3162857dbd78c27b1d7a1b2c250f Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:07 +0100 Subject: [PATCH 1/6] ui/console-vc: introduce parsing of the 'ESC ( ' sequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change introduces parsing of the 'ESC ( ' sequence, which is supposed to change character set [1]. In the QEMU case, the introduced parsing logic does not actually change the character set, but simply parses the sequence and does not let output of a tool to be corrupted with leftovers: `top` sends 'ESC ( B', so if character sequence is not parsed correctly, chracter 'B' appears in the output: Btop - 11:08:42 up 5 min, 1 user, load average: 0BB Tasks:B 158 Btotal,B 1 Brunning,B 157 Bsleeping,B 0 BsBB %Cpu(s):B 0.0 Bus,B 0.0 Bsy,B 0.0 Bni,B 99.8 Bid,B 0.2 BB MiB Mem :B 7955.6 Btotal,B 7778.6 Bfree,B 79.6 BB MiB Swap:B 0.0 Btotal,B 0.0 Bfree,B 0.0 BB PID USER PR NI VIRT RES SHR S B B 735 root 20 0 9328 3540 3152 R B B 1 root 20 0 20084 10904 8404 S B B 2 root 20 0 0 0 0 S B [1] https://vt100.net/docs/vt100-ug/chapter3.html#SCS Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-2-r.peniaev@gmail.com> --- ui/console-vc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ui/console-vc.c b/ui/console-vc.c index fe20579832..90ff0ffda8 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -42,6 +42,8 @@ enum TTYState { TTY_STATE_NORM, TTY_STATE_ESC, TTY_STATE_CSI, + TTY_STATE_G0, + TTY_STATE_G1, }; typedef struct QemuTextConsole { @@ -694,6 +696,10 @@ static void vc_putchar(VCChardev *vc, int ch) 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 { vc->state = TTY_STATE_NORM; } @@ -844,6 +850,16 @@ static void vc_putchar(VCChardev *vc, int ch) } 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; } } From 0a9f48e9ead2b067e8d7058e7bc7a1d68721882d Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:08 +0100 Subject: [PATCH 2/6] ui/console-vc: report to the application instead of screen rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Terminal Device Status Report (DSR) [1] should be sent to an application, not rendered to the screen. This patch fixes rendering of terminal report, which appear only on the graphical screen of the terminal (console "vc") and can be reproduced by the following command: echo -en '\e[6n'; IFS='[;' read -sdR _ row col; echo $row:$col Command requests cursor position and waits for terminal response, but instead, the response is rendered to the graphical screen and never sent to an application. Why bother? Busybox shell (ash) in Alpine distribution requests cursor position on each shell prompt (once is pressed), which makes a prompt on a graphical screen corrupted with repeating Cursor Position Report (CPR) [2]: [root@alpine ~]# \033[57;1R] Which is very annoying and incorrect. [1] https://vt100.net/docs/vt100-ug/chapter3.html#DSR [2] https://vt100.net/docs/vt100-ug/chapter3.html#CPR Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-3-r.peniaev@gmail.com> --- ui/console-vc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/console-vc.c b/ui/console-vc.c index 90ff0ffda8..d512f57e10 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -617,10 +617,9 @@ static void vc_put_one(VCChardev *vc, int ch) static void vc_respond_str(VCChardev *vc, const char *buf) { - while (*buf) { - vc_put_one(vc, *buf); - buf++; - } + QemuTextConsole *s = vc->console; + + qemu_chr_be_write(s->chr, (const uint8_t *)buf, strlen(buf)); } /* set cursor, checking bounds */ From 40339871da115b68e01f1da9ce2f8175e8f65d3c Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:09 +0100 Subject: [PATCH 3/6] ui/console-vc: report cursor position in the screen not in the scroll buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The format of the CSI cursor position report is `ESC[row;columnR`, where `row` is a row of a cursor in the screen, not in the scrollback buffer. What's the difference? Let's say the terminal screen has 24 lines, no matter how long the scrollback buffer may be, the last line is the 24th. For example the following command can be executed in xterm on the last screen line: $ echo -en '\e[6n'; IFS='[;' read -sdR _ row col; echo $row:$col 24:1 It shows the cursor position on the current screen and not relative to the backscroll buffer. Before this change the row number was always increasing for the QEMU VC and represents the cursor position relative to the backscroll buffer. Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-4-r.peniaev@gmail.com> --- ui/console-vc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/console-vc.c b/ui/console-vc.c index d512f57e10..87f57f1c52 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -827,8 +827,7 @@ static void vc_putchar(VCChardev *vc, int ch) case 6: /* report cursor position */ response = g_strdup_printf("\033[%d;%dR", - (s->y_base + s->y) % s->total_height + 1, - s->x + 1); + s->y + 1, s->x + 1); vc_respond_str(vc, response); break; } From 1a0fd7838a9dddf91241bc9faa471dc9dec04329 Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:10 +0100 Subject: [PATCH 4/6] ui/console-vc: add support for cursor DECSC and DECRC commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are aliases for save and restore cursor commands: * save cursor `ESC 7` (DEC Save Cursor [1], older VT100) `ESC [ s` (CSI Save Cursor, standard ANSI) * load cursor `ESC 8` (DEC Restore Cursor [2], older VT100) `ESC [ u` (CSI Restore Cursor, standard ANSI) This change introduces older DEC sequencies for compatibility with some scripts (for example [3]) and tools. This change also adds saving and restoring of character attributes, which is according to the VT spec [1][2] [1] https://vt100.net/docs/vt510-rm/DECSC.html [2] https://vt100.net/docs/vt510-rm/DECRC.html [3] https://wiki.archlinux.org/title/Working_with_the_serial_console#Resizing_a_terminal Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-5-r.peniaev@gmail.com> --- ui/console-vc.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/ui/console-vc.c b/ui/console-vc.c index 87f57f1c52..522adc2806 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -90,6 +90,7 @@ struct VCChardev { 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; @@ -644,6 +645,31 @@ static void vc_set_cursor(VCChardev *vc, int x, int y) s->y = 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; @@ -699,6 +725,12 @@ static void vc_putchar(VCChardev *vc, int 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; } @@ -833,14 +865,10 @@ static void vc_putchar(VCChardev *vc, int ch) } break; case 's': - /* save cursor position */ - vc->x_saved = s->x; - vc->y_saved = s->y; + vc_save_cursor(vc); break; case 'u': - /* restore cursor position */ - s->x = vc->x_saved; - s->y = vc->y_saved; + vc_restore_cursor(vc); break; default: trace_console_putchar_unhandled(ch); From a97ef3624437c5a5fbc8bd45e2a206d10ca840be Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:11 +0100 Subject: [PATCH 5/6] ui/console-vc: implement DCH (delete) and ICH (insert) commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-6-r.peniaev@gmail.com> --- ui/console-vc.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/ui/console-vc.c b/ui/console-vc.c index 522adc2806..df1341513d 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -645,6 +645,88 @@ static void vc_set_cursor(VCChardev *vc, int x, int y) 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. */ @@ -847,6 +929,9 @@ static void vc_putchar(VCChardev *vc, int ch) break; } break; + case 'P': + vc_csi_P(vc, vc->esc_params[0]); + break; case 'm': vc_handle_escape(vc); break; @@ -870,6 +955,9 @@ static void vc_putchar(VCChardev *vc, int ch) case 'u': vc_restore_cursor(vc); break; + case '@': + vc_csi_at(vc, vc->esc_params[0]); + break; default: trace_console_putchar_unhandled(ch); break; From 46f83c898a6658921fed57f98af6d505ab78a6e4 Mon Sep 17 00:00:00 2001 From: Haoqian He Date: Tue, 25 Feb 2025 18:45:26 +0800 Subject: [PATCH 6/6] chardev: use remoteAddr if the chardev is client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the chardev is client, the socket file path in localAddr may be NULL. This is because the socket path comes from getsockname(), according to man page, getsockname() returns the current address bound by the socket sockfd. If the chardev is client, it's socket is unbound sockfd. Therefore, when computing the client chardev socket file path, using remoteAddr is more appropriate. Signed-off-by: Haoqian He Acked-by: Marc-André Lureau Message-ID: <20250225104526.2924175-1-haoqian.he@smartx.com> --- chardev/char-socket.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 91496ceda9..2f842f9f88 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -571,9 +571,13 @@ static char *qemu_chr_compute_filename(SocketChardev *s) switch (ss->ss_family) { case AF_UNIX: - return g_strdup_printf("unix:%s%s", - ((struct sockaddr_un *)(ss))->sun_path, - s->is_listen ? ",server=on" : ""); + if (s->is_listen) { + return g_strdup_printf("unix:%s,server=on", + ((struct sockaddr_un *)(ss))->sun_path); + } else { + return g_strdup_printf("unix:%s", + ((struct sockaddr_un *)(ps))->sun_path); + } case AF_INET6: left = "["; right = "]";