/* * QEMU graphical console * * Copyright (c) 2004 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "qemu/osdep.h" #include "ui/console.h" #include "hw/qdev-core.h" #include "qapi/error.h" #include "qapi/qapi-commands-ui.h" #include "qapi/visitor.h" #include "qemu/coroutine.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" #include "qemu/module.h" #include "qemu/option.h" #include "chardev/char.h" #include "trace.h" #include "exec/memory.h" #include "qom/object.h" #include "qemu/memfd.h" #include "console-priv.h" OBJECT_DEFINE_ABSTRACT_TYPE(QemuConsole, qemu_console, QEMU_CONSOLE, OBJECT) typedef struct QemuGraphicConsole { QemuConsole parent; Object *device; uint32_t head; QEMUCursor *cursor; int cursor_x, cursor_y; bool cursor_on; } QemuGraphicConsole; typedef QemuConsoleClass QemuGraphicConsoleClass; OBJECT_DEFINE_TYPE(QemuGraphicConsole, qemu_graphic_console, QEMU_GRAPHIC_CONSOLE, QEMU_CONSOLE) struct DisplayState { QEMUTimer *gui_timer; uint64_t last_update; uint64_t update_interval; bool refreshing; QLIST_HEAD(, DisplayChangeListener) listeners; }; static DisplayState *display_state; static QTAILQ_HEAD(, QemuConsole) consoles = QTAILQ_HEAD_INITIALIZER(consoles); static void dpy_refresh(DisplayState *s); static DisplayState *get_alloc_displaystate(void); static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl); static bool console_compatible_with(QemuConsole *con, DisplayChangeListener *dcl, Error **errp); static QemuConsole *qemu_graphic_console_lookup_unused(void); static void dpy_set_ui_info_timer(void *opaque); static void gui_update(void *opaque) { uint64_t interval = GUI_REFRESH_INTERVAL_IDLE; uint64_t dcl_interval; DisplayState *ds = opaque; DisplayChangeListener *dcl; ds->refreshing = true; dpy_refresh(ds); ds->refreshing = false; QLIST_FOREACH(dcl, &ds->listeners, next) { dcl_interval = dcl->update_interval ? dcl->update_interval : GUI_REFRESH_INTERVAL_DEFAULT; if (interval > dcl_interval) { interval = dcl_interval; } } if (ds->update_interval != interval) { ds->update_interval = interval; trace_console_refresh(interval); } ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); timer_mod(ds->gui_timer, ds->last_update + interval); } static void gui_setup_refresh(DisplayState *ds) { DisplayChangeListener *dcl; bool need_timer = false; QLIST_FOREACH(dcl, &ds->listeners, next) { if (dcl->ops->dpy_refresh != NULL) { need_timer = true; } } if (need_timer && ds->gui_timer == NULL) { ds->gui_timer = timer_new_ms(QEMU_CLOCK_REALTIME, gui_update, ds); timer_mod(ds->gui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME)); } if (!need_timer && ds->gui_timer != NULL) { timer_free(ds->gui_timer); ds->gui_timer = NULL; } } void graphic_hw_update_done(QemuConsole *con) { if (con) { qemu_co_enter_all(&con->dump_queue, NULL); } } void graphic_hw_update(QemuConsole *con) { bool async = false; if (!con) { return; } if (con->hw_ops->gfx_update) { con->hw_ops->gfx_update(con->hw); async = con->hw_ops->gfx_update_async; } if (!async) { graphic_hw_update_done(con); } } static void graphic_hw_update_bh(void *con) { graphic_hw_update(con); } void qemu_console_co_wait_update(QemuConsole *con) { if (qemu_co_queue_empty(&con->dump_queue)) { /* Defer the update, it will restart the pending coroutines */ aio_bh_schedule_oneshot(qemu_get_aio_context(), graphic_hw_update_bh, con); } qemu_co_queue_wait(&con->dump_queue, NULL); } static void graphic_hw_gl_unblock_timer(void *opaque) { warn_report("console: no gl-unblock within one second"); } void graphic_hw_gl_block(QemuConsole *con, bool block) { uint64_t timeout; assert(con != NULL); if (block) { con->gl_block++; } else { con->gl_block--; } assert(con->gl_block >= 0); if (!con->hw_ops->gl_block) { return; } if ((block && con->gl_block != 1) || (!block && con->gl_block != 0)) { return; } con->hw_ops->gl_block(con->hw, block); if (block) { timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); timeout += 1000; /* one sec */ timer_mod(con->gl_unblock_timer, timeout); } else { timer_del(con->gl_unblock_timer); } } int qemu_console_get_window_id(QemuConsole *con) { return con->window_id; } void qemu_console_set_window_id(QemuConsole *con, int window_id) { con->window_id = window_id; } void graphic_hw_invalidate(QemuConsole *con) { if (con && con->hw_ops->invalidate) { con->hw_ops->invalidate(con->hw); } } void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata) { if (con && con->hw_ops->text_update) { con->hw_ops->text_update(con->hw, chardata); } } static void displaychangelistener_gfx_switch(DisplayChangeListener *dcl, struct DisplaySurface *new_surface, bool update) { if (dcl->ops->dpy_gfx_switch) { dcl->ops->dpy_gfx_switch(dcl, new_surface); } if (update && dcl->ops->dpy_gfx_update) { dcl->ops->dpy_gfx_update(dcl, 0, 0, surface_width(new_surface), surface_height(new_surface)); } } static void dpy_gfx_create_texture(QemuConsole *con, DisplaySurface *surface) { if (con->gl && con->gl->ops->dpy_gl_ctx_create_texture) { con->gl->ops->dpy_gl_ctx_create_texture(con->gl, surface); } } static void dpy_gfx_destroy_texture(QemuConsole *con, DisplaySurface *surface) { if (con->gl && con->gl->ops->dpy_gl_ctx_destroy_texture) { con->gl->ops->dpy_gl_ctx_destroy_texture(con->gl, surface); } } static void dpy_gfx_update_texture(QemuConsole *con, DisplaySurface *surface, int x, int y, int w, int h) { if (con->gl && con->gl->ops->dpy_gl_ctx_update_texture) { con->gl->ops->dpy_gl_ctx_update_texture(con->gl, surface, x, y, w, h); } } static void displaychangelistener_display_console(DisplayChangeListener *dcl, Error **errp) { static const char nodev[] = "This VM has no graphic display device."; static DisplaySurface *dummy; QemuConsole *con = dcl->con; if (!con || !console_compatible_with(con, dcl, errp)) { if (!dummy) { dummy = qemu_create_placeholder_surface(640, 480, nodev); } if (con) { dpy_gfx_create_texture(con, dummy); } displaychangelistener_gfx_switch(dcl, dummy, TRUE); return; } dpy_gfx_create_texture(con, con->surface); displaychangelistener_gfx_switch(dcl, con->surface, con->scanout.kind == SCANOUT_SURFACE); if (con->scanout.kind == SCANOUT_DMABUF && displaychangelistener_has_dmabuf(dcl)) { dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf); } else if (con->scanout.kind == SCANOUT_TEXTURE && dcl->ops->dpy_gl_scanout_texture) { dcl->ops->dpy_gl_scanout_texture(dcl, con->scanout.texture.backing_id, con->scanout.texture.backing_y_0_top, con->scanout.texture.backing_width, con->scanout.texture.backing_height, con->scanout.texture.x, con->scanout.texture.y, con->scanout.texture.width, con->scanout.texture.height, con->scanout.texture.d3d_tex2d); } } void qemu_text_console_put_keysym(QemuTextConsole *s, int keysym) { qemu_text_console_handle_keysym(s, keysym); } static const int qcode_to_keysym[Q_KEY_CODE__MAX] = { [Q_KEY_CODE_UP] = QEMU_KEY_UP, [Q_KEY_CODE_DOWN] = QEMU_KEY_DOWN, [Q_KEY_CODE_RIGHT] = QEMU_KEY_RIGHT, [Q_KEY_CODE_LEFT] = QEMU_KEY_LEFT, [Q_KEY_CODE_HOME] = QEMU_KEY_HOME, [Q_KEY_CODE_END] = QEMU_KEY_END, [Q_KEY_CODE_PGUP] = QEMU_KEY_PAGEUP, [Q_KEY_CODE_PGDN] = QEMU_KEY_PAGEDOWN, [Q_KEY_CODE_DELETE] = QEMU_KEY_DELETE, [Q_KEY_CODE_TAB] = QEMU_KEY_TAB, [Q_KEY_CODE_BACKSPACE] = QEMU_KEY_BACKSPACE, }; static const int ctrl_qcode_to_keysym[Q_KEY_CODE__MAX] = { [Q_KEY_CODE_UP] = QEMU_KEY_CTRL_UP, [Q_KEY_CODE_DOWN] = QEMU_KEY_CTRL_DOWN, [Q_KEY_CODE_RIGHT] = QEMU_KEY_CTRL_RIGHT, [Q_KEY_CODE_LEFT] = QEMU_KEY_CTRL_LEFT, [Q_KEY_CODE_HOME] = QEMU_KEY_CTRL_HOME, [Q_KEY_CODE_END] = QEMU_KEY_CTRL_END, [Q_KEY_CODE_PGUP] = QEMU_KEY_CTRL_PAGEUP, [Q_KEY_CODE_PGDN] = QEMU_KEY_CTRL_PAGEDOWN, }; bool qemu_text_console_put_qcode(QemuTextConsole *s, int qcode, bool ctrl) { int keysym; keysym = ctrl ? ctrl_qcode_to_keysym[qcode] : qcode_to_keysym[qcode]; if (keysym == 0) { return false; } qemu_text_console_put_keysym(s, keysym); return true; } void qemu_text_console_put_string(QemuTextConsole *s, const char *str, int len) { int i; for (i = 0; i < len && str[i]; i++) { qemu_text_console_put_keysym(s, str[i]); } } static void qemu_console_register(QemuConsole *c) { int i; if (QTAILQ_EMPTY(&consoles)) { c->index = 0; QTAILQ_INSERT_TAIL(&consoles, c, next); } else if (!QEMU_IS_GRAPHIC_CONSOLE(c) || phase_check(PHASE_MACHINE_READY)) { QemuConsole *last = QTAILQ_LAST(&consoles); c->index = last->index + 1; QTAILQ_INSERT_TAIL(&consoles, c, next); } else { /* * HACK: Put graphical consoles before text consoles. * * Only do that for coldplugged devices. After initial device * initialization we will not renumber the consoles any more. */ QemuConsole *it = QTAILQ_FIRST(&consoles); while (QTAILQ_NEXT(it, next) != NULL && QEMU_IS_GRAPHIC_CONSOLE(it)) { it = QTAILQ_NEXT(it, next); } if (QEMU_IS_GRAPHIC_CONSOLE(it)) { /* have no text consoles */ c->index = it->index + 1; QTAILQ_INSERT_AFTER(&consoles, it, c, next); } else { c->index = it->index; QTAILQ_INSERT_BEFORE(it, c, next); /* renumber text consoles */ for (i = c->index + 1; it != NULL; it = QTAILQ_NEXT(it, next), i++) { it->index = i; } } } } static void qemu_console_finalize(Object *obj) { QemuConsole *c = QEMU_CONSOLE(obj); /* TODO: check this code path, and unregister from consoles */ g_clear_pointer(&c->surface, qemu_free_displaysurface); g_clear_pointer(&c->gl_unblock_timer, timer_free); g_clear_pointer(&c->ui_timer, timer_free); } static void qemu_console_class_init(ObjectClass *oc, void *data) { } static void qemu_console_init(Object *obj) { QemuConsole *c = QEMU_CONSOLE(obj); DisplayState *ds = get_alloc_displaystate(); qemu_co_queue_init(&c->dump_queue); c->ds = ds; c->window_id = -1; c->ui_timer = timer_new_ms(QEMU_CLOCK_REALTIME, dpy_set_ui_info_timer, c); qemu_console_register(c); } static void qemu_graphic_console_finalize(Object *obj) { QemuGraphicConsole *c = QEMU_GRAPHIC_CONSOLE(obj); g_clear_pointer(&c->device, object_unref); } static void qemu_graphic_console_prop_get_head(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) { QemuGraphicConsole *c = QEMU_GRAPHIC_CONSOLE(obj); visit_type_uint32(v, name, &c->head, errp); } static void qemu_graphic_console_class_init(ObjectClass *oc, void *data) { object_class_property_add_link(oc, "device", TYPE_DEVICE, offsetof(QemuGraphicConsole, device), object_property_allow_set_link, OBJ_PROP_LINK_STRONG); object_class_property_add(oc, "head", "uint32", qemu_graphic_console_prop_get_head, NULL, NULL, NULL); } static void qemu_graphic_console_init(Object *obj) { } void qemu_displaysurface_set_share_handle(DisplaySurface *surface, qemu_pixman_shareable handle, uint32_t offset) { assert(surface->share_handle == SHAREABLE_NONE); surface->share_handle = handle; surface->share_handle_offset = offset; } DisplaySurface *qemu_create_displaysurface(int width, int height) { trace_displaysurface_create(width, height); return qemu_create_displaysurface_from( width, height, PIXMAN_x8r8g8b8, width * 4, NULL ); } DisplaySurface *qemu_create_displaysurface_from(int width, int height, pixman_format_code_t format, int linesize, uint8_t *data) { DisplaySurface *surface = g_new0(DisplaySurface, 1); trace_displaysurface_create_from(surface, width, height, format); surface->share_handle = SHAREABLE_NONE; if (data) { surface->image = pixman_image_create_bits(format, width, height, (void *)data, linesize); } else { qemu_pixman_image_new_shareable(&surface->image, &surface->share_handle, "displaysurface", format, width, height, linesize, &error_abort); surface->flags = QEMU_ALLOCATED_FLAG; } assert(surface->image != NULL); return surface; } DisplaySurface *qemu_create_displaysurface_pixman(pixman_image_t *image) { DisplaySurface *surface = g_new0(DisplaySurface, 1); trace_displaysurface_create_pixman(surface); surface->share_handle = SHAREABLE_NONE; surface->image = pixman_image_ref(image); return surface; } DisplaySurface *qemu_create_placeholder_surface(int w, int h, const char *msg) { DisplaySurface *surface = qemu_create_displaysurface(w, h); #ifdef CONFIG_PIXMAN pixman_color_t bg = QEMU_PIXMAN_COLOR_BLACK; pixman_color_t fg = QEMU_PIXMAN_COLOR_GRAY; pixman_image_t *glyph; int len, x, y, i; len = strlen(msg); x = (w / FONT_WIDTH - len) / 2; y = (h / FONT_HEIGHT - 1) / 2; for (i = 0; i < len; i++) { glyph = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, msg[i]); qemu_pixman_glyph_render(glyph, surface->image, &fg, &bg, x+i, y, FONT_WIDTH, FONT_HEIGHT); qemu_pixman_image_unref(glyph); } #endif surface->flags |= QEMU_PLACEHOLDER_FLAG; return surface; } void qemu_free_displaysurface(DisplaySurface *surface) { if (surface == NULL) { return; } trace_displaysurface_free(surface); qemu_pixman_image_unref(surface->image); g_free(surface); } bool console_has_gl(QemuConsole *con) { return con->gl != NULL; } static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl) { if (dcl->ops->dpy_has_dmabuf) { return dcl->ops->dpy_has_dmabuf(dcl); } if (dcl->ops->dpy_gl_scanout_dmabuf) { return true; } return false; } static bool console_compatible_with(QemuConsole *con, DisplayChangeListener *dcl, Error **errp) { int flags; flags = con->hw_ops->get_flags ? con->hw_ops->get_flags(con->hw) : 0; if (console_has_gl(con) && !con->gl->ops->dpy_gl_ctx_is_compatible_dcl(con->gl, dcl)) { error_setg(errp, "Display %s is incompatible with the GL context", dcl->ops->dpy_name); return false; } if (flags & GRAPHIC_FLAGS_GL && !console_has_gl(con)) { error_setg(errp, "The console requires a GL context."); return false; } if (flags & GRAPHIC_FLAGS_DMABUF && !displaychangelistener_has_dmabuf(dcl)) { error_setg(errp, "The console requires display DMABUF support."); return false; } return true; } void console_handle_touch_event(QemuConsole *con, struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX], uint64_t num_slot, int width, int height, double x, double y, InputMultiTouchType type, Error **errp) { struct touch_slot *slot; bool needs_sync = false; int update; int i; if (num_slot >= INPUT_EVENT_SLOTS_MAX) { error_setg(errp, "Unexpected touch slot number: % " PRId64" >= %d", num_slot, INPUT_EVENT_SLOTS_MAX); return; } slot = &touch_slots[num_slot]; slot->x = x; slot->y = y; if (type == INPUT_MULTI_TOUCH_TYPE_BEGIN) { slot->tracking_id = num_slot; } for (i = 0; i < INPUT_EVENT_SLOTS_MAX; ++i) { if (i == num_slot) { update = type; } else { update = INPUT_MULTI_TOUCH_TYPE_UPDATE; } slot = &touch_slots[i]; if (slot->tracking_id == -1) { continue; } if (update == INPUT_MULTI_TOUCH_TYPE_END) { slot->tracking_id = -1; qemu_input_queue_mtt(con, update, i, slot->tracking_id); needs_sync = true; } else { qemu_input_queue_mtt(con, update, i, slot->tracking_id); qemu_input_queue_btn(con, INPUT_BUTTON_TOUCH, true); qemu_input_queue_mtt_abs(con, INPUT_AXIS_X, (int) slot->x, 0, width, i, slot->tracking_id); qemu_input_queue_mtt_abs(con, INPUT_AXIS_Y, (int) slot->y, 0, height, i, slot->tracking_id); needs_sync = true; } } if (needs_sync) { qemu_input_event_sync(); } } void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl) { /* display has opengl support */ assert(con); if (con->gl) { error_report("The console already has an OpenGL context."); exit(1); } con->gl = gl; } static void dcl_set_graphic_cursor(DisplayChangeListener *dcl, QemuGraphicConsole *con) { if (con && con->cursor && dcl->ops->dpy_cursor_define) { dcl->ops->dpy_cursor_define(dcl, con->cursor); } if (con && dcl->ops->dpy_mouse_set) { dcl->ops->dpy_mouse_set(dcl, con->cursor_x, con->cursor_y, con->cursor_on); } } void register_displaychangelistener(DisplayChangeListener *dcl) { assert(!dcl->ds); trace_displaychangelistener_register(dcl, dcl->ops->dpy_name); dcl->ds = get_alloc_displaystate(); QLIST_INSERT_HEAD(&dcl->ds->listeners, dcl, next); gui_setup_refresh(dcl->ds); if (dcl->con) { dcl->con->dcls++; } displaychangelistener_display_console(dcl, &error_fatal); if (QEMU_IS_GRAPHIC_CONSOLE(dcl->con)) { dcl_set_graphic_cursor(dcl, QEMU_GRAPHIC_CONSOLE(dcl->con)); } else if (QEMU_IS_TEXT_CONSOLE(dcl->con)) { qemu_text_console_update_size(QEMU_TEXT_CONSOLE(dcl->con)); } qemu_text_console_update_cursor(); } void update_displaychangelistener(DisplayChangeListener *dcl, uint64_t interval) { DisplayState *ds = dcl->ds; dcl->update_interval = interval; if (!ds->refreshing && ds->update_interval > interval) { timer_mod(ds->gui_timer, ds->last_update + interval); } } void unregister_displaychangelistener(DisplayChangeListener *dcl) { DisplayState *ds = dcl->ds; trace_displaychangelistener_unregister(dcl, dcl->ops->dpy_name); if (dcl->con) { dcl->con->dcls--; } QLIST_REMOVE(dcl, next); dcl->ds = NULL; gui_setup_refresh(ds); } static void dpy_set_ui_info_timer(void *opaque) { QemuConsole *con = opaque; uint32_t head = qemu_console_get_head(con); con->hw_ops->ui_info(con->hw, head, &con->ui_info); } bool dpy_ui_info_supported(const QemuConsole *con) { if (con == NULL) { return false; } return con->hw_ops->ui_info != NULL; } const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con) { assert(dpy_ui_info_supported(con)); return &con->ui_info; } int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay) { if (!dpy_ui_info_supported(con)) { return -1; } if (memcmp(&con->ui_info, info, sizeof(con->ui_info)) == 0) { /* nothing changed -- ignore */ return 0; } /* * Typically we get a flood of these as the user resizes the window. * Wait until the dust has settled (one second without updates), then * go notify the guest. */ con->ui_info = *info; timer_mod(con->ui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + (delay ? 1000 : 0)); return 0; } void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h) { DisplayState *s = con->ds; DisplayChangeListener *dcl; int width = qemu_console_get_width(con, x + w); int height = qemu_console_get_height(con, y + h); x = MAX(x, 0); y = MAX(y, 0); x = MIN(x, width); y = MIN(y, height); w = MIN(w, width - x); h = MIN(h, height - y); if (!qemu_console_is_visible(con)) { return; } dpy_gfx_update_texture(con, con->surface, x, y, w, h); QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gfx_update) { dcl->ops->dpy_gfx_update(dcl, x, y, w, h); } } } void dpy_gfx_update_full(QemuConsole *con) { int w = qemu_console_get_width(con, 0); int h = qemu_console_get_height(con, 0); dpy_gfx_update(con, 0, 0, w, h); } void dpy_gfx_replace_surface(QemuConsole *con, DisplaySurface *surface) { static const char placeholder_msg[] = "Display output is not active."; DisplayState *s = con->ds; DisplaySurface *old_surface = con->surface; DisplaySurface *new_surface = surface; DisplayChangeListener *dcl; int width; int height; if (!surface) { if (old_surface) { width = surface_width(old_surface); height = surface_height(old_surface); } else { width = 640; height = 480; } new_surface = qemu_create_placeholder_surface(width, height, placeholder_msg); } assert(old_surface != new_surface); con->scanout.kind = SCANOUT_SURFACE; con->surface = new_surface; dpy_gfx_create_texture(con, new_surface); QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } displaychangelistener_gfx_switch(dcl, new_surface, surface ? FALSE : TRUE); } dpy_gfx_destroy_texture(con, old_surface); qemu_free_displaysurface(old_surface); } bool dpy_gfx_check_format(QemuConsole *con, pixman_format_code_t format) { DisplayChangeListener *dcl; DisplayState *s = con->ds; QLIST_FOREACH(dcl, &s->listeners, next) { if (dcl->con && dcl->con != con) { /* dcl bound to another console -> skip */ continue; } if (dcl->ops->dpy_gfx_check_format) { if (!dcl->ops->dpy_gfx_check_format(dcl, format)) { return false; } } else { /* default is to allow native 32 bpp only */ if (format != qemu_default_pixman_format(32, true)) { return false; } } } return true; } static void dpy_refresh(DisplayState *s) { DisplayChangeListener *dcl; QLIST_FOREACH(dcl, &s->listeners, next) { if (dcl->ops->dpy_refresh) { dcl->ops->dpy_refresh(dcl); } } } void dpy_text_cursor(QemuConsole *con, int x, int y) { DisplayState *s = con->ds; DisplayChangeListener *dcl; if (!qemu_console_is_visible(con)) { return; } QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_text_cursor) { dcl->ops->dpy_text_cursor(dcl, x, y); } } } void dpy_text_update(QemuConsole *con, int x, int y, int w, int h) { DisplayState *s = con->ds; DisplayChangeListener *dcl; if (!qemu_console_is_visible(con)) { return; } QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_text_update) { dcl->ops->dpy_text_update(dcl, x, y, w, h); } } } void dpy_text_resize(QemuConsole *con, int w, int h) { DisplayState *s = con->ds; DisplayChangeListener *dcl; if (!qemu_console_is_visible(con)) { return; } QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_text_resize) { dcl->ops->dpy_text_resize(dcl, w, h); } } } void dpy_mouse_set(QemuConsole *c, int x, int y, bool on) { QemuGraphicConsole *con = QEMU_GRAPHIC_CONSOLE(c); DisplayState *s = c->ds; DisplayChangeListener *dcl; con->cursor_x = x; con->cursor_y = y; con->cursor_on = on; if (!qemu_console_is_visible(c)) { return; } QLIST_FOREACH(dcl, &s->listeners, next) { if (c != dcl->con) { continue; } if (dcl->ops->dpy_mouse_set) { dcl->ops->dpy_mouse_set(dcl, x, y, on); } } } void dpy_cursor_define(QemuConsole *c, QEMUCursor *cursor) { QemuGraphicConsole *con = QEMU_GRAPHIC_CONSOLE(c); DisplayState *s = c->ds; DisplayChangeListener *dcl; cursor_unref(con->cursor); con->cursor = cursor_ref(cursor); if (!qemu_console_is_visible(c)) { return; } QLIST_FOREACH(dcl, &s->listeners, next) { if (c != dcl->con) { continue; } if (dcl->ops->dpy_cursor_define) { dcl->ops->dpy_cursor_define(dcl, cursor); } } } QEMUGLContext dpy_gl_ctx_create(QemuConsole *con, struct QEMUGLParams *qparams) { assert(con->gl); return con->gl->ops->dpy_gl_ctx_create(con->gl, qparams); } void dpy_gl_ctx_destroy(QemuConsole *con, QEMUGLContext ctx) { assert(con->gl); con->gl->ops->dpy_gl_ctx_destroy(con->gl, ctx); } int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx) { assert(con->gl); return con->gl->ops->dpy_gl_ctx_make_current(con->gl, ctx); } void dpy_gl_scanout_disable(QemuConsole *con) { DisplayState *s = con->ds; DisplayChangeListener *dcl; if (con->scanout.kind != SCANOUT_SURFACE) { con->scanout.kind = SCANOUT_NONE; } QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gl_scanout_disable) { dcl->ops->dpy_gl_scanout_disable(dcl); } } } void dpy_gl_scanout_texture(QemuConsole *con, uint32_t backing_id, bool backing_y_0_top, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, uint32_t width, uint32_t height, void *d3d_tex2d) { DisplayState *s = con->ds; DisplayChangeListener *dcl; con->scanout.kind = SCANOUT_TEXTURE; con->scanout.texture = (ScanoutTexture) { backing_id, backing_y_0_top, backing_width, backing_height, x, y, width, height, d3d_tex2d, }; QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gl_scanout_texture) { dcl->ops->dpy_gl_scanout_texture(dcl, backing_id, backing_y_0_top, backing_width, backing_height, x, y, width, height, d3d_tex2d); } } } void dpy_gl_scanout_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf) { DisplayState *s = con->ds; DisplayChangeListener *dcl; con->scanout.kind = SCANOUT_DMABUF; con->scanout.dmabuf = dmabuf; QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gl_scanout_dmabuf) { dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf); } } } void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf, bool have_hot, uint32_t hot_x, uint32_t hot_y) { DisplayState *s = con->ds; DisplayChangeListener *dcl; QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gl_cursor_dmabuf) { dcl->ops->dpy_gl_cursor_dmabuf(dcl, dmabuf, have_hot, hot_x, hot_y); } } } void dpy_gl_cursor_position(QemuConsole *con, uint32_t pos_x, uint32_t pos_y) { DisplayState *s = con->ds; DisplayChangeListener *dcl; QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gl_cursor_position) { dcl->ops->dpy_gl_cursor_position(dcl, pos_x, pos_y); } } } void dpy_gl_release_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf) { DisplayState *s = con->ds; DisplayChangeListener *dcl; QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gl_release_dmabuf) { dcl->ops->dpy_gl_release_dmabuf(dcl, dmabuf); } } } void dpy_gl_update(QemuConsole *con, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { DisplayState *s = con->ds; DisplayChangeListener *dcl; assert(con->gl); graphic_hw_gl_block(con, true); QLIST_FOREACH(dcl, &s->listeners, next) { if (con != dcl->con) { continue; } if (dcl->ops->dpy_gl_update) { dcl->ops->dpy_gl_update(dcl, x, y, w, h); } } graphic_hw_gl_block(con, false); } /***********************************************************/ /* register display */ /* console.c internal use only */ static DisplayState *get_alloc_displaystate(void) { if (!display_state) { display_state = g_new0(DisplayState, 1); } return display_state; } /* * Called by main(), after creating QemuConsoles * and before initializing ui (sdl/vnc/...). */ DisplayState *init_displaystate(void) { gchar *name; QemuConsole *con; QTAILQ_FOREACH(con, &consoles, next) { /* Hook up into the qom tree here (not in object_new()), once * all QemuConsoles are created and the order / numbering * doesn't change any more */ name = g_strdup_printf("console[%d]", con->index); object_property_add_child(container_get(object_get_root(), "/backend"), name, OBJECT(con)); g_free(name); } return display_state; } void graphic_console_set_hwops(QemuConsole *con, const GraphicHwOps *hw_ops, void *opaque) { con->hw_ops = hw_ops; con->hw = opaque; } QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head, const GraphicHwOps *hw_ops, void *opaque) { static const char noinit[] = "Guest has not initialized the display (yet)."; int width = 640; int height = 480; QemuConsole *s; DisplaySurface *surface; s = qemu_graphic_console_lookup_unused(); if (s) { trace_console_gfx_reuse(s->index); width = qemu_console_get_width(s, 0); height = qemu_console_get_height(s, 0); } else { trace_console_gfx_new(); s = (QemuConsole *)object_new(TYPE_QEMU_GRAPHIC_CONSOLE); } QEMU_GRAPHIC_CONSOLE(s)->head = head; graphic_console_set_hwops(s, hw_ops, opaque); if (dev) { object_property_set_link(OBJECT(s), "device", OBJECT(dev), &error_abort); } surface = qemu_create_placeholder_surface(width, height, noinit); dpy_gfx_replace_surface(s, surface); s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME, graphic_hw_gl_unblock_timer, s); return s; } static const GraphicHwOps unused_ops = { /* no callbacks */ }; void graphic_console_close(QemuConsole *con) { static const char unplugged[] = "Guest display has been unplugged"; DisplaySurface *surface; int width = qemu_console_get_width(con, 640); int height = qemu_console_get_height(con, 480); trace_console_gfx_close(con->index); object_property_set_link(OBJECT(con), "device", NULL, &error_abort); graphic_console_set_hwops(con, &unused_ops, NULL); if (con->gl) { dpy_gl_scanout_disable(con); } surface = qemu_create_placeholder_surface(width, height, unplugged); dpy_gfx_replace_surface(con, surface); } QemuConsole *qemu_console_lookup_default(void) { QemuConsole *con; QTAILQ_FOREACH(con, &consoles, next) { if (QEMU_IS_GRAPHIC_CONSOLE(con)) { return con; } } return QTAILQ_FIRST(&consoles); } QemuConsole *qemu_console_lookup_by_index(unsigned int index) { QemuConsole *con; QTAILQ_FOREACH(con, &consoles, next) { if (con->index == index) { return con; } } return NULL; } QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head) { QemuConsole *con; Object *obj; uint32_t h; QTAILQ_FOREACH(con, &consoles, next) { obj = object_property_get_link(OBJECT(con), "device", &error_abort); if (DEVICE(obj) != dev) { continue; } h = object_property_get_uint(OBJECT(con), "head", &error_abort); if (h != head) { continue; } return con; } return NULL; } QemuConsole *qemu_console_lookup_by_device_name(const char *device_id, uint32_t head, Error **errp) { DeviceState *dev; QemuConsole *con; dev = qdev_find_recursive(sysbus_get_default(), device_id); if (dev == NULL) { error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, "Device '%s' not found", device_id); return NULL; } con = qemu_console_lookup_by_device(dev, head); if (con == NULL) { error_setg(errp, "Device %s (head %d) is not bound to a QemuConsole", device_id, head); return NULL; } return con; } static QemuConsole *qemu_graphic_console_lookup_unused(void) { QemuConsole *con; Object *obj; QTAILQ_FOREACH(con, &consoles, next) { if (!QEMU_IS_GRAPHIC_CONSOLE(con) || con->hw_ops != &unused_ops) { continue; } obj = object_property_get_link(OBJECT(con), "device", &error_abort); if (obj != NULL) { continue; } return con; } return NULL; } QEMUCursor *qemu_console_get_cursor(QemuConsole *con) { return QEMU_IS_GRAPHIC_CONSOLE(con) ? QEMU_GRAPHIC_CONSOLE(con)->cursor : NULL; } bool qemu_console_is_visible(QemuConsole *con) { return con->dcls > 0; } bool qemu_console_is_graphic(QemuConsole *con) { return con && QEMU_IS_GRAPHIC_CONSOLE(con); } bool qemu_console_is_fixedsize(QemuConsole *con) { return con && (QEMU_IS_GRAPHIC_CONSOLE(con) || QEMU_IS_FIXED_TEXT_CONSOLE(con)); } bool qemu_console_is_gl_blocked(QemuConsole *con) { assert(con != NULL); return con->gl_block; } static bool qemu_graphic_console_is_multihead(QemuGraphicConsole *c) { QemuConsole *con; QTAILQ_FOREACH(con, &consoles, next) { QemuGraphicConsole *candidate; if (!QEMU_IS_GRAPHIC_CONSOLE(con)) { continue; } candidate = QEMU_GRAPHIC_CONSOLE(con); if (candidate->device != c->device) { continue; } if (candidate->head != c->head) { return true; } } return false; } char *qemu_console_get_label(QemuConsole *con) { if (QEMU_IS_GRAPHIC_CONSOLE(con)) { QemuGraphicConsole *c = QEMU_GRAPHIC_CONSOLE(con); if (c->device) { DeviceState *dev; bool multihead; dev = DEVICE(c->device); multihead = qemu_graphic_console_is_multihead(c); if (multihead) { return g_strdup_printf("%s.%d", dev->id ? dev->id : object_get_typename(c->device), c->head); } else { return g_strdup_printf("%s", dev->id ? dev->id : object_get_typename(c->device)); } } return g_strdup("VGA"); } else if (QEMU_IS_TEXT_CONSOLE(con)) { const char *label = qemu_text_console_get_label(QEMU_TEXT_CONSOLE(con)); if (label) { return g_strdup(label); } } return g_strdup_printf("vc%d", con->index); } int qemu_console_get_index(QemuConsole *con) { return con ? con->index : -1; } uint32_t qemu_console_get_head(QemuConsole *con) { if (con == NULL) { return -1; } if (QEMU_IS_GRAPHIC_CONSOLE(con)) { return QEMU_GRAPHIC_CONSOLE(con)->head; } return 0; } int qemu_console_get_width(QemuConsole *con, int fallback) { if (con == NULL) { return fallback; } switch (con->scanout.kind) { case SCANOUT_DMABUF: return qemu_dmabuf_get_width(con->scanout.dmabuf); case SCANOUT_TEXTURE: return con->scanout.texture.width; case SCANOUT_SURFACE: return surface_width(con->surface); default: return fallback; } } int qemu_console_get_height(QemuConsole *con, int fallback) { if (con == NULL) { return fallback; } switch (con->scanout.kind) { case SCANOUT_DMABUF: return qemu_dmabuf_get_height(con->scanout.dmabuf); case SCANOUT_TEXTURE: return con->scanout.texture.height; case SCANOUT_SURFACE: return surface_height(con->surface); default: return fallback; } } int qemu_invalidate_text_consoles(void) { QemuConsole *s; int count = 0; QTAILQ_FOREACH(s, &consoles, next) { if (qemu_console_is_graphic(s) || !qemu_console_is_visible(s)) { continue; } count++; graphic_hw_invalidate(s); } return count; } void qemu_console_resize(QemuConsole *s, int width, int height) { DisplaySurface *surface = qemu_console_surface(s); assert(QEMU_IS_GRAPHIC_CONSOLE(s)); if ((s->scanout.kind != SCANOUT_SURFACE || (surface && surface_is_allocated(surface) && !surface_is_placeholder(surface))) && qemu_console_get_width(s, -1) == width && qemu_console_get_height(s, -1) == height) { return; } surface = qemu_create_displaysurface(width, height); dpy_gfx_replace_surface(s, surface); } DisplaySurface *qemu_console_surface(QemuConsole *console) { switch (console->scanout.kind) { case SCANOUT_SURFACE: return console->surface; default: return NULL; } } PixelFormat qemu_default_pixelformat(int bpp) { pixman_format_code_t fmt = qemu_default_pixman_format(bpp, true); PixelFormat pf = qemu_pixelformat_from_pixman(fmt); return pf; } static QemuDisplay *dpys[DISPLAY_TYPE__MAX]; void qemu_display_register(QemuDisplay *ui) { assert(ui->type < DISPLAY_TYPE__MAX); dpys[ui->type] = ui; } bool qemu_display_find_default(DisplayOptions *opts) { static DisplayType prio[] = { #if defined(CONFIG_GTK) DISPLAY_TYPE_GTK, #endif #if defined(CONFIG_SDL) DISPLAY_TYPE_SDL, #endif #if defined(CONFIG_COCOA) DISPLAY_TYPE_COCOA #endif }; int i; for (i = 0; i < (int)ARRAY_SIZE(prio); i++) { if (dpys[prio[i]] == NULL) { Error *local_err = NULL; int rv = ui_module_load(DisplayType_str(prio[i]), &local_err); if (rv < 0) { error_report_err(local_err); } } if (dpys[prio[i]] == NULL) { continue; } opts->type = prio[i]; return true; } return false; } void qemu_display_early_init(DisplayOptions *opts) { assert(opts->type < DISPLAY_TYPE__MAX); if (opts->type == DISPLAY_TYPE_NONE) { return; } if (dpys[opts->type] == NULL) { Error *local_err = NULL; int rv = ui_module_load(DisplayType_str(opts->type), &local_err); if (rv < 0) { error_report_err(local_err); } } if (dpys[opts->type] == NULL) { error_report("Display '%s' is not available.", DisplayType_str(opts->type)); exit(1); } if (dpys[opts->type]->early_init) { dpys[opts->type]->early_init(opts); } } void qemu_display_init(DisplayState *ds, DisplayOptions *opts) { assert(opts->type < DISPLAY_TYPE__MAX); if (opts->type == DISPLAY_TYPE_NONE) { return; } assert(dpys[opts->type] != NULL); dpys[opts->type]->init(ds, opts); } const char *qemu_display_get_vc(DisplayOptions *opts) { #ifdef CONFIG_PIXMAN const char *vc = "vc:80Cx24C"; #else const char *vc = NULL; #endif assert(opts->type < DISPLAY_TYPE__MAX); if (dpys[opts->type] && dpys[opts->type]->vc) { vc = dpys[opts->type]->vc; } return vc; } void qemu_display_help(void) { int idx; printf("Available display backend types:\n"); printf("none\n"); for (idx = DISPLAY_TYPE_NONE; idx < DISPLAY_TYPE__MAX; idx++) { if (!dpys[idx]) { Error *local_err = NULL; int rv = ui_module_load(DisplayType_str(idx), &local_err); if (rv < 0) { error_report_err(local_err); } } if (dpys[idx]) { printf("%s\n", DisplayType_str(dpys[idx]->type)); } } printf("\n" "Some display backends support suboptions, which can be set with\n" " -display backend,option=value,option=value...\n" "For a short list of the suboptions for each display, see the " "top-level -help output; more detail is in the documentation.\n"); }