Files
qemu/ui/console-vc.c
Marc-André Lureau b05c6e2532 ui/console-vc: fire ADDED/REMOVED notifications
Fire CONSOLE_ADDED when the chardev is opened.

Fire CONSOLE_REMOVED in char_vc_finalize() before dropping the console
reference, so the console is still in a valid state when listeners
handle the event. Also fixes a console object leak by adding the
missing object_unref().

Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260623-b4-ui-v4-25-4656aec3398d@redhat.com>
2026-06-24 15:41:15 +04:00

359 lines
9.0 KiB
C

/*
* SPDX-License-Identifier: MIT
* QEMU VC
*/
#include "qemu/osdep.h"
#include "chardev/char.h"
#include "qapi/error.h"
#include "qemu/option.h"
#include "qemu/queue.h"
#include "qom/compat-properties.h"
#include "ui/console.h"
#include "ui/vgafont.h"
#include "ui/vt100.h"
#include "pixman.h"
#include "trace.h"
#include "console-priv.h"
typedef struct QemuTextConsole {
QemuConsole parent;
QemuVT100 vt;
Chardev *chr;
} 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;
ChardevVCEncoding encoding;
QemuTextConsole *console;
};
typedef struct VCChardev VCChardev;
static char *
qemu_text_console_get_label(const QemuConsole *c)
{
QemuTextConsole *tc = QEMU_TEXT_CONSOLE(c);
return tc->chr ? g_strdup(tc->chr->label) : NULL;
}
static void qemu_text_console_out_flush(QemuTextConsole *s)
{
uint32_t len, avail;
len = qemu_chr_be_can_write(s->chr);
avail = fifo8_num_used(&s->vt.out_fifo);
while (len > 0 && avail > 0) {
const uint8_t *buf;
uint32_t size;
buf = fifo8_pop_bufptr(&s->vt.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)
{
vt100_keysym(&s->vt, keysym);
}
static void text_console_update(void *opaque, uint32_t *chardata)
{
QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
int i, j, src;
if (s->vt.text_x[0] <= s->vt.text_x[1]) {
src = (s->vt.y_base + s->vt.text_y[0]) * s->vt.width;
chardata += s->vt.text_y[0] * s->vt.width;
for (i = s->vt.text_y[0]; i <= s->vt.text_y[1]; i ++)
for (j = 0; j < s->vt.width; j++, src++) {
*chardata++ = ATTR2CHTYPE(s->vt.cells[src].ch,
s->vt.cells[src].t_attrib.fgcol,
s->vt.cells[src].t_attrib.bgcol,
s->vt.cells[src].t_attrib.bold);
}
qemu_console_text_update(QEMU_CONSOLE(s), s->vt.text_x[0], s->vt.text_y[0],
s->vt.text_x[1] - s->vt.text_x[0], i - s->vt.text_y[0]);
s->vt.text_x[0] = s->vt.width;
s->vt.text_y[0] = s->vt.height;
s->vt.text_x[1] = 0;
s->vt.text_y[1] = 0;
}
if (s->vt.cursor_invalidate) {
qemu_console_text_set_cursor(QEMU_CONSOLE(s), s->vt.x, s->vt.y);
s->vt.cursor_invalidate = 0;
}
}
#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;
return vt100_input(&s->vt, buf, len);
}
static void text_console_invalidate(void *opaque)
{
QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
if (!QEMU_IS_FIXED_TEXT_CONSOLE(s)) {
vt100_set_image(&s->vt, QEMU_CONSOLE(s)->surface->image);
}
vt100_refresh(&s->vt);
}
static void
qemu_text_console_finalize(Object *obj)
{
QemuTextConsole *s = QEMU_TEXT_CONSOLE(obj);
vt100_fini(&s->vt);
}
static void
qemu_text_console_class_init(ObjectClass *oc, const void *data)
{
QemuConsoleClass *cc = QEMU_CONSOLE_CLASS(oc);
cc->get_label = qemu_text_console_get_label;
}
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);
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, const void *data)
{
}
static void
qemu_fixed_text_console_init(Object *obj)
{
}
static void vc_chr_accept_input(Chardev *chr)
{
VCChardev *drv = VC_CHARDEV(chr);
qemu_text_console_out_flush(drv->console);
}
static void vc_chr_set_echo(Chardev *chr, bool echo)
{
VCChardev *drv = VC_CHARDEV(chr);
drv->console->vt.echo = echo;
}
void qemu_text_console_update_size(QemuTextConsole *c)
{
qemu_console_text_resize(QEMU_CONSOLE(c), c->vt.width, c->vt.height);
}
static void text_console_image_update(QemuVT100 *vt, int x, int y, int width, int height)
{
QemuTextConsole *console = container_of(vt, QemuTextConsole, vt);
qemu_console_update(QEMU_CONSOLE(console), x, y, width, height);
}
static void text_console_out_flush(QemuVT100 *vt)
{
QemuTextConsole *console = container_of(vt, QemuTextConsole, vt);
qemu_text_console_out_flush(console);
}
static bool vc_chr_open(Chardev *chr, ChardevBackend *backend, 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));
}
qemu_console_set_surface(QEMU_CONSOLE(s), qemu_create_displaysurface(width, height));
if (vc->has_encoding) {
drv->encoding = vc->encoding;
}
vt100_init(&s->vt, QEMU_CONSOLE(s)->surface->image,
drv->encoding,
text_console_image_update,
text_console_out_flush);
s->chr = chr;
drv->console = s;
if (chr->label) {
char *msg;
s->vt.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);
s->vt.t_attrib = TEXT_ATTRIBUTES_DEFAULT;
}
qemu_chr_be_event(chr, CHR_EVENT_OPENED);
qemu_console_notify(QEMU_CONSOLE_ADDED, QEMU_CONSOLE(s));
return true;
}
static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp)
{
int val;
const char *str;
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;
}
str = qemu_opt_get(opts, "encoding");
if (str) {
int cs = qapi_enum_parse(&ChardevVCEncoding_lookup, str, -1, errp);
if (cs < 0) {
return;
}
vc->has_encoding = true;
vc->encoding = cs;
}
}
CHARDEV_VC_ENCODING_PROPERTY_DEFINE(VC_CHARDEV)
static void char_vc_class_init(ObjectClass *oc, const void *data)
{
ChardevClass *cc = CHARDEV_CLASS(oc);
cc->chr_parse = vc_chr_parse;
cc->chr_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;
cc->supports_size_opts = true;
cc->supports_encoding_opts = true;
chardev_vc_add_encoding_prop(oc, get_encoding, set_encoding);
}
static void char_vc_init(Object *obj)
{
VCChardev *vc = VC_CHARDEV(obj);
vc->encoding = CHARDEV_VC_ENCODING_UTF8;
}
static void char_vc_finalize(Object *obj)
{
VCChardev *vc = VC_CHARDEV(obj);
QemuConsole *con = QEMU_CONSOLE(vc->console);
if (con) {
qemu_console_notify(QEMU_CONSOLE_REMOVED, con);
object_unref(con);
}
}
static const TypeInfo char_vc_type_info = {
.name = TYPE_CHARDEV_VC,
.parent = TYPE_CHARDEV,
.instance_size = sizeof(VCChardev),
.instance_init = char_vc_init,
.instance_post_init = object_apply_compat_props,
.instance_finalize = char_vc_finalize,
.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);
}
}