Merge tag 'ui-pr-v1' of https://gitlab.com/marcandre.lureau/qemu into staging

UI patches

- ui: better console hotplug support
- vga: implement blinking

To: qemu-devel@nongnu.org
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCgAdFiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAmo7wlsACgkQ2ujhCXWW
# nOUAaA/+OWDMM1SdVCk9M9K/sWiSP2fsXAB+Zpdj9mUKz4B86ulVAc5Wv0ROMKyC
# hO9XT8ksCC5Xuehhfb1gxhazA46z9QWDiZ1Drnm945WUJF9U6zcqMS9w343HNL/p
# HE+wCxz+D2Izx0wrlPHVPnNp+54Ge/YK4vNjfGKSXdwFGgQPfqMX3D+chL57/quG
# xttjbDNixplqxKGqzmQRIZKgifyrMUs1knLv+bi7BHGnKIws1ww5EwOAF9zfY626
# ouo5knVNqRKMekCudIudWIYKNtVSV2WPQz/k2AdNq5L0NeI2Mj6tFswVSfLlZ1LZ
# Np0Ijeip2EOYLuPTZJKZmAWlsUK1E/VXqpehzmZdi07i37kHJBafppPOD2//P3Bs
# UB8pXVcjlXrHxEwkwEKRLHsAbivmD/bszLeNj+/E1V9M7P9pIc0jiEYPG+9WniYv
# CJZ9w9Q9S7C1IKjRIRtR/veLI4MxBu/gz5DG8qzD2GfRVpGUV8J5loSc2CidBvCR
# Vk7fetkBLh9vWgWqqbBBguHieiqoWqJZ3OL+F+tMRfiA7WIJgl2K6hRlmA5baQpW
# pD794a2bn6h6jK26natfdA6Ns4NfWc4R/3lZRNqX3cPjyAy98nxdmCQwDJBV61e9
# e9YKU2tJ6zruUs3Oa9/XW/W4kyEm+aBXdysg5KziXUKLx3pHTOY=
# =N7bZ
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 24 Jun 2026 07:41:15 EDT
# gpg:                using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5
# gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [full]
# gpg:                 aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [full]
# Primary key fingerprint: 87A9 BD93 3F87 C606 D276  F62D DAE8 E109 7596 9CE5

* tag 'ui-pr-v1' of https://gitlab.com/marcandre.lureau/qemu: (35 commits)
  vga: implement text mode character blink
  tests/qtest: add D-Bus display hotplug test
  ui/dbus: handle console hotplug/unplug events
  ui/console: unregister console from QOM tree on close
  ui/console: register console in QOM tree dynamically
  ui/gtk: handle console hotplug/unplug events
  ui/gtk: centralize console menu and shortcut management
  ui/gtk: fix tab re-insertion order on window close
  ui/gtk: move global display settings out of per-console init
  ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray
  ui/console-vc: fire ADDED/REMOVED notifications
  ui/console: fire console ADDED/REMOVED notifications
  ui/console: add console event notifier infrastructure
  ui/gtk: implement display cleanup
  ui/dbus: implement display cleanup
  ui/cocoa: implement display cleanup
  ui/egl: implement display and EGL cleanup
  ui/spice-app: implement display cleanup
  ui/sdl2: implement display cleanup
  ui/curses: implement display cleanup
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi
2026-06-25 16:58:35 -04:00
41 changed files with 1033 additions and 198 deletions

View File

@@ -2348,6 +2348,7 @@ S: Supported
F: hw/vfio/*
F: util/vfio-helpers.c
F: include/hw/vfio/
F: docs/devel/vfio-mdpy.rst
F: docs/devel/migration/vfio.rst
F: qapi/vfio.json
F: migration/vfio-stub.c

View File

@@ -22,6 +22,7 @@ Details about QEMU's various subsystems including how to add features to them.
tracing
uefi-vars
vfio-iommufd
vfio-mdpy
writing-monitor-commands
virtio-backends
crypto

90
docs/devel/vfio-mdpy.rst Normal file
View File

@@ -0,0 +1,90 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
===================================
Testing VFIO display with mdev mdpy
===================================
.. contents:: Table of Contents
The kernel provides a sample mediated device driver, ``mdpy``
(``samples/vfio-mdev/mdpy.c``), that exposes a fake framebuffer through the VFIO
display region interface. It can be used to test VFIO display support, including
hotplug, without any real GPU hardware.
The kernel modules
==================
The ``mdpy`` driver depends on the ``mdev`` subsystem. Enable, build and load
the modules.
The minimal set is::
CONFIG_SAMPLE_VFIO_MDEV_MDPY=m
CONFIG_SAMPLE_VFIO_MDEV_MDPY_FB=m # guest framebuffer driver
CONFIG_VFIO_MDEV is selected automatically.
Verify that the driver registered successfully:
.. code-block:: bash
ls /sys/devices/virtual/mdpy/mdpy/mdev_supported_types/
Creating an mdev instance
=========================
Available types correspond to different resolutions (e.g. ``mdpy-vga``
for 640x480, ``mdpy-xga`` for 1024x768, ``mdpy-hd`` for 1920x1080).
Each mdev instance is identified by a UUID:
.. code-block:: bash
uuid=$(uuidgen)
echo "$uuid" > /sys/devices/virtual/mdpy/mdpy/mdev_supported_types/mdpy-xga/create
To remove the instance later:
.. code-block:: bash
echo 1 > /sys/bus/mdev/devices/$uuid/remove
Make sure your user has the necessary permissions to access the vfio group.
(ex: chmod 666 /dev/vfio/16)
Starting QEMU
=============
Boot-time attachment
--------------------
.. code-block:: bash
qemu-system-x86_64 -machine q35 -m 1G \
-device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$uuid,display=on \
-display gtk,gl=on
Hotplug via HMP
---------------
Start QEMU with a PCIe root port (required for PCIe hotplug) and a
monitor:
.. code-block:: bash
qemu-system-x86_64 -machine q35 -m 1G \
-device pcie-root-port,id=rp0,slot=1 \
-display gtk,gl=on \
-monitor stdio
Then at the ``(qemu)`` prompt:
.. code-block:: none
device_add vfio-pci,sysfsdev=/sys/bus/mdev/devices/<uuid>,display=on,bus=rp0,id=mdpy0
To hot-unplug:
.. code-block:: none
device_del mdpy0

View File

@@ -32,6 +32,9 @@ void qemu_set_irq(qemu_irq irq, int level)
return;
irq->handler(irq->opaque, irq->n, level);
if (unlikely(irq->observer)) {
irq->observer(irq->opaque, irq->n, level);
}
}
static void init_irq_fields(IRQState *irq, qemu_irq_handler handler,
@@ -111,7 +114,7 @@ static void qemu_notirq(void *opaque, int line, int level)
{
IRQState *irq = opaque;
irq->handler(irq->opaque, irq->n, !level);
qemu_set_irq(irq, !level);
}
qemu_irq qemu_irq_invert(qemu_irq irq)
@@ -121,14 +124,11 @@ qemu_irq qemu_irq_invert(qemu_irq irq)
return qemu_allocate_irq(qemu_notirq, irq, 0);
}
void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
void qemu_irq_set_observer(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
{
int i;
qemu_irq *old_irqs = qemu_allocate_irqs(NULL, NULL, n);
for (i = 0; i < n; i++) {
*old_irqs[i] = *gpio_in[i];
gpio_in[i]->handler = handler;
gpio_in[i]->opaque = &old_irqs[i];
gpio_in[i]->observer = handler;
}
}

View File

@@ -45,8 +45,10 @@
bool have_vga = true;
/* 16 state changes per vertical frame @60 Hz */
/* frame counter bit 4: cursor blink toggles every 16 frames @60 Hz */
#define VGA_TEXT_CURSOR_PERIOD_MS (1000 * 2 * 16 / 60)
/* frame counter bit 5: character blink toggles every 32 frames @60 Hz */
#define VGA_TEXT_BLINK_PERIOD_MS (1000 * 2 * 32 / 60)
/* Address mask for non-VESA modes. */
#define VGA_VRAM_SIZE (256 * KiB)
@@ -1190,7 +1192,6 @@ static void vga_get_text_resolution(VGACommonState *s, int *pwidth, int *pheight
* - double scan
* - double width
* - underline
* - flashing
*/
static void vga_draw_text(VGACommonState *s, int full_update)
{
@@ -1286,6 +1287,13 @@ static void vga_draw_text(VGACommonState *s, int full_update)
s->cursor_blink_time = now + VGA_TEXT_CURSOR_PERIOD_MS / 2;
s->cursor_visible_phase = !s->cursor_visible_phase;
}
if (now >= s->blink_time) {
s->blink_time = now + VGA_TEXT_BLINK_PERIOD_MS / 2;
s->blink_visible_phase = !s->blink_visible_phase;
if (s->ar[VGA_ATC_MODE] & 0x08) {
full_update = 1;
}
}
dest = surface_data(surface);
linesize = surface_stride(surface);
@@ -1317,8 +1325,17 @@ static void vga_draw_text(VGACommonState *s, int full_update)
#endif
font_ptr = font_base[(cattr >> 3) & 1];
font_ptr += 32 * 4 * ch;
bgcol = palette[cattr >> 4];
fgcol = palette[cattr & 0x0f];
if (s->ar[VGA_ATC_MODE] & 0x08) {
bgcol = palette[(cattr >> 4) & 0x07];
if ((cattr & 0x80) && !s->blink_visible_phase) {
fgcol = bgcol;
} else {
fgcol = palette[cattr & 0x0f];
}
} else {
bgcol = palette[cattr >> 4];
fgcol = palette[cattr & 0x0f];
}
if (cw == 16) {
vga_draw_glyph16(d1, linesize,
font_ptr, cheight, fgcol, bgcol);

View File

@@ -130,6 +130,8 @@ typedef struct VGACommonState {
uint8_t cursor_start, cursor_end;
bool cursor_visible_phase;
int64_t cursor_blink_time;
bool blink_visible_phase;
int64_t blink_time;
uint32_t cursor_offset;
const GraphicHwOps *hw_ops;
bool full_update_text;

View File

@@ -505,15 +505,6 @@ static bool vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
return true;
}
static void vfio_display_region_exit(VFIODisplay *dpy)
{
if (!dpy->region.buffer.size) {
return;
}
vfio_region_exit(&dpy->region.buffer);
vfio_region_finalize(&dpy->region.buffer);
}
/* ---------------------------------------------------------------------- */
@@ -547,17 +538,31 @@ bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
return false;
}
void vfio_display_exit(VFIOPCIDevice *vdev)
{
if (!vdev->dpy) {
return;
}
vfio_display_dmabuf_exit(vdev->dpy);
qemu_graphic_console_close(vdev->dpy->con);
if (vdev->dpy->region.buffer.size) {
vfio_region_exit(&vdev->dpy->region.buffer);
}
}
void vfio_display_finalize(VFIOPCIDevice *vdev)
{
if (!vdev->dpy) {
return;
}
qemu_graphic_console_close(vdev->dpy->con);
vfio_display_dmabuf_exit(vdev->dpy);
vfio_display_region_exit(vdev->dpy);
if (vdev->dpy->region.buffer.size) {
vfio_region_finalize(&vdev->dpy->region.buffer);
}
vfio_display_edid_exit(vdev->dpy);
g_free(vdev->dpy);
vdev->dpy = NULL;
}
static bool migrate_needed(void *opaque)

View File

@@ -3591,6 +3591,7 @@ static void vfio_pci_realize(PCIDevice *pdev, Error **errp)
return;
out_deregister:
vfio_display_exit(vdev);
if (vdev->interrupt == VFIO_INT_INTx) {
vfio_intx_disable(vdev);
}
@@ -3624,6 +3625,7 @@ static void vfio_exitfn(PCIDevice *pdev)
VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(pdev);
VFIODevice *vbasedev = &vdev->vbasedev;
vfio_display_exit(vdev);
vfio_unregister_req_notifier(vdev);
vfio_unregister_err_notifier(vdev);
pci_device_set_intx_routing_notifier(pdev, NULL);

View File

@@ -270,6 +270,7 @@ bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp);
void vfio_display_reset(VFIOPCIDevice *vdev);
bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp);
void vfio_display_exit(VFIOPCIDevice *vdev);
void vfio_display_finalize(VFIOPCIDevice *vdev);
extern const VMStateDescription vfio_display_vmstate;

