mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
90
docs/devel/vfio-mdpy.rst
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
19
ui/cocoa.m
19
ui/cocoa.m
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
77
ui/console.c
77
ui/console.c
@@ -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
|
||||
|
||||
17
ui/curses.c
17
ui/curses.c
@@ -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)
|
||||
|
||||
@@ -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
108
ui/dbus.c
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
387
ui/gtk.c
@@ -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(>k_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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
23
ui/sdl2.c
23
ui/sdl2.c
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user