| /* | 
 |  * 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"); | 
 | } |