View File

@@ -30,6 +30,7 @@
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <glib.h>
#include <glib/gstdio.h>
#if defined(G_OS_UNIX)
#include <glib-unix.h>
#include <sys/types.h>
@@ -129,6 +130,34 @@ qemu_g_test_slow(void)
#define g_test_thorough() qemu_g_test_slow()
#define g_test_quick() (!qemu_g_test_slow())
static inline gboolean g_clear_fd_qemu(int *fd_ptr, GError **error)
{
#if GLIB_CHECK_VERSION(2, 76, 0)
return g_clear_fd(fd_ptr, error);
#else
int fd = *fd_ptr;
*fd_ptr = -1;
if (fd < 0) {
return TRUE;
}
return g_close(fd, error);
#endif
}
#define g_clear_fd(fd, err) g_clear_fd_qemu(fd, err)
#if !GLIB_CHECK_VERSION(2, 76, 0)
static inline void _g_clear_fd_ignore_error(int *fd_ptr)
{
int errsv = errno;
g_clear_fd(fd_ptr, NULL);
errno = errsv;
}
#define g_autofd __attribute__((cleanup(_g_clear_fd_ignore_error)))
#endif
#pragma GCC diagnostic pop
#ifndef G_NORETURN

View File

@@ -14,6 +14,7 @@ struct IRQState {
qemu_irq_handler handler;
void *opaque;
int n;
qemu_irq_handler observer;
};
void qemu_set_irq(qemu_irq irq, int level);
@@ -96,9 +97,8 @@ void qemu_free_irq(qemu_irq irq);
/* Returns a new IRQ with opposite polarity. */
qemu_irq qemu_irq_invert(qemu_irq irq);
/* For internal use in qtest. Similar to qemu_irq_split, but operating
on an existing vector of qemu_irq. */
void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n);
/* For internal use in qtest. */
void qemu_irq_set_observer(qemu_irq *gpio_in, qemu_irq_handler handler, int n);
/**
* qemu_irq_is_connected: Return true if IRQ line is wired up

View File

@@ -403,6 +403,7 @@ struct QemuDisplay {
DisplayType type;
void (*early_init)(DisplayOptions *opts);
void (*init)(DisplayState *ds, DisplayOptions *opts);
void (*cleanup)(void);
const char *vc;
};
@@ -411,8 +412,27 @@ bool qemu_display_find_default(DisplayOptions *opts);
void qemu_display_early_init(DisplayOptions *opts);
void qemu_display_init(DisplayState *ds, DisplayOptions *opts);
const char *qemu_display_get_vc(DisplayOptions *opts);
void qemu_display_cleanup(void);
void qemu_display_help(void);
/*
* Console notifications:
* Handlers must be idempotent, it may notify multiple times.
*/
typedef enum {
QEMU_CONSOLE_ADDED,
QEMU_CONSOLE_REMOVED,
} QemuConsoleEventType;
typedef struct QemuConsoleEvent {
QemuConsoleEventType type;
QemuConsole *con;
} QemuConsoleEvent;
void qemu_console_add_notifier(Notifier *notifier);
void qemu_console_remove_notifier(Notifier *notifier);
void qemu_console_notify(QemuConsoleEventType type, QemuConsole *con);
/* vnc.c */
void vnc_display_add_client(const char *id, int csock, bool skipauth);
int vnc_display_password(const char *id, const char *password, Error **errp);

View File

@@ -81,6 +81,7 @@ EGLContext qemu_egl_init_ctx(void);
bool qemu_egl_has_dmabuf(void);
bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp);
void egl_cleanup(void);
const char *qemu_egl_get_error_string(void);

View File

@@ -118,9 +118,9 @@ struct GtkDisplayState {
GtkWidget *grab_item;
GtkWidget *grab_on_hover_item;
int nb_vcs;
VirtualConsole vc[MAX_VCS];
GPtrArray *vcs;
GtkWidget *vc_menu_separator;
GtkWidget *show_tabs_item;
GtkWidget *untabify_item;
GtkWidget *show_menubar_item;
@@ -140,6 +140,8 @@ struct GtkDisplayState {
GdkCursor *null_cursor;
Notifier mouse_mode_notifier;
VMChangeStateEntry *vmse;
Notifier console_notifier;
gboolean free_scale;
gboolean keep_aspect_ratio;
@@ -225,6 +227,7 @@ int gd_gl_area_make_current(DisplayGLCtx *dgc,
/* gtk-clipboard.c */
void gd_clipboard_init(GtkDisplayState *gd);
void gd_clipboard_cleanup(GtkDisplayState *gd);
void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh);

View File

@@ -26,6 +26,7 @@ typedef struct SpiceInfo SpiceInfo;
struct QemuSpiceOps {
void (*init)(void);
void (*cleanup)(void);
void (*display_init)(void);
int (*migrate_info)(const char *h, int p, int t, const char *s);
int (*set_passwd)(const char *passwd,

View File

@@ -27,19 +27,14 @@
#include "qemu/config-file.h"
void qemu_spice_input_init(void);
void qemu_spice_input_cleanup(void);
void qemu_spice_display_init(void);
void qemu_spice_display_cleanup(void);
void qemu_spice_display_init_done(void);
bool qemu_spice_have_display_interface(QemuConsole *con);
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con);
int qemu_spice_migrate_info(const char *hostname, int port, int tls_port,
const char *subject);
#else /* CONFIG_SPICE */
#include "qemu/error-report.h"
#define spice_displays 0
#endif /* CONFIG_SPICE */
static inline bool qemu_using_spice(Error **errp)

View File

@@ -545,6 +545,8 @@ if get_option('asan')
if cc.has_argument('-fsanitize=address')
qemu_cflags = ['-fsanitize=address'] + qemu_cflags
qemu_ldflags = ['-fsanitize=address'] + qemu_ldflags
# Ensure complete stack traces for LSan suppressions to match correctly.
qemu_cflags += ['-fno-omit-frame-pointer']
else
error('Your compiler does not support -fsanitize=address')
endif

View File

@@ -10,6 +10,10 @@ leak:/lib64/libtcmalloc_minimal.so.4
# libxkbcommon also leaks in qemu-keymap
leak:/lib64/libxkbcommon.so.0
# libfontconfig leaks are notorious, for ex
# https://gitlab.freedesktop.org/fontconfig/fontconfig/-/work_items/519
leak:libfontconfig.so
# g_set_user_dirs() deliberately leaks the previous cached g_get_user_*
# values. This is documented in upstream glib's valgrind-format
# suppression file:
@@ -17,10 +21,7 @@ leak:/lib64/libxkbcommon.so.0
# This avoids false positive leak reports for the qga-ssh-test.
leak:g_set_user_dirs
# qemu_irq_intercept_in is only used by the qtest harness, and
# its API inherently involves a leak.
# While we could keep track of the old IRQ data structure
# in order to free it, it doesn't seem very important to fix
# since it is only used by the qtest test harness.
# Just ignore the leak, at least for the moment.
leak:qemu_irq_intercept_in
# spice_server_add_interface allocates internal channel data that
# spice_server_destroy does not free
# https://gitlab.freedesktop.org/spice/spice/-/merge_requests/246
leak:spice_server_add_interface

View File

@@ -326,9 +326,6 @@ void qtest_sendf(CharFrontend *chr, const char *fmt, ...)
static void qtest_irq_handler(void *opaque, int n, int level)
{
qemu_irq old_irq = *(qemu_irq *)opaque;
qemu_set_irq(old_irq, level);
if (irq_levels[n] != level) {
CharFrontend *chr = &qtest->qtest_chr;
irq_levels[n] = level;
@@ -421,7 +418,7 @@ static void qtest_process_command(CharFrontend *chr, gchar **words)
interception_succeeded = true;
}
} else {
qemu_irq_intercept_in(ngl->in, qtest_irq_handler,
qemu_irq_set_observer(ngl->in, qtest_irq_handler,
ngl->num_in);
interception_succeeded = true;
}

View File

@@ -1045,9 +1045,7 @@ void qemu_cleanup(int status)
audio_cleanup();
monitor_cleanup();
qemu_chr_cleanup();
qemu_display_cleanup();
user_creatable_cleanup();
#ifdef CONFIG_VNC
vnc_cleanup();
#endif
/* TODO: unref root container, check all devices are ok */
}

View File

@@ -7,6 +7,8 @@
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "libqtest.h"
#include "qobject/qdict.h"
#include "qobject/qstring.h"
#include "ui/dbus-display1.h"
static GDBusConnection*
@@ -38,11 +40,11 @@ test_dbus_p2p_from_fd(int fd)
}
static void
test_setup(QTestState **qts, GDBusConnection **conn)
test_setup_args(QTestState **qts, GDBusConnection **conn, const char *args)
{
int pair[2];
*qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
*qts = qtest_init(args);
g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
@@ -52,6 +54,12 @@ test_setup(QTestState **qts, GDBusConnection **conn)
g_dbus_connection_start_message_processing(*conn);
}
static void
test_setup(QTestState **qts, GDBusConnection **conn)
{
test_setup_args(qts, conn, "-display dbus,p2p=yes -name dbus-test");
}
static void
test_dbus_display_vm(void)
{
@@ -360,6 +368,92 @@ test_dbus_display_keyboard(void)
qtest_quit(qts);
}
static gsize
get_console_ids_count(GDBusConnection *conn)
{
g_autoptr(GError) err = NULL;
g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
GVariant *console_ids;
gsize n_ids = 0;
vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
qemu_dbus_display1_vm_proxy_new_sync(
conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
DBUS_DISPLAY1_ROOT "/VM",
NULL,
&err));
g_assert_no_error(err);
console_ids = qemu_dbus_display1_vm_get_console_ids(
QEMU_DBUS_DISPLAY1_VM(vm));
if (console_ids) {
n_ids = g_variant_n_children(console_ids);
}
return n_ids;
}
static void
wait_device_event(QTestState *qts, const char *event_name, const char *id)
{
QDict *resp, *data;
QString *qstr;
for (;;) {
resp = qtest_qmp_eventwait_ref(qts, event_name);
data = qdict_get_qdict(resp, "data");
if (!data || !qdict_get(data, "device")) {
qobject_unref(resp);
continue;
}
qstr = qobject_to(QString, qdict_get(data, "device"));
if (!strcmp(qstring_get_str(qstr), id)) {
qobject_unref(resp);
break;
}
qobject_unref(resp);
}
}
static void
test_dbus_display_hotplug(void)
{
g_autoptr(GDBusConnection) conn = NULL;
QTestState *qts = NULL;
gsize n;
test_setup_args(&qts, &conn,
"-machine q35"
" -device pcie-root-port,id=rp0"
" -display dbus,p2p=yes"
" -name dbus-test");
n = get_console_ids_count(conn);
g_assert_cmpuint(n, ==, 1);
qtest_qmp_device_add(qts, "bochs-display", "bochs0",
"{'bus': 'rp0'}");
n = get_console_ids_count(conn);
g_assert_cmpuint(n, ==, 2);
/*
* On q35, PCI device removal is ACPI-based and requires guest
* acknowledgement. Since qtest has no guest OS, issue the delete
* request and force removal via system reset.
*/
qtest_qmp_device_del_send(qts, "bochs0");
qtest_system_reset_nowait(qts);
wait_device_event(qts, "DEVICE_DELETED", "bochs0");
n = get_console_ids_count(conn);
g_assert_cmpuint(n, ==, 1);
g_clear_object(&conn);
qtest_quit(qts);
}
int
main(int argc, char **argv)
{
@@ -369,6 +463,9 @@ main(int argc, char **argv)
qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console);
qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console);
qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
if (qtest_has_machine("q35") && qtest_has_device("bochs-display")) {
qtest_add_func("/dbus-display/hotplug", test_dbus_display_hotplug);
}
return g_test_run();
}

View File

@@ -1206,8 +1206,6 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
[cocoaView release];
[cbowner release];
cbowner = nil;
[super dealloc];
}
@@ -2038,9 +2036,26 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
qemu_main = cocoa_main;
}
static void cocoa_display_cleanup(void)
{
if (!kbd) {
return;
}
qemu_console_unregister_listener(&dcl);
g_clear_pointer(&kbd, qkbd_state_free);
qemu_remove_mouse_mode_change_notifier(&mouse_mode_change_notifier);
qemu_clipboard_peer_unregister(&cbpeer);
g_clear_pointer(&cbinfo, qemu_clipboard_info_unref);
qemu_event_destroy(&cbevent);
[cbowner release];
cbowner = nil;
}
static QemuDisplay qemu_display_cocoa = {
.type = DISPLAY_TYPE_COCOA,
.init = cocoa_display_init,
.cleanup = cocoa_display_cleanup,
};
static void register_cocoa(void)

View File

@@ -255,6 +255,7 @@ static bool vc_chr_open(Chardev *chr, ChardevBackend *backend, Error **errp)
}
qemu_chr_be_event(chr, CHR_EVENT_OPENED);
qemu_console_notify(QEMU_CONSOLE_ADDED, QEMU_CONSOLE(s));
return true;
}
@@ -327,12 +328,24 @@ static void char_vc_init(Object *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,
};

View File

@@ -42,6 +42,7 @@
#include "qemu/memfd.h"
#include "ui/vt100.h"
#include "vgafont.h"
#include "ui/qemu-spice.h"
#include "console-priv.h"
@@ -67,8 +68,10 @@ struct DisplayState {
uint64_t last_update;
uint64_t update_interval;
bool refreshing;
bool initialized;
QLIST_HEAD(, DisplayChangeListener) listeners;
NotifierList console_notifiers;
};
static DisplayState *display_state;
@@ -83,6 +86,16 @@ static bool console_compatible_with(QemuConsole *con,
static QemuConsole *qemu_graphic_console_lookup_unused(void);
static void dpy_set_ui_info_timer(void *opaque);
void qemu_console_notify(QemuConsoleEventType type, QemuConsole *con)
{
DisplayState *ds = get_alloc_displaystate();
QemuConsoleEvent event = {
.type = type,
.con = con,
};
notifier_list_notify(&ds->console_notifiers, &event);
}
static void gui_update(void *opaque)
{
uint64_t interval = GUI_REFRESH_INTERVAL_IDLE;
@@ -364,6 +377,13 @@ void qemu_text_console_put_string(QemuTextConsole *s, const char *str, int len)
}
}
static void qemu_console_add_to_qom(QemuConsole *con)
{
g_autofree gchar *name = g_strdup_printf("console[%d]", con->index);
object_property_add_child(object_get_container("backend"),
name, OBJECT(con));
}
static void
qemu_console_register(QemuConsole *c)
{
@@ -401,6 +421,10 @@ qemu_console_register(QemuConsole *c)
}
}
}
if (c->ds->initialized) {
qemu_console_add_to_qom(c);
}
}
static void
@@ -433,6 +457,8 @@ qemu_console_init(Object *obj)
c->window_id = -1;
c->ui_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
dpy_set_ui_info_timer, c);
c->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
console_hw_gl_unblock_timer, c);
qemu_console_register(c);
}
@@ -573,7 +599,7 @@ void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl)
{
/* display has opengl support */
assert(con);
if (con->gl) {
if (gl && con->gl) {
error_report("The console already has an OpenGL context.");
exit(1);
}
@@ -1053,30 +1079,41 @@ static DisplayState *get_alloc_displaystate(void)
{
if (!display_state) {
display_state = g_new0(DisplayState, 1);
notifier_list_init(&display_state->console_notifiers);
}
return display_state;
}
void qemu_console_add_notifier(Notifier *notifier)
{
DisplayState *ds = get_alloc_displaystate();
notifier_list_add(&ds->console_notifiers, notifier);
}
void qemu_console_remove_notifier(Notifier *notifier)
{
notifier_remove(notifier);
}
/*
* Called by main(), after creating QemuConsoles
* and before initializing ui (sdl/vnc/...).
*/
DisplayState *init_displaystate(void)
{
gchar *name;
DisplayState *ds = get_alloc_displaystate();
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(object_get_container("backend"),
name, OBJECT(con));
g_free(name);
qemu_console_add_to_qom(con);
}
return display_state;
ds->initialized = true;
return ds;
}
void qemu_graphic_console_set_hwops(QemuConsole *con,
@@ -1103,6 +1140,9 @@ QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head,
trace_console_gfx_reuse(s->index);
width = qemu_console_get_width(s, 0);
height = qemu_console_get_height(s, 0);
if (s->ds->initialized) {
qemu_console_add_to_qom(s);
}
} else {
trace_console_gfx_new();
s = (QemuConsole *)object_new(TYPE_QEMU_GRAPHIC_CONSOLE);
@@ -1116,8 +1156,7 @@ QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head,
surface = qemu_create_placeholder_surface(width, height, noinit);
qemu_console_set_surface(s, surface);
s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
console_hw_gl_unblock_timer, s);
qemu_console_notify(QEMU_CONSOLE_ADDED, s);
return s;
}
@@ -1134,14 +1173,17 @@ void qemu_graphic_console_close(QemuConsole *con)
int height = qemu_console_get_height(con, 480);
trace_console_gfx_close(con->index);
qemu_console_notify(QEMU_CONSOLE_REMOVED, con);
object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
qemu_graphic_console_set_hwops(con, &unused_ops, NULL);
timer_del(con->ui_timer);
if (con->gl) {
qemu_console_gl_scanout_disable(con);
}
surface = qemu_create_placeholder_surface(width, height, unplugged);
qemu_console_set_surface(con, surface);
object_unparent(OBJECT(con));
}
QemuConsole *qemu_console_lookup_default(void)
@@ -1413,6 +1455,23 @@ void qemu_display_init(DisplayState *ds, DisplayOptions *opts)
dpys[opts->type]->init(ds, opts);
}
void qemu_display_cleanup(void)
{
int i;
for (i = 0; i < DISPLAY_TYPE__MAX; i++) {
if (dpys[i] && dpys[i]->cleanup) {
dpys[i]->cleanup();
}
}
#ifdef CONFIG_VNC
vnc_cleanup();
#endif
#ifdef CONFIG_SPICE
qemu_spice.cleanup();
#endif
}
const char *qemu_display_get_vc(DisplayOptions *opts)
{
#ifdef CONFIG_PIXMAN

View File

@@ -411,11 +411,19 @@ static void curses_refresh(DisplayChangeListener *dcl)
}
}
static void curses_atexit(void)
static void curses_cleanup(void)
{
if (!dcl) {
return;
}
endwin();
g_free(vga_to_curses);
g_free(screen);
qemu_console_unregister_listener(dcl);
g_clear_pointer(&dcl, g_free);
g_clear_pointer(&screenpad, delwin);
g_clear_pointer(&vga_to_curses, g_free);
g_clear_pointer(&screen, g_free);
g_clear_pointer(&kbd_layout, kbd_layout_free);
}
/*
@@ -799,8 +807,6 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
vga_to_curses = g_new0(cchar_t, 256);
curses_setup();
curses_keyboard_setup();
atexit(curses_atexit);
curses_winch_init();
dcl = g_new0(DisplayChangeListener, 1);
@@ -812,6 +818,7 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
static QemuDisplay qemu_display_curses = {
.type = DISPLAY_TYPE_CURSES,
.init = curses_display_init,
.cleanup = curses_cleanup,
};
static void register_curses(void)

View File

@@ -154,6 +154,7 @@ dbus_display_console_dispose(GObject *object)
qemu_input_led_notifier_remove(&ddc->led_notifier);
qemu_console_unregister_listener(&ddc->dcl);
qemu_remove_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
g_clear_object(&ddc->iface_touch);
g_clear_object(&ddc->iface_mouse);
g_clear_object(&ddc->iface_kbd);
@@ -532,6 +533,11 @@ int dbus_display_console_get_index(DBusDisplayConsole *ddc)
return qemu_console_get_index(ddc->dcl.con);
}
QemuConsole *dbus_display_console_get_qemu_console(DBusDisplayConsole *ddc)
{
return ddc->dcl.con;
}
DBusDisplayConsole *
dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
{

108
ui/dbus.c
View File

@@ -142,6 +142,9 @@ dbus_display_finalize(Object *o)
{
DBusDisplay *dd = DBUS_DISPLAY(o);
if (dd->console_notifier.notify) {
qemu_console_remove_notifier(&dd->console_notifier);
}
if (dd->notifier.notify) {
dbus_display_notifier_remove(&dd->notifier);
}
@@ -164,14 +167,35 @@ dbus_display_finalize(Object *o)
dbus_display = NULL;
}
static bool
dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
static void
dbus_update_console_ids(DBusDisplay *dd)
{
g_autoptr(GArray) arr = g_array_new(FALSE, FALSE, sizeof(guint32));
for (guint i = 0; i < dd->consoles->len; i++) {
DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
guint32 idx = dbus_display_console_get_index(ddc);
g_array_append_val(arr, idx);
}
g_object_set(dd->iface, "console-ids",
g_variant_new_fixed_array(G_VARIANT_TYPE("u"),
arr->data, arr->len,
sizeof(guint32)),
NULL);
}
static bool
dbus_display_add_console(DBusDisplay *dd, QemuConsole *con, Error **errp)
{
QemuConsole *con;
DBusDisplayConsole *dbus_console;
con = qemu_console_lookup_by_index(idx);
assert(con);
for (guint i = 0; i < dd->consoles->len; i++) {
DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
if (dbus_display_console_get_qemu_console(ddc) == con) {
return true;
}
}
if (qemu_console_is_graphic(con) &&
dd->gl_mode != DISPLAY_GL_MODE_OFF) {
@@ -179,20 +203,58 @@ dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
}
dbus_console = dbus_display_console_new(dd, con);
g_ptr_array_insert(dd->consoles, idx, dbus_console);
g_ptr_array_add(dd->consoles, dbus_console);
g_dbus_object_manager_server_export(dd->server,
G_DBUS_OBJECT_SKELETON(dbus_console));
dbus_update_console_ids(dd);
return true;
}
static void
dbus_display_remove_console(DBusDisplay *dd, QemuConsole *con)
{
for (guint i = 0; i < dd->consoles->len; i++) {
DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
if (dbus_display_console_get_qemu_console(ddc) == con) {
if (display_opengl) {
qemu_console_set_display_gl_ctx(con, NULL);
}
g_dbus_object_manager_server_unexport(
dd->server,
g_dbus_object_get_object_path(G_DBUS_OBJECT(ddc)));
g_ptr_array_remove_index(dd->consoles, i);
dbus_update_console_ids(dd);
break;
}
}
}
static void
dbus_console_notify(Notifier *n, void *data)
{
DBusDisplay *dd = container_of(n, DBusDisplay, console_notifier);
QemuConsoleEvent *event = data;
switch (event->type) {
case QEMU_CONSOLE_ADDED: {
Error *err = NULL;
if (!dbus_display_add_console(dd, event->con, &err)) {
error_report_err(err);
}
break;
}
case QEMU_CONSOLE_REMOVED:
dbus_display_remove_console(dd, event->con);
break;
}
}
static void
dbus_display_complete(UserCreatable *uc, Error **errp)
{
DBusDisplay *dd = DBUS_DISPLAY(uc);
g_autoptr(GError) err = NULL;
g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
g_autoptr(GArray) consoles = NULL;
GVariant *console_ids;
int idx;
if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
@@ -233,27 +295,24 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
}
}
consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
for (idx = 0;; idx++) {
if (!qemu_console_lookup_by_index(idx)) {
QemuConsole *con = qemu_console_lookup_by_index(idx);
if (!con) {
break;
}
if (!dbus_display_add_console(dd, idx, errp)) {
if (!dbus_display_add_console(dd, con, errp)) {
return;
}
g_array_append_val(consoles, idx);
}
console_ids = g_variant_new_from_data(
G_VARIANT_TYPE("au"),
consoles->data, consoles->len * sizeof(guint32), TRUE,
(GDestroyNotify)g_array_unref, consoles);
g_steal_pointer(&consoles);
g_object_set(dd->iface,
"name", qemu_name ?: "QEMU " QEMU_VERSION,
"uuid", uuid,
"console-ids", console_ids,
NULL);
dbus_update_console_ids(dd);
dd->console_notifier.notify = dbus_console_notify;
qemu_console_add_notifier(&dd->console_notifier);
if (dd->bus) {
g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
@@ -615,10 +674,23 @@ static const TypeInfo dbus_display_info = {
}
};
static void
dbus_cleanup(void)
{
Object *o;
o = object_resolve_path_component(object_get_objects_root(),
"dbus-display");
if (o) {
object_unparent(o);
}
}
static QemuDisplay qemu_display_dbus = {
.type = DISPLAY_TYPE_DBUS,
.early_init = early_dbus_init,
.init = dbus_init,
.cleanup = dbus_cleanup,
.vc = "vc",
};

View File

@@ -60,6 +60,7 @@ struct DBusDisplay {
DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT];
Notifier notifier;
Notifier console_notifier;
};
#ifdef WIN32
@@ -86,6 +87,8 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con);
int
dbus_display_console_get_index(DBusDisplayConsole *ddc);
QemuConsole *
dbus_display_console_get_qemu_console(DBusDisplayConsole *ddc);
extern const DisplayChangeListenerOps dbus_console_dcl_ops;

View File

@@ -9,6 +9,7 @@
typedef struct egl_dpy {
DisplayChangeListener dcl;
DisplayGLCtx *ctx;
DisplaySurface *ds;
QemuGLShader *gls;
egl_fb guest_fb;
@@ -19,6 +20,8 @@ typedef struct egl_dpy {
uint32_t pos_y;
} egl_dpy;
static GPtrArray *egl_dpys;
/* ------------------------------------------------------------------ */
static void egl_refresh(DisplayChangeListener *dcl)
@@ -220,6 +223,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
egl_dpy *edpy;
int idx;
egl_dpys = g_ptr_array_new();
for (idx = 0;; idx++) {
DisplayGLCtx *ctx;
@@ -232,15 +237,41 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
edpy->gls = qemu_gl_init_shader();
ctx = g_new0(DisplayGLCtx, 1);
ctx->ops = &eglctx_ops;
edpy->ctx = ctx;
qemu_console_set_display_gl_ctx(con, ctx);
qemu_console_register_listener(con, &edpy->dcl, &egl_ops);
g_ptr_array_add(egl_dpys, edpy);
}
}
static void egl_headless_cleanup(void)
{
if (!egl_dpys) {
return;
}
for (guint i = 0; i < egl_dpys->len; i++) {
egl_dpy *edpy = g_ptr_array_index(egl_dpys, i);
qemu_console_unregister_listener(&edpy->dcl);
qemu_console_set_display_gl_ctx(edpy->dcl.con, NULL);
egl_fb_destroy(&edpy->guest_fb);
egl_fb_destroy(&edpy->cursor_fb);
egl_fb_destroy(&edpy->blit_fb);
qemu_gl_fini_shader(edpy->gls);
g_free(edpy->ctx);
g_free(edpy);
}
g_clear_pointer(&egl_dpys, g_ptr_array_unref);
egl_cleanup();
}
static QemuDisplay qemu_display_egl = {
.type = DISPLAY_TYPE_EGL_HEADLESS,
.early_init = early_egl_headless_init,
.init = egl_headless_init,
.cleanup = egl_headless_cleanup,
};
static void register_egl(void)

View File

@@ -733,3 +733,22 @@ bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp)
display_opengl = 1;
return true;
}
void egl_cleanup(void)
{
if (qemu_egl_rn_ctx) {
eglDestroyContext(qemu_egl_display, qemu_egl_rn_ctx);
qemu_egl_rn_ctx = NULL;
}
#ifdef CONFIG_GBM
g_clear_pointer(&qemu_egl_rn_gbm_dev, gbm_device_destroy);
g_clear_fd(&qemu_egl_rn_fd, NULL);
#endif
if (qemu_egl_display) {
eglReleaseThread();
eglTerminate(qemu_egl_display);
qemu_egl_display = NULL;
}
}

View File

@@ -235,3 +235,18 @@ void gd_clipboard_init(GtkDisplayState *gd)
g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY],
"owner-change", G_CALLBACK(gd_owner_change), gd);
}
void gd_clipboard_cleanup(GtkDisplayState *gd)
{
if (!gd->cbpeer.name) {
return;
}
qemu_clipboard_peer_unregister(&gd->cbpeer);
g_signal_handlers_disconnect_by_data(
gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD], gd);
g_signal_handlers_disconnect_by_data(
gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY], gd);
g_signal_handlers_disconnect_by_data(
gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY], gd);
gd->cbpeer.name = NULL;
}

387
ui/gtk.c
View File

@@ -142,6 +142,7 @@ static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
static void gd_ungrab_pointer(GtkDisplayState *s);
static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
static void gd_ungrab_keyboard(GtkDisplayState *s);
static void gd_rebuild_vc_menu(GtkDisplayState *s);
/** Utility Functions **/
@@ -150,8 +151,8 @@ static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
VirtualConsole *vc;
gint i;
for (i = 0; i < s->nb_vcs; i++) {
vc = &s->vc[i];
for (i = 0; i < s->vcs->len; i++) {
vc = g_ptr_array_index(s->vcs, i);
if (gtk_check_menu_item_get_active
(GTK_CHECK_MENU_ITEM(vc->menu_item))) {
return vc;
@@ -165,8 +166,11 @@ static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
VirtualConsole *vc;
gint i, p;
for (i = 0; i < s->nb_vcs; i++) {
vc = &s->vc[i];
if (!s->vcs) {
return NULL;
}
for (i = 0; i < s->vcs->len; i++) {
vc = g_ptr_array_index(s->vcs, i);
p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
if (p == page) {
return vc;
@@ -247,8 +251,8 @@ static void gd_update_caption(GtkDisplayState *s)
gtk_window_set_title(GTK_WINDOW(s->window), title);
g_free(title);
for (i = 0; i < s->nb_vcs; i++) {
VirtualConsole *vc = &s->vc[i];
for (i = 0; i < s->vcs->len; i++) {
VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
if (!vc->window) {
continue;
@@ -357,7 +361,7 @@ static void gtk_release_modifiers(GtkDisplayState *s)
{
VirtualConsole *vc = gd_vc_find_current(s);
if (vc->type != GD_VC_GFX ||
if (!vc || vc->type != GD_VC_GFX ||
!qemu_console_is_graphic(vc->gfx.dcl.con)) {
return;
}
@@ -702,8 +706,8 @@ static void gd_mouse_mode_change(Notifier *notify, void *data)
gd_ungrab_pointer(s);
}
}
for (i = 0; i < s->nb_vcs; i++) {
VirtualConsole *vc = &s->vc[i];
for (i = 0; i < s->vcs->len; i++) {
VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
gd_update_cursor(vc);
}
}
@@ -1461,16 +1465,36 @@ static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
gd_update_windowsize(vc);
}
static int gd_vc_notebook_pos(GtkDisplayState *s, VirtualConsole *target)
{
int pos = 0;
guint i;
for (i = 0; i < s->vcs->len; i++) {
VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
if (vc == target) {
return pos;
}
if (!vc->window) {
pos++;
}
}
g_assert_not_reached();
}
static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
void *opaque)
{
VirtualConsole *vc = opaque;
GtkDisplayState *s = vc->s;
int page;
gtk_widget_set_sensitive(vc->menu_item, true);
gd_widget_reparent(vc->window, s->notebook, vc->tab_item);
gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
vc->tab_item, vc->label);
g_object_ref(vc->tab_item);
gtk_container_remove(GTK_CONTAINER(vc->window), vc->tab_item);
page = gd_vc_notebook_pos(s, vc);
gtk_notebook_insert_page(GTK_NOTEBOOK(s->notebook),
vc->tab_item, gtk_label_new(vc->label), page);
g_object_unref(vc->tab_item);
gtk_widget_destroy(vc->window);
vc->window = NULL;
#if defined(CONFIG_OPENGL)
@@ -1484,6 +1508,8 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
}
#endif
gd_rebuild_vc_menu(s);
if (vc == gd_vc_find_by_menu(s)) {
gtk_widget_grab_focus(vc->focus);
}
@@ -1515,7 +1541,6 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
FALSE);
}
if (!vc->window) {
gtk_widget_set_sensitive(vc->menu_item, false);
vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#if defined(CONFIG_OPENGL)
if (vc->gfx.esurface) {
@@ -1542,6 +1567,7 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
}
gd_rebuild_vc_menu(s);
gd_update_geometry_hints(vc);
gd_update_caption(s);
}
@@ -1882,22 +1908,73 @@ static gboolean gd_configure(GtkWidget *widget,
/** Virtual Console Callbacks **/
static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
int idx, GSList *group, GtkWidget *view_menu)
static void gd_rebuild_vc_menu(GtkDisplayState *s)
{
vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx,
HOTKEY_MODIFIERS, 0,
g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL));
gtk_accel_label_set_accel(
GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
GSList *group = NULL;
VirtualConsole *vc;
GList *children;
gint insert_pos;
int shortcut_idx = 0;
guint i;
g_signal_connect(vc->menu_item, "activate",
G_CALLBACK(gd_menu_switch_vc), s);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
for (i = 0; i < s->vcs->len; i++) {
vc = g_ptr_array_index(s->vcs, i);
if (vc->menu_item) {
gtk_widget_destroy(vc->menu_item);
vc->menu_item = NULL;
}
}
return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
for (i = 0; i < 9; i++) {
gtk_accel_group_disconnect_key(s->accel_group,
GDK_KEY_1 + i, HOTKEY_MODIFIERS);
}
/* find insertion position (just before vc_menu_separator) */
children = gtk_container_get_children(GTK_CONTAINER(s->view_menu));
insert_pos = g_list_index(children, s->vc_menu_separator);
g_list_free(children);
/* create new menu items for each console */
for (i = 0; i < s->vcs->len; i++) {
vc = g_ptr_array_index(s->vcs, i);
vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group,
vc->label);
group = gtk_radio_menu_item_get_group(
GTK_RADIO_MENU_ITEM(vc->menu_item));
if (vc->window) {
gtk_widget_set_sensitive(vc->menu_item, false);
} else if (shortcut_idx < 9) {
guint key = GDK_KEY_1 + shortcut_idx;
gtk_accel_group_connect(s->accel_group, key,
HOTKEY_MODIFIERS, 0,
g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc),
vc, NULL));
gtk_accel_label_set_accel(
GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
key, HOTKEY_MODIFIERS);
shortcut_idx++;
}
g_signal_connect(vc->menu_item, "activate",
G_CALLBACK(gd_menu_switch_vc), s);
gtk_menu_shell_insert(GTK_MENU_SHELL(s->view_menu),
vc->menu_item, insert_pos + i);
gtk_widget_show(vc->menu_item);
}
/* sync active menu item with current notebook page */
vc = gd_vc_find_current(s);
if (vc && vc->menu_item) {
g_signal_handlers_block_by_func(vc->menu_item,
gd_menu_switch_vc, s);
gtk_check_menu_item_set_active(
GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
g_signal_handlers_unblock_by_func(vc->menu_item,
gd_menu_switch_vc, s);
}
}
#if defined(CONFIG_VTE)
@@ -2040,9 +2117,8 @@ static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
return TRUE;
}
static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
Chardev *chr, int idx,
GSList *group, GtkWidget *view_menu)
static void gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
Chardev *chr, int idx)
{
char buffer[32];
GtkWidget *box;
@@ -2058,7 +2134,6 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
snprintf(buffer, sizeof(buffer), "vc%d", idx);
vc->label = g_strdup(vc->vte.chr->label ? : buffer);
group = gd_vc_menu_init(s, vc, idx, group, view_menu);
vc->vte.terminal = vte_terminal_new();
g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
@@ -2104,19 +2179,16 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
gtk_label_new(vc->label));
qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED);
return group;
}
static void gd_vcs_init(GtkDisplayState *s, GSList *group,
GtkWidget *view_menu)
static void gd_vcs_init(GtkDisplayState *s)
{
int i;
for (i = 0; i < nb_vcs; i++) {
VirtualConsole *vc = &s->vc[s->nb_vcs];
group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
s->nb_vcs++;
VirtualConsole *vc = g_new0(VirtualConsole, 1);
g_ptr_array_add(s->vcs, vc);
gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1);
}
}
#endif /* CONFIG_VTE */
@@ -2260,14 +2332,13 @@ static bool gd_scale_valid(double scale)
return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX;
}
static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
QemuConsole *con, int idx,
GSList *group, GtkWidget *view_menu)
static VirtualConsole *
add_gfx_console(GtkDisplayState *s, QemuConsole *con)
{
VirtualConsole *vc = g_new0(VirtualConsole, 1);
const DisplayChangeListenerOps *ops = &dcl_ops;
bool zoom_to_fit = false;
int i;
g_ptr_array_add(s->vcs, vc);
vc->label = qemu_console_get_label(con);
vc->s = s;
vc->gfx.preferred_scale = 1.0;
@@ -2344,40 +2415,86 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
qemu_console_register_listener(con, &vc->gfx.dcl, ops);
gd_connect_vc_gfx_signals(vc);
group = gd_vc_menu_init(s, vc, idx, group, view_menu);
if (qemu_console_ui_info_supported(vc->gfx.dcl.con)) {
zoom_to_fit = true;
}
if (s->opts->u.gtk.has_zoom_to_fit) {
zoom_to_fit = s->opts->u.gtk.zoom_to_fit;
}
if (zoom_to_fit) {
gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
s->free_scale = true;
}
s->keep_aspect_ratio = true;
if (s->opts->u.gtk.has_keep_aspect_ratio)
s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio;
for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
struct touch_slot *slot = &touch_slots[i];
slot->tracking_id = -1;
}
return group;
return vc;
}
static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
static void gd_vc_add_gfx(GtkDisplayState *s, QemuConsole *con)
{
VirtualConsole *vc;
int i;
for (i = 0; i < (int)s->vcs->len; i++) {
VirtualConsole *v = g_ptr_array_index(s->vcs, i);
if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) {
return;
}
}
vc = add_gfx_console(s, con);
gtk_widget_show_all(vc->tab_item);
gtk_widget_realize(vc->gfx.drawing_area);
if (s->free_scale) {
gd_update_windowsize(vc);
}
gd_update_caption(s);
gd_rebuild_vc_menu(s);
}
static void gd_vc_remove_gfx(GtkDisplayState *s, QemuConsole *con)
{
VirtualConsole *vc = NULL;
guint idx;
for (idx = 0; idx < s->vcs->len; idx++) {
VirtualConsole *v = g_ptr_array_index(s->vcs, idx);
if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) {
vc = v;
break;
}
}
if (!vc) {
return;
}
if (s->kbd_owner == vc) {
gd_ungrab_keyboard(s);
}
if (s->ptr_owner == vc) {
gd_ungrab_pointer(s);
}
g_ptr_array_remove_index(s->vcs, idx);
gd_rebuild_vc_menu(s);
gd_update_caption(s);
}
static void gd_console_notify(Notifier *n, void *data)
{
GtkDisplayState *s = container_of(n, GtkDisplayState, console_notifier);
QemuConsoleEvent *event = data;
switch (event->type) {
case QEMU_CONSOLE_ADDED:
gd_vc_add_gfx(s, event->con);
break;
case QEMU_CONSOLE_REMOVED:
gd_vc_remove_gfx(s, event->con);
break;
}
}
static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
{
GSList *group = NULL;
GtkWidget *view_menu;
GtkWidget *separator;
QemuConsole *con;
int vc;
bool zoom_to_fit = false;
int vc, i;
view_menu = gtk_menu_new();
s->view_menu = view_menu;
gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
@@ -2445,18 +2562,39 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
if (!con) {
break;
}
group = gd_vc_gfx_init(s, &s->vc[vc], con,
vc, group, view_menu);
s->nb_vcs++;
add_gfx_console(s, con);
if (qemu_console_ui_info_supported(con)) {
zoom_to_fit = true;
}
}
if (s->opts->u.gtk.has_zoom_to_fit) {
zoom_to_fit = s->opts->u.gtk.zoom_to_fit;
}
if (zoom_to_fit) {
gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
s->free_scale = true;
}
s->keep_aspect_ratio = true;
if (s->opts->u.gtk.has_keep_aspect_ratio) {
s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio;
}
for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
struct touch_slot *slot = &touch_slots[i];
slot->tracking_id = -1;
}
#if defined(CONFIG_VTE)
/* vte */
gd_vcs_init(s, group, view_menu);
gd_vcs_init(s);
#endif
separator = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
s->vc_menu_separator = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->vc_menu_separator);
gd_rebuild_vc_menu(s);
s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
@@ -2475,8 +2613,6 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->show_menubar_item))),
GDK_KEY_m, HOTKEY_MODIFIERS);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_menubar_item);
return view_menu;
}
static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
@@ -2485,7 +2621,7 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
s->accel_group = gtk_accel_group_new();
s->machine_menu = gd_create_menu_machine(s);
s->view_menu = gd_create_menu_view(s, opts);
gd_create_menu_view(s, opts);
s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
@@ -2505,6 +2641,65 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
}
static void gd_vc_free(void *p)
{
VirtualConsole *vc = p;
switch (vc->type) {
case GD_VC_GFX:
qemu_console_unregister_listener(&vc->gfx.dcl);
#if defined(CONFIG_OPENGL)
if (display_opengl) {
qemu_console_set_display_gl_ctx(vc->gfx.dcl.con, NULL);
}
if (vc->gfx.ectx) {
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
} else if (gtk_use_gl_area) {
gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
}
if (vc->gfx.gls) {
surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
qemu_gl_fini_shader(vc->gfx.gls);
}
egl_fb_destroy(&vc->gfx.guest_fb);
egl_fb_destroy(&vc->gfx.win_fb);
egl_fb_destroy(&vc->gfx.cursor_fb);
if (vc->gfx.esurface) {
eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
}
if (vc->gfx.ectx) {
eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
}
#endif
qkbd_state_free(vc->gfx.kbd);
if (vc->gfx.surface) {
cairo_surface_destroy(vc->gfx.surface);
}
if (vc->gfx.convert) {
pixman_image_unref(vc->gfx.convert);
}
break;
case GD_VC_VTE:
#ifdef CONFIG_VTE
fifo8_destroy(&vc->vte.out_fifo);
#endif
break;
}
if (vc->window) {
gtk_widget_destroy(vc->window);
} else if (vc->tab_item) {
gtk_widget_destroy(vc->tab_item);
}
if (vc->menu_item) {
gtk_widget_destroy(vc->menu_item);
}
g_free(vc->label);
g_free(vc);
}
static GtkDisplayState *gtk_display_state;
static gboolean gtkinit;
static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
@@ -2523,6 +2718,8 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
}
assert(opts->type == DISPLAY_TYPE_GTK);
s = g_malloc0(sizeof(*s));
gtk_display_state = s;
s->vcs = g_ptr_array_new_with_free_func(gd_vc_free);
s->opts = opts;
theme = gtk_icon_theme_get_default();
@@ -2560,12 +2757,15 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
s->mouse_mode_notifier.notify = gd_mouse_mode_change;
qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
qemu_add_vm_change_state_handler(gd_change_runstate, s);
s->vmse = qemu_add_vm_change_state_handler(gd_change_runstate, s);
gtk_window_set_icon_name(GTK_WINDOW(s->window), "qemu");
gd_create_menus(s, opts);
s->console_notifier.notify = gd_console_notify;
qemu_console_add_notifier(&s->console_notifier);
gd_connect_signals(s);
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
@@ -2580,12 +2780,11 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
gtk_widget_show_all(s->window);
for (idx = 0;; idx++) {
QemuConsole *con = qemu_console_lookup_by_index(idx);
if (!con) {
break;
for (idx = 0; idx < s->vcs->len; idx++) {
VirtualConsole *v = g_ptr_array_index(s->vcs, idx);
if (v->type == GD_VC_GFX) {
gtk_widget_realize(v->gfx.drawing_area);
}
gtk_widget_realize(s->vc[idx].gfx.drawing_area);
}
if (opts->u.gtk.has_show_menubar &&
@@ -2679,10 +2878,30 @@ static void early_gtk_display_init(DisplayOptions *opts)
#endif
}
static void gtk_display_cleanup(void)
{
GtkDisplayState *s = gtk_display_state;
if (!s) {
return;
}
qemu_del_vm_change_state_handler(s->vmse);
qemu_console_remove_notifier(&s->console_notifier);
qemu_remove_mouse_mode_change_notifier(&s->mouse_mode_notifier);
gd_clipboard_cleanup(s);
g_signal_handlers_disconnect_by_func(s->notebook,
G_CALLBACK(gd_change_page), s);
g_clear_pointer(&s->vcs, g_ptr_array_unref);
g_clear_pointer(&s->window, gtk_widget_destroy);
g_clear_object(&s->null_cursor);
g_clear_pointer(&gtk_display_state, g_free);
}
static QemuDisplay qemu_display_gtk = {
.type = DISPLAY_TYPE_GTK,
.early_init = early_gtk_display_init,
.init = gtk_display_init,
.cleanup = gtk_display_cleanup,
.vc = "vc",
};

View File

@@ -42,13 +42,14 @@ libui_sources = files(
'kbd-state.c',
'keymaps.c',
'qemu-pixman.c',
'spice-module.c',
'vgafont.c',
)
if pixman.found()
libui_sources += files('cp437.c', 'vt100.c')
endif
libui = static_library('qemuui', libui_sources + genh,
dependencies: [pixman],
dependencies: [pixman, spice_headers],
build_by_default: false)
ui = declare_dependency(objects: libui.extract_all_objects(recursive: false), dependencies: [pixman])
system_ss.add(png)
@@ -64,7 +65,6 @@ system_ss.add(when: pixman, if_true: files('console-vc.c'), if_false: files('con
if dbus_display
system_ss.add(files('dbus-module.c'))
endif
system_ss.add([spice_headers, files('spice-module.c')])
system_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
if host_os == 'linux'

View File

@@ -790,9 +790,25 @@ static void sdl_mouse_define(DisplayChangeListener *dcl,
static void sdl_cleanup(void)
{
if (guest_sprite) {
SDL_FreeCursor(guest_sprite);
int i;
if (!sdl2_console) {
return;
}
qemu_remove_mouse_mode_change_notifier(&mouse_mode_notifier);
for (i = 0; i < sdl2_num_outputs; i++) {
qemu_console_unregister_listener(&sdl2_console[i].dcl);
qkbd_state_free(sdl2_console[i].kbd);
sdl2_window_destroy(&sdl2_console[i]);
}
g_clear_pointer(&sdl2_console, g_free);
sdl2_num_outputs = 0;
g_clear_pointer(&guest_sprite, SDL_FreeCursor);
g_clear_pointer(&guest_sprite_surface, SDL_FreeSurface);
g_clear_pointer(&sdl_cursor_hidden, SDL_FreeCursor);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
@@ -998,8 +1014,6 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
sdl_grab_start(&sdl2_console[0]);
}
atexit(sdl_cleanup);
/* SDL's event polling (in dpy_refresh) must happen on the main thread. */
qemu_main = NULL;
}
@@ -1008,6 +1022,7 @@ static QemuDisplay qemu_display_sdl2 = {
.type = DISPLAY_TYPE_SDL,
.early_init = sdl2_display_early_init,
.init = sdl2_display_init,
.cleanup = sdl_cleanup,
};
static void register_sdl1(void)

View File

@@ -119,16 +119,17 @@ static const TypeInfo char_vc_type_info = {
.class_size = sizeof(VCChardevClass),
};
static void spice_app_atexit(void)
static void spice_app_cleanup(void)
{
if (sock_path) {
unlink(sock_path);
g_clear_pointer(&sock_path, g_free);
}
if (tmp_dir) {
rmdir(tmp_dir);
tmp_dir = NULL;
}
g_free(sock_path);
g_free(app_dir);
g_clear_pointer(&app_dir, g_free);
}
static void spice_app_display_early_init(DisplayOptions *opts)
@@ -146,8 +147,6 @@ static void spice_app_display_early_init(DisplayOptions *opts)
exit(1);
}
atexit(spice_app_atexit);
if (qemu_name) {
app_dir = g_build_filename(g_get_user_runtime_dir(),
"qemu", qemu_name, NULL);
@@ -218,6 +217,7 @@ static QemuDisplay qemu_display_spice_app = {
.type = DISPLAY_TYPE_SPICE_APP,
.early_init = spice_app_display_early_init,
.init = spice_app_display_init,
.cleanup = spice_app_cleanup,
.vc = "vc",
};

View File

@@ -651,12 +651,15 @@ static void vm_change_state_handler(void *opaque, bool running,
}
}
static VMChangeStateEntry *vm_change_entry;
void qemu_spice_display_init_done(void)
{
if (runstate_is_running()) {
qemu_spice_display_start();
}
qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
vm_change_entry =
qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
}
static void qemu_spice_init(void)
@@ -894,7 +897,8 @@ static int qemu_spice_add_interface(SpiceBaseInstance *sin)
spice_server = spice_server_new();
spice_server_set_sasl_appname(spice_server, "qemu");
spice_server_init(spice_server, &core_interface);
qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
vm_change_entry =
qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
}
return spice_server_add_interface(spice_server, sin);
@@ -1005,8 +1009,25 @@ int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd)
return spice_display_is_running;
}
static void qemu_spice_cleanup(void)
{
if (!spice_server) {
return;
}
qemu_spice_display_cleanup();
qemu_spice_input_cleanup();
migration_remove_notifier(&migration_state);
g_clear_pointer(&spice_consoles, g_slist_free);
g_clear_pointer(&auth_passwd, g_free);
g_clear_pointer(&spice_server, spice_server_destroy);
g_clear_pointer(&vm_change_entry, qemu_del_vm_change_state_handler);
using_spice = 0;
}
static struct QemuSpiceOps real_spice_ops = {
.init = qemu_spice_init,
.cleanup = qemu_spice_cleanup,
.display_init = qemu_spice_display_init,
.migrate_info = qemu_spice_migrate_info,
.set_passwd = qemu_spice_set_passwd,

View File

@@ -34,6 +34,8 @@ bool spice_opengl;
bool spice_remote_client;
int spice_max_refresh_rate;
static GPtrArray *spice_displays;
int qemu_spice_rect_is_empty(const QXLRect* r)
{
return r->top == r->bottom || r->left == r->right;
@@ -1421,6 +1423,54 @@ static void qemu_spice_display_init_one(QemuConsole *con)
qemu_console_set_display_gl_ctx(con, &ssd->dgc);
}
qemu_console_register_listener(con, &ssd->dcl, ops);
g_ptr_array_add(spice_displays, ssd);
}
void qemu_spice_display_cleanup(void)
{
if (!spice_displays) {
return;
}
for (guint i = 0; i < spice_displays->len; i++) {
SimpleSpiceDisplay *ssd = g_ptr_array_index(spice_displays, i);
SimpleSpiceUpdate *update;
qemu_console_unregister_listener(&ssd->dcl);
#ifdef HAVE_SPICE_GL
if (spice_opengl) {
qemu_console_set_display_gl_ctx(ssd->dcl.con, NULL);
}
#endif
if (ssd->ds) {
qemu_spice_destroy_host_primary(ssd);
}
qemu_spice_del_memslot(ssd, MEMSLOT_GROUP_HOST, 0);
spice_server_remove_interface(&ssd->qxl.base);
while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) {
QTAILQ_REMOVE(&ssd->updates, update, next);
qemu_spice_destroy_update(ssd, update);
}
g_clear_pointer(&ssd->ptr_define, g_free);
g_clear_pointer(&ssd->ptr_move, g_free);
g_clear_pointer(&ssd->cursor, cursor_unref);
g_clear_pointer(&ssd->surface, pixman_image_unref);
g_clear_pointer(&ssd->mirror, pixman_image_unref);
g_clear_pointer(&ssd->buf, g_free);
#ifdef HAVE_SPICE_GL
g_clear_pointer(&ssd->gl_unblock_bh, qemu_bh_delete);
g_clear_pointer(&ssd->gl_unblock_timer, timer_free);
g_clear_pointer(&ssd->gls, qemu_gl_fini_shader);
egl_fb_destroy(&ssd->guest_fb);
egl_fb_destroy(&ssd->blit_fb);
egl_fb_destroy(&ssd->cursor_fb);
#endif
qemu_mutex_destroy(&ssd->lock);
g_free(ssd);
}
g_clear_pointer(&spice_displays, g_ptr_array_unref);
}
void qemu_spice_display_init(void)
@@ -1431,6 +1481,8 @@ void qemu_spice_display_init(void)
const char *str;
int i;
spice_displays = g_ptr_array_new();
str = qemu_opt_get(opts, "display");
if (str) {
int head = qemu_opt_get_number(opts, "head", 0);

View File

@@ -242,24 +242,41 @@ static void mouse_mode_notifier(Notifier *notifier, void *data)
pointer->absolute = is_absolute;
}
static QemuSpiceKbd *spice_kbd;
static QemuSpicePointer *spice_pointer;
void qemu_spice_input_init(void)
{
QemuSpiceKbd *kbd;
QemuSpicePointer *pointer;
spice_kbd = g_new0(QemuSpiceKbd, 1);
spice_kbd->sin.base.sif = &kbd_interface.base;
qemu_spice.add_interface(&spice_kbd->sin.base);
spice_kbd->led_notifier.notify = kbd_leds;
qemu_input_led_notifier_add(&spice_kbd->led_notifier);
kbd = g_malloc0(sizeof(*kbd));
kbd->sin.base.sif = &kbd_interface.base;
qemu_spice.add_interface(&kbd->sin.base);
kbd->led_notifier.notify = kbd_leds;
qemu_input_led_notifier_add(&kbd->led_notifier);
spice_pointer = g_new0(QemuSpicePointer, 1);
spice_pointer->mouse.base.sif = &mouse_interface.base;
spice_pointer->tablet.base.sif = &tablet_interface.base;
qemu_spice.add_interface(&spice_pointer->mouse.base);
pointer = g_malloc0(sizeof(*pointer));
pointer->mouse.base.sif = &mouse_interface.base;
pointer->tablet.base.sif = &tablet_interface.base;
qemu_spice.add_interface(&pointer->mouse.base);
pointer->absolute = false;
pointer->mouse_mode.notify = mouse_mode_notifier;
qemu_add_mouse_mode_change_notifier(&pointer->mouse_mode);
mouse_mode_notifier(&pointer->mouse_mode, NULL);
spice_pointer->absolute = false;
spice_pointer->mouse_mode.notify = mouse_mode_notifier;
qemu_add_mouse_mode_change_notifier(&spice_pointer->mouse_mode);
mouse_mode_notifier(&spice_pointer->mouse_mode, NULL);
}
void qemu_spice_input_cleanup(void)
{
if (spice_pointer) {
qemu_remove_mouse_mode_change_notifier(&spice_pointer->mouse_mode);
if (spice_pointer->absolute) {
spice_server_remove_interface(&spice_pointer->tablet.base);
}
spice_server_remove_interface(&spice_pointer->mouse.base);
g_clear_pointer(&spice_pointer, g_free);
}
if (spice_kbd) {
qemu_input_led_notifier_remove(&spice_kbd->led_notifier);
spice_server_remove_interface(&spice_kbd->sin.base);
g_clear_pointer(&spice_kbd, g_free);
}
}

View File

@@ -62,6 +62,10 @@ static int qemu_spice_display_add_client_stub(int csock, int skipauth,
return -1;
}
static void qemu_spice_cleanup_stub(void)
{
}
struct QemuSpiceOps qemu_spice = {
.init = qemu_spice_init_stub,
.display_init = qemu_spice_display_init_stub,
@@ -69,6 +73,7 @@ struct QemuSpiceOps qemu_spice = {
.set_passwd = qemu_spice_set_passwd_stub,
.set_pw_expire = qemu_spice_set_pw_expire_stub,
.display_add_client = qemu_spice_display_add_client_stub,
.cleanup = qemu_spice_cleanup_stub,
};
#ifdef CONFIG_SPICE

View File

@@ -348,6 +348,7 @@ qmp_screendump(const char *filename, const char *device,
}
}
object_ref(con);
qemu_console_co_wait_update(con);
/*
@@ -358,9 +359,11 @@ qmp_screendump(const char *filename, const char *device,
surface = qemu_console_surface(con);
if (!surface) {
error_setg(errp, "no surface");
object_unref(con);
return;
}
image = pixman_image_ref(surface->image);
object_unref(con);
fd = qemu_create(filename, O_WRONLY | O_TRUNC | O_BINARY, 0666, errp);
if (fd == -1) {

View File

@@ -28,7 +28,7 @@ void notifier_list_add(NotifierList *list, Notifier *notifier)
void notifier_remove(Notifier *notifier)
{
QLIST_REMOVE(notifier, node);
QLIST_SAFE_REMOVE(notifier, node);
}
void notifier_list_notify(NotifierList *list, void *data